VLCMovieViewController.m 68.3 KB
Newer Older
1 2 3 4
/*****************************************************************************
 * VLCMovieViewController.m
 * VLC for iOS
 *****************************************************************************
Felix Paul Kühne's avatar
Felix Paul Kühne committed
5
 * Copyright (c) 2013-2014 VideoLAN. All rights reserved.
6 7 8 9 10 11 12 13 14 15 16
 * $Id$
 *
 * Authors: Felix Paul Kühne <fkuehne # videolan.org>
 *          Gleb Pinigin <gpinigin # gmail.com>
 *          Ahmad Harb <harb.dev.leb # gmail.com>
 *          Fabio Ritrovato <sephiroth87 # videolan.org>
 *          Pierre SAGASPE <pierre.sagaspe # me.com>
 *          Jean-Baptiste Kempf <jb # videolan.org>
 *
 * Refer to the COPYING file of the official project for license.
 *****************************************************************************/
Felix Paul Kühne's avatar
Felix Paul Kühne committed
17

18
#import "VLCMovieViewController.h"
19
#import "VLCExternalDisplayController.h"
20
#import <AVFoundation/AVFoundation.h>
21
#import <CommonCrypto/CommonDigest.h>
22
#import "UIDevice+SpeedCategory.h"
23
#import "VLCBugreporter.h"
24
#import "VLCThumbnailsCache.h"
25 26
#import "VLCTrackSelectorTableViewCell.h"
#import "VLCTrackSelectorHeaderView.h"
27 28 29

#import "OBSlider.h"
#import "VLCStatusLabel.h"
Felix Paul Kühne's avatar
Felix Paul Kühne committed
30

31
#define INPUT_RATE_DEFAULT  1000.
32 33
#define FORWARD_SWIPE_DURATION 30
#define BACKWARD_SWIPE_DURATION 10
34

35 36 37
#define TRACK_SELECTOR_TABLEVIEW_CELL @"track selector table view cell"
#define TRACK_SELECTOR_TABLEVIEW_SECTIONHEADER @"track selector table view section header"

38 39 40 41
#define LOCKCHECK \
if (_interfaceIsLocked) \
return

42
@interface VLCMovieViewController () <UIGestureRecognizerDelegate, AVAudioSessionDelegate, VLCMediaDelegate, UITableViewDataSource, UITableViewDelegate>
43
{
44
    VLCMediaListPlayer *_listPlayer;
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
    VLCMediaPlayer *_mediaPlayer;

    BOOL _controlsHidden;
    BOOL _videoFiltersHidden;
    BOOL _playbackSpeedViewHidden;

    UIActionSheet *_subtitleActionSheet;
    UIActionSheet *_audiotrackActionSheet;

    float _currentPlaybackRate;
    NSArray *_aspectRatios;
    NSUInteger _currentAspectRatioMask;

    NSTimer *_idleTimer;

60
    BOOL _shouldResumePlaying;
61
    BOOL _viewAppeared;
62
    BOOL _displayRemainingTime;
63
    BOOL _positionSet;
64
    BOOL _playerIsSetup;
65
    BOOL _isScrubbing;
66
    BOOL _interfaceIsLocked;
Ahmad Harb's avatar
Ahmad Harb committed
67 68

    BOOL _swipeGesturesEnabled;
69
    UIPinchGestureRecognizer *_pinchRecognizer;
Ahmad Harb's avatar
Ahmad Harb committed
70 71 72
    UIPanGestureRecognizer *_panRecognizer;
    UISwipeGestureRecognizer *_swipeRecognizerLeft;
    UISwipeGestureRecognizer *_swipeRecognizerRight;
73
    UITapGestureRecognizer *_tapRecognizer;
74
    UITapGestureRecognizer *_tapOnVideoRecognizer;
75 76 77

    UIView *_trackSelectorContainer;
    UITableView *_trackSelectorTableView;
78 79
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
80 81
@property (nonatomic, strong) UIPopoverController *masterPopoverController;
@property (nonatomic, strong) UIWindow *externalWindow;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
82 83
@end

84
@implementation VLCMovieViewController
Felix Paul Kühne's avatar
Felix Paul Kühne committed
85

86 87 88 89 90 91 92
+ (void)initialize
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSDictionary *appDefaults = @{kVLCShowRemainingTime : @(YES)};
    [defaults registerDefaults:appDefaults];
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
93 94
- (void)dealloc
{
95 96
    [[NSNotificationCenter defaultCenter] removeObserver:self];

97
    if (_tapRecognizer)
98
        [self.view removeGestureRecognizer:_tapRecognizer];
Ahmad Harb's avatar
Ahmad Harb committed
99
    if (_swipeRecognizerLeft)
100
        [self.view removeGestureRecognizer:_swipeRecognizerLeft];
Ahmad Harb's avatar
Ahmad Harb committed
101
    if (_swipeRecognizerRight)
102
        [self.view removeGestureRecognizer:_swipeRecognizerRight];
Ahmad Harb's avatar
Ahmad Harb committed
103
    if (_panRecognizer)
104
        [self.view removeGestureRecognizer:_panRecognizer];
105 106 107 108
    if (_pinchRecognizer)
        [self.view removeGestureRecognizer:_pinchRecognizer];
    [self.view removeGestureRecognizer:_tapOnVideoRecognizer];

109 110 111 112
    _tapRecognizer = nil;
    _swipeRecognizerLeft = nil;
    _swipeRecognizerRight = nil;
    _panRecognizer = nil;
113 114 115 116
    _pinchRecognizer = nil;
    _tapOnVideoRecognizer = nil;

    [[NSNotificationCenter defaultCenter] removeObserver:self];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
117 118
}

119
#pragma mark - Managing the media item
Felix Paul Kühne's avatar
Felix Paul Kühne committed
120

121
- (void)setFileFromMediaLibrary:(id)newFile
Felix Paul Kühne's avatar
Felix Paul Kühne committed
122
{
123
    if (_fileFromMediaLibrary != newFile) {
124
        [self _stopPlayback];
125
        _fileFromMediaLibrary = newFile;
126 127 128
        if (_viewAppeared)
            [self _startPlayback];
    }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
129

130
    if (self.masterPopoverController != nil)
Felix Paul Kühne's avatar
Felix Paul Kühne committed
131 132 133
        [self.masterPopoverController dismissPopoverAnimated:YES];
}

134 135
- (void)setUrl:(NSURL *)url
{
136 137 138 139 140
    [self _stopPlayback];
    _url = url;
    _playerIsSetup = NO;
    if (_viewAppeared)
        [self _startPlayback];
141 142
}

143 144 145 146 147 148 149 150 151
- (void)setMediaList:(VLCMediaList *)mediaList
{
    [self _stopPlayback];
    _mediaList = mediaList;
    _playerIsSetup = NO;
    if (_viewAppeared)
        [self _startPlayback];
}

152
- (void)viewDidLoad
Felix Paul Kühne's avatar
Felix Paul Kühne committed
153
{
154
    [super viewDidLoad];
155 156
    CGRect rect;

157 158
    self.wantsFullScreenLayout = YES;

159 160
    self.videoFilterView.hidden = YES;
    _videoFiltersHidden = YES;
161
    _hueLabel.text = NSLocalizedString(@"VFILTER_HUE", nil);
162 163
    _hueSlider.accessibilityLabel = _hueLabel.text;
    _hueSlider.isAccessibilityElement = YES;
164
    _contrastLabel.text = NSLocalizedString(@"VFILTER_CONTRAST", nil);
165 166
    _contrastSlider.accessibilityLabel = _contrastLabel.text;
    _contrastSlider.isAccessibilityElement = YES;
167
    _brightnessLabel.text = NSLocalizedString(@"VFILTER_BRIGHTNESS", nil);
168 169
    _brightnessSlider.accessibilityLabel = _brightnessLabel.text;
    _brightnessSlider.isAccessibilityElement = YES;
170
    _saturationLabel.text = NSLocalizedString(@"VFILTER_SATURATION", nil);
171 172
    _saturationSlider.accessibilityLabel = _saturationLabel.text;
    _saturationSlider.isAccessibilityElement = YES;
173
    _gammaLabel.text = NSLocalizedString(@"VFILTER_GAMMA", nil);
174 175
    _gammaSlider.accessibilityLabel = _gammaLabel.text;
    _gammaSlider.isAccessibilityElement = YES;
176
    _playbackSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SPEED", nil);
177 178
    _playbackSpeedSlider.accessibilityLabel = _playbackSpeedLabel.text;
    _playbackSpeedSlider.isAccessibilityElement = YES;
179 180 181 182 183 184
    _audioDelayLabel.text = NSLocalizedString(@"AUDIO_DELAY", nil);
    _audioDelaySlider.accessibilityLabel = _audioDelayLabel.text;
    _audioDelaySlider.isAccessibilityElement = YES;
    _spuDelayLabel.text = NSLocalizedString(@"SPU_DELAY", nil);
    _spuDelaySlider.accessibilityLabel = _spuDelayLabel.text;
    _spuDelaySlider.isAccessibilityElement = YES;
185

186
    _positionSlider.accessibilityLabel = NSLocalizedString(@"PLAYBACK_POSITION", nil);
187 188 189
    _positionSlider.isAccessibilityElement = YES;
    _timeDisplay.isAccessibilityElement = YES;

190 191 192 193
    _trackSwitcherButton.accessibilityLabel = NSLocalizedString(@"OPEN_TRACK_PANEL", nil);
    _trackSwitcherButton.isAccessibilityElement = YES;
    _trackSwitcherButtonLandscape.accessibilityLabel = NSLocalizedString(@"OPEN_TRACK_PANEL", nil);
    _trackSwitcherButtonLandscape.isAccessibilityElement = YES;
194 195
    _playbackSpeedButton.accessibilityLabel = _playbackSpeedLabel.text;
    _playbackSpeedButton.isAccessibilityElement = YES;
196 197
    _playbackSpeedButtonLandscape.accessibilityLabel = _playbackSpeedLabel.text;
    _playbackSpeedButtonLandscape.isAccessibilityElement = YES;
198
    _videoFilterButton.accessibilityLabel = NSLocalizedString(@"VIDEO_FILTER", nil);
199
    _videoFilterButton.isAccessibilityElement = YES;
200
    _videoFilterButtonLandscape.accessibilityLabel = NSLocalizedString(@"VIDEO_FILTER", nil);
201
    _videoFilterButtonLandscape.isAccessibilityElement = YES;
202
    _resetVideoFilterButton.accessibilityLabel = NSLocalizedString(@"VIDEO_FILTER_RESET_BUTTON", nil);
203
    _resetVideoFilterButton.isAccessibilityElement = YES;
204
    _aspectRatioButton.accessibilityLabel = NSLocalizedString(@"VIDEO_ASPECT_RATIO_BUTTON", nil);
205
    _aspectRatioButton.isAccessibilityElement = YES;
206
    _playPauseButton.accessibilityLabel = NSLocalizedString(@"PLAY_PAUSE_BUTTON", nil);
207
    _playPauseButton.isAccessibilityElement = YES;
208
    _playPauseButtonLandscape.accessibilityLabel = NSLocalizedString(@"PLAY_PAUSE_BUTTON", nil);
209
    _playPauseButtonLandscape.isAccessibilityElement = YES;
210
    _bwdButton.accessibilityLabel = NSLocalizedString(@"BWD_BUTTON", nil);
211
    _bwdButton.isAccessibilityElement = YES;
212
    _bwdButtonLandscape.accessibilityLabel = NSLocalizedString(@"BWD_BUTTON", nil);
213
    _bwdButtonLandscape.isAccessibilityElement = YES;
214
    _fwdButton.accessibilityLabel = NSLocalizedString(@"FWD_BUTTON", nil);
215
    _fwdButton.isAccessibilityElement = YES;
216
    _fwdButtonLandscape.accessibilityLabel = NSLocalizedString(@"FWD_BUTTON", nil);
217
    _fwdButtonLandscape.isAccessibilityElement = YES;
218
    _repeatButton.accessibilityLabel = NSLocalizedString(@"BUTTON_REPEAT", nil);
Felix Paul Kühne's avatar
Felix Paul Kühne committed
219
    _repeatButton.isAccessibilityElement = YES;
220

221
    _scrubHelpLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HELP", nil);
222

223 224
    self.playbackSpeedView.hidden = YES;
    _playbackSpeedViewHidden = YES;
225

226 227 228 229 230
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(handleExternalScreenDidConnect:)
                   name:UIScreenDidConnectNotification object:nil];
    [center addObserver:self selector:@selector(handleExternalScreenDidDisconnect:)
                   name:UIScreenDidDisconnectNotification object:nil];
231 232 233 234 235 236
    [center addObserver:self selector:@selector(applicationWillResignActive:)
                   name:UIApplicationWillResignActiveNotification object:nil];
    [center addObserver:self selector:@selector(applicationDidBecomeActive:)
                   name:UIApplicationDidBecomeActiveNotification object:nil];
    [center addObserver:self selector:@selector(applicationDidEnterBackground:)
                   name:UIApplicationDidEnterBackgroundNotification object:nil];
237 238
    [center addObserver:self selector:@selector(audioSessionRouteChange:)
                   name:AVAudioSessionRouteChangeNotification object:nil];
239

240 241
    _playingExternallyTitle.text = NSLocalizedString(@"PLAYING_EXTERNALLY_TITLE", nil);
    _playingExternallyDescription.text = NSLocalizedString(@"PLAYING_EXTERNALLY_DESC", nil);
242
    if ([self hasExternalDisplay])
243
        [self showOnExternalDisplay];
244

245 246
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";

247
    _movieView.userInteractionEnabled = NO;
248 249 250
    _tapOnVideoRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleControlsVisible)];
    _tapOnVideoRecognizer.delegate = self;
    [self.view addGestureRecognizer:_tapOnVideoRecognizer];
251

252 253 254
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    _displayRemainingTime = [[defaults objectForKey:kVLCShowRemainingTime] boolValue];
    _swipeGesturesEnabled = [[defaults objectForKey:kVLCSettingPlaybackGestures] boolValue];
255

256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
    _pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchGesture:)];
    _pinchRecognizer.delegate = self;
    [self.view addGestureRecognizer:_pinchRecognizer];

    _tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapRecognized)];
    [_tapRecognizer setNumberOfTouchesRequired:2];
    _panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panRecognized:)];
    [_panRecognizer setMinimumNumberOfTouches:1];
    [_panRecognizer setMaximumNumberOfTouches:1];

    _swipeRecognizerLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRecognized:)];
    _swipeRecognizerLeft.direction = UISwipeGestureRecognizerDirectionLeft;
    _swipeRecognizerRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRecognized:)];
    _swipeRecognizerRight.direction = UISwipeGestureRecognizerDirectionRight;

    [self.view addGestureRecognizer:_swipeRecognizerLeft];
    [self.view addGestureRecognizer:_swipeRecognizerRight];
    [self.view addGestureRecognizer:_panRecognizer];
    [self.view addGestureRecognizer:_tapRecognizer];
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerLeft];
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerRight];

    _panRecognizer.delegate = self;
    _swipeRecognizerRight.delegate = self;
    _swipeRecognizerLeft.delegate = self;
    _tapRecognizer.delegate = self;
282

283
    _aspectRatios = @[@"DEFAULT", @"FILL_TO_SCREEN", @"4:3", @"16:9", @"16:10", @"2.21:1"];
284

285
    [self.aspectRatioButton setImage:[UIImage imageNamed:@"ratioIcon"] forState:UIControlStateNormal];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
286
    if (SYSTEM_RUNS_IOS7_OR_LATER) {
287
        self.backButton.tintColor = [UIColor colorWithRed:(190.0f/255.0f) green:(190.0f/255.0f) blue:(190.0f/255.0f) alpha:1.];
288
        self.toolbar.tintColor = [UIColor whiteColor];
289
        self.toolbar.barStyle = UIBarStyleBlack;
290

291
        rect = self.resetVideoFilterButton.frame;
292 293
        rect.origin.y = rect.origin.y + 5.;
        self.resetVideoFilterButton.frame = rect;
294 295 296 297
        rect = self.toolbar.frame;
        rect.size.height = rect.size.height + rect.origin.y;
        rect.origin.y = 0;
        self.toolbar.frame = rect;
298 299 300 301 302 303 304 305 306 307
        rect = self.aspectRatioButton.frame;
        rect.size.width -= 19.;
        rect.origin.x += 19.;
        self.aspectRatioButton.frame = rect;
        rect = self.timeDisplay.frame;
        rect.origin.x += 19.;
        self.timeDisplay.frame = rect;
        rect = self.positionSlider.frame;
        rect.size.width += 19.;
        self.positionSlider.frame = rect;
308
    } else {
309
        rect = self.positionSlider.frame;
310 311
        rect.origin.y = rect.origin.y + 3.;
        self.positionSlider.frame = rect;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
312 313
        [self.aspectRatioButton setBackgroundImage:[UIImage imageNamed:@"ratioButton"] forState:UIControlStateNormal];
        [self.aspectRatioButton setBackgroundImage:[UIImage imageNamed:@"ratioButtonHighlight"] forState:UIControlStateHighlighted];
314
        [self.toolbar setBackgroundImage:[UIImage imageNamed:@"seekbarBg"] forBarMetrics:UIBarMetricsDefault];
315 316 317
        [self.backButton setBackgroundImage:[UIImage imageNamed:@"playbackDoneButton"] forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
        [self.backButton setBackgroundImage:[UIImage imageNamed:@"playbackDoneButtonHighlight"] forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
    }
318

319
    /* FIXME: there is a saner iOS 6+ API for this! */
320
    /* this looks a bit weird, but we need to support iOS 5 and should show the same appearance */
321 322 323 324 325 326 327
    void (^initVolumeSlider)(MPVolumeView *) = ^(MPVolumeView *volumeView){
        UISlider *volumeSlider = nil;
        for (id aView in volumeView.subviews){
            if ([[[aView class] description] isEqualToString:@"MPVolumeSlider"]){
                volumeSlider = (UISlider *)aView;
                break;
            }
328
        }
329 330 331 332 333 334 335 336 337 338 339 340 341
        if (!SYSTEM_RUNS_IOS7_OR_LATER) {
            [volumeSlider setMinimumTrackImage:[[UIImage imageNamed:@"sliderminiValue"]resizableImageWithCapInsets:UIEdgeInsetsMake(0, 4, 0, 0)] forState:UIControlStateNormal];
            [volumeSlider setMaximumTrackImage:[[UIImage imageNamed:@"slidermaxValue"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 4)] forState:UIControlStateNormal];
            [volumeSlider setThumbImage:[UIImage imageNamed:@"volumeballslider"] forState:UIControlStateNormal];
        } else
            [volumeView setVolumeThumbImage:[UIImage imageNamed:@"modernSliderKnob"] forState:UIControlStateNormal];
        [volumeSlider addTarget:self
                         action:@selector(volumeSliderAction:)
               forControlEvents:UIControlEventValueChanged];
    };

    initVolumeSlider(self.volumeView);
    initVolumeSlider(self.volumeViewLandscape);
342

343
    [[AVAudioSession sharedInstance] setDelegate:self];
344 345 346

    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
        self.positionSlider.scrubbingSpeedChangePositions = @[@(0.), @(100.), @(200.), @(300)];
347 348

    _playerIsSetup = NO;
349

350 351
    [self.movieView setAccessibilityLabel:NSLocalizedString(@"VO_VIDEOPLAYER_TITLE", nil)];
    [self.movieView setAccessibilityHint:NSLocalizedString(@"VO_VIDEOPLAYER_DOUBLETAP", nil)];
352 353 354 355 356 357 358 359 360 361

    rect = self.view.frame;

    _trackSelectorTableView = [[UITableView alloc] initWithFrame:CGRectMake(0., 0., 300., 320.) style:UITableViewStylePlain];
    _trackSelectorTableView.delegate = self;
    _trackSelectorTableView.dataSource = self;
    _trackSelectorTableView.separatorColor = [UIColor VLCDarkBackgroundColor];
    _trackSelectorTableView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
    [_trackSelectorTableView registerClass:[VLCTrackSelectorTableViewCell class] forCellReuseIdentifier:TRACK_SELECTOR_TABLEVIEW_CELL];
    [_trackSelectorTableView registerClass:[VLCTrackSelectorHeaderView class] forHeaderFooterViewReuseIdentifier:TRACK_SELECTOR_TABLEVIEW_SECTIONHEADER];
362
    _trackSelectorTableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
363 364 365

    _trackSelectorContainer = [[VLCFrostedGlasView alloc] initWithFrame:CGRectMake((rect.size.width - 300.) / 2., (rect.size.height - 320.) / 2., 300., 320.)];
    [_trackSelectorContainer addSubview:_trackSelectorTableView];
366
    _trackSelectorContainer.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleHeight;
367 368 369 370 371 372
    _trackSelectorContainer.hidden = YES;

    if ([[UIDevice currentDevice] speedCategory] >= 3) {
        _trackSelectorTableView.opaque = NO;
        _trackSelectorTableView.backgroundColor = [UIColor clearColor];
    } else
373
        _trackSelectorTableView.backgroundColor = [UIColor blackColor];
374 375

    [self.view addSubview:_trackSelectorContainer];
376
}
Felix Paul Kühne's avatar
Felix Paul Kühne committed
377

378 379 380 381 382
- (BOOL)_blobCheck
{
    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *directoryPath = searchPaths[0];

383
    if (![[NSFileManager defaultManager] fileExistsAtPath:[directoryPath stringByAppendingPathComponent:@"blob.bin"]])
384 385
        return NO;

386
    NSData *data = [NSData dataWithContentsOfFile:[directoryPath stringByAppendingPathComponent:@"blob.bin"]];
387
    uint8_t digest[CC_SHA1_DIGEST_LENGTH];
388
    CC_SHA1(data.bytes, (unsigned int)data.length, digest);
389 390 391 392 393 394 395 396 397 398 399 400

    NSMutableString *hash = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];

    for (unsigned int u = 0; u < CC_SHA1_DIGEST_LENGTH; u++)
        [hash appendFormat:@"%02x", digest[u]];

    if ([hash isEqualToString:kBlobHash])
        return YES;
    else
        return NO;
}

401 402
- (void)viewWillAppear:(BOOL)animated
{
403
    [super viewWillAppear:animated];
404

405 406 407 408 409 410
    /* reset audio meta data views */
    self.artworkImageView.image = nil;
    self.trackNameLabel.text = nil;
    self.artistNameLabel.text = nil;
    self.albumNameLabel.text = nil;

411 412
    _swipeGesturesEnabled = [[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingPlaybackGestures] boolValue];

413
    [self.navigationController setNavigationBarHidden:YES animated:YES];
414

Felix Paul Kühne's avatar
Felix Paul Kühne committed
415
    if (!SYSTEM_RUNS_IOS7_OR_LATER) {
416 417 418
        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
            [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackTranslucent;
    }
419

420 421 422 423 424 425
    [self _startPlayback];

    [self setControlsHidden:NO animated:YES];
    _viewAppeared = YES;
}

426 427 428 429 430
- (void)viewWillLayoutSubviews
{
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
        CGSize viewSize = self.view.frame.size;
        if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) {
431 432 433
            [_controllerPanel removeFromSuperview];
            _controllerPanelLandscape.frame = (CGRect){CGPointMake(0, viewSize.height - _controllerPanelLandscape.frame.size.height), CGSizeMake(viewSize.width, _controllerPanelLandscape.frame.size.height)};
            [self.view addSubview:_controllerPanelLandscape];
434
        } else {
435 436 437
            [_controllerPanelLandscape removeFromSuperview];
            _controllerPanel.frame = (CGRect){CGPointMake(0, viewSize.height - _controllerPanel.frame.size.height), CGSizeMake(viewSize.width, _controllerPanel.frame.size.height)};
            [self.view addSubview:_controllerPanel];
438 439 440 441
        }
    }
}

442 443 444 445 446
- (void)_startPlayback
{
    if (_playerIsSetup)
        return;

447 448
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

449
    if (!self.fileFromMediaLibrary && !self.url && !self.mediaList) {
450
        [self _stopPlayback];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
451
        return;
452
    }
453 454 455 456
    if (self.pathToExternalSubtitlesFile)
        _listPlayer = [[VLCMediaListPlayer alloc] initWithOptions:@[[NSString stringWithFormat:@"--%@=%@", kVLCSettingSubtitlesFilePath, self.pathToExternalSubtitlesFile]]];
    else
        _listPlayer = [[VLCMediaListPlayer alloc] init];
457

458
    _mediaPlayer = _listPlayer.mediaPlayer;
459 460
    [_mediaPlayer setDelegate:self];
    [_mediaPlayer setDrawable:self.movieView];
461 462
    if ([[defaults objectForKey:kVLCSettingPlaybackSpeedDefaultValue] floatValue] != 0)
        [_mediaPlayer setRate: [[defaults objectForKey:kVLCSettingPlaybackSpeedDefaultValue] floatValue]];
463 464 465 466
    if ([[defaults objectForKey:kVLCSettingDeinterlace] intValue] != 0)
        [_mediaPlayer setDeinterlaceFilter:@"blend"];
    else
        [_mediaPlayer setDeinterlaceFilter:nil];
467 468 469
    if (self.pathToExternalSubtitlesFile)
        [_mediaPlayer openVideoSubTitlesFromFile:self.pathToExternalSubtitlesFile];

470
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";
471

472
    VLCMedia *media;
473 474
    if (self.fileFromMediaLibrary) {
        MLFile *item = self.fileFromMediaLibrary;
475
        media = [VLCMedia mediaWithURL:[NSURL URLWithString:item.url]];
476 477 478 479
    } else if (self.mediaList) {
        media = [self.mediaList mediaAtIndex:self.itemInMediaListToBePlayedFirst];
        [media parse];
    } else {
480
        media = [VLCMedia mediaWithURL:self.url];
481 482
        [media parse];
    }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
483

484 485
    NSMutableDictionary *mediaDictionary = [[NSMutableDictionary alloc] init];
    [mediaDictionary setObject:[defaults objectForKey:kVLCSettingNetworkCaching] forKey:kVLCSettingNetworkCaching];
486 487 488
    [mediaDictionary setObject:[[defaults objectForKey:kVLCSettingStretchAudio] boolValue] ? kVLCSettingStretchAudioOnValue : kVLCSettingStretchAudioOffValue forKey:kVLCSettingStretchAudio];
    [mediaDictionary setObject:[defaults objectForKey:kVLCSettingTextEncoding] forKey:kVLCSettingTextEncoding];
    [mediaDictionary setObject:[defaults objectForKey:kVLCSettingSkipLoopFilter] forKey:kVLCSettingSkipLoopFilter];
489

490 491 492 493
    [NSTimeZone resetSystemTimeZone];
    NSString *tzName = [[NSTimeZone systemTimeZone] name];
    NSArray *tzNames = @[@"America/Adak", @"America/Anchorage", @"America/Boise", @"America/Chicago", @"America/Denver", @"America/Detroit", @"America/Indiana/Indianapolis", @"America/Indiana/Knox", @"America/Indiana/Marengo", @"America/Indiana/Petersburg", @"America/Indiana/Tell_City", @"America/Indiana/Vevay", @"America/Indiana/Vincennes", @"America/Indiana/Winamac", @"America/Juneau", @"America/Kentucky/Louisville", @"America/Kentucky/Monticello", @"America/Los_Angeles", @"America/Menominee", @"America/Metlakatla", @"America/New_York", @"America/Nome", @"America/North_Dakota/Beulah", @"America/North_Dakota/Center", @"America/North_Dakota/New_Salem", @"America/Phoenix", @"America/Puerto_Rico", @"America/Shiprock", @"America/Sitka", @"America/St_Thomas", @"America/Thule", @"America/Yakutat", @"Pacific/Guam", @"Pacific/Honolulu", @"Pacific/Johnston", @"Pacific/Kwajalein", @"Pacific/Midway", @"Pacific/Pago_Pago", @"Pacific/Saipan", @"Pacific/Wake"];

494
    if ([tzNames containsObject:tzName] || [[tzName stringByDeletingLastPathComponent] isEqualToString:@"US"]) {
495 496 497 498 499 500 501 502 503 504 505 506
        NSArray *tracksInfo = media.tracksInformation;
        for (NSUInteger x = 0; x < tracksInfo.count; x++) {
            if ([[tracksInfo[x] objectForKey:VLCMediaTracksInformationType] isEqualToString:VLCMediaTracksInformationTypeAudio])
            {
                NSInteger fourcc = [[tracksInfo[x] objectForKey:VLCMediaTracksInformationCodec] integerValue];

                switch (fourcc) {
                    case 540161377:
                    case 1647457633:
                    case 858612577:
                    case 862151027:
                    case 862151013:
507 508
                    case 1684566644:
                    case 2126701:
509
                    {
510
                        if (![self _blobCheck]) {
511
                            [mediaDictionary setObject:[NSNull null] forKey:@"no-audio"];
512 513
                            APLog(@"audio playback disabled because an unsupported codec was found");
                        }
514 515 516 517 518 519 520 521 522 523
                        break;
                    }

                    default:
                        break;
                }
            }
        }
    }

524 525 526 527 528 529 530 531 532 533
    if (self.mediaList) {
        VLCMediaList *list = self.mediaList;
        NSUInteger count = list.count;
        for (NSUInteger x = 0; x < count; x++)
            [[list mediaAtIndex:x] addOptions:mediaDictionary];
        [_listPlayer setMediaList:self.mediaList];
    } else {
        [media addOptions:mediaDictionary];
        [_listPlayer setRootMedia:media];
    }
534
    [_listPlayer setRepeatMode:VLCDoNotRepeat];
535

536
    self.positionSlider.value = 0.;
537
    [self.timeDisplay setTitle:@"" forState:UIControlStateNormal];
538
    self.timeDisplay.accessibilityLabel = @"";
539
    [self.repeatButton setImage:[UIImage imageNamed:@"repeat"] forState:UIControlStateNormal];
540
    [self.repeatButtonLandscape setImage:[UIImage imageNamed:@"repeat"] forState:UIControlStateNormal];
541

542
    if (![self _isMediaSuitableForDevice]) {
543
        UIAlertView * alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"DEVICE_TOOSLOW_TITLE", nil) message:[NSString stringWithFormat:NSLocalizedString(@"DEVICE_TOOSLOW", nil), [[UIDevice currentDevice] model], self.fileFromMediaLibrary.title] delegate:self cancelButtonTitle:NSLocalizedString(@"BUTTON_CANCEL", nil) otherButtonTitles:NSLocalizedString(@"BUTTON_OPEN", nil), nil];
544 545 546
        [alert show];
    } else
        [self _playNewMedia];
547

548 549
    if (![self hasExternalDisplay])
        self.brightnessSlider.value = [UIScreen mainScreen].brightness * 2.;
550 551 552 553
}

- (BOOL)_isMediaSuitableForDevice
{
554
    if (!self.fileFromMediaLibrary)
555 556
        return YES;

557
    NSUInteger totalNumberOfPixels = [[[self.fileFromMediaLibrary videoTrack] valueForKey:@"width"] doubleValue] * [[[self.fileFromMediaLibrary videoTrack] valueForKey:@"height"] doubleValue];
558

559
    NSInteger speedCategory = [[UIDevice currentDevice] speedCategory];
560

561
    if (speedCategory == 1) {
562 563
        // iPhone 3GS, iPhone 4, first gen. iPad, 3rd and 4th generation iPod touch
        return (totalNumberOfPixels < 600000); // between 480p and 720p
564
    } else if (speedCategory == 2) {
565 566
        // iPhone 4S, iPad 2 and 3, iPod 4 and 5
        return (totalNumberOfPixels < 922000); // 720p
567
    } else if (speedCategory == 3) {
568 569
        // iPhone 5, iPad 4
        return (totalNumberOfPixels < 2074000); // 1080p
570 571 572
    } else if (speedCategory == 4) {
        // iPhone 6, 2014 iPads
        return (totalNumberOfPixels < 8850000); // 4K
573 574 575 576 577 578 579 580 581
    }

    return YES;
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (buttonIndex == 1)
        [self _playNewMedia];
582 583
    else {
        [self _stopPlayback];
584
        [self closePlayback:nil];
585
    }
586 587 588 589
}

- (void)_playNewMedia
{
590
    NSNumber *playbackPositionInTime = @(0);
591
    CGFloat lastPosition = .0;
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606
    NSInteger duration = 0;
    MLFile *matchedFile;

    if (self.fileFromMediaLibrary)
        matchedFile = self.fileFromMediaLibrary;
    else if (self.mediaList) {
        NSArray *matches = [MLFile fileForURL:[[[self.mediaList mediaAtIndex:self.itemInMediaListToBePlayedFirst] url] absoluteString]];
        if (matches.count > 0) {
            matchedFile = matches[0];
            lastPosition = matchedFile.lastPosition.floatValue;
        }
    }
    if (matchedFile.lastPosition)
        lastPosition = matchedFile.lastPosition.floatValue;
    duration = matchedFile.duration.intValue;
607 608 609
    if (lastPosition < .95) {
        if (duration != 0)
            playbackPositionInTime = @(lastPosition * (duration / 1000.));
610
    }
611
    if (playbackPositionInTime.intValue > 0 && (duration * lastPosition - duration) < -60000) {
612 613 614
        [_mediaPlayer.media addOptions:@{@"start-time": playbackPositionInTime}];
        APLog(@"set starttime to %i", playbackPositionInTime.intValue);
    }
615

616 617 618
    [_mediaPlayer addObserver:self forKeyPath:@"time" options:0 context:nil];
    [_mediaPlayer addObserver:self forKeyPath:@"remainingTime" options:0 context:nil];

619
    if (self.mediaList)
620
        [_listPlayer playItemAtIndex:self.itemInMediaListToBePlayedFirst];
621
    else
622
        [_listPlayer playMedia:_listPlayer.rootMedia];
623

624 625 626 627 628
    if (matchedFile) {
        if (matchedFile.lastAudioTrack.intValue > 0)
            _mediaPlayer.currentAudioTrackIndex = matchedFile.lastAudioTrack.intValue;
        if (matchedFile.lastSubtitleTrack.intValue > 0)
            _mediaPlayer.currentVideoSubTitleIndex = matchedFile.lastSubtitleTrack.intValue;
629
    }
630

631 632
    self.playbackSpeedSlider.value = [self _playbackSpeed];
    [self _updatePlaybackSpeedIndicator];
633

634 635 636 637 638 639
    self.audioDelaySlider.value = _mediaPlayer.currentAudioPlaybackDelay / 1000000;
    self.audioDelayIndicator.text = [NSString stringWithFormat:@"%1.00f s", self.audioDelaySlider.value];

    self.spuDelaySlider.value = _mediaPlayer.currentVideoSubTitleDelay / 1000000;
    self.spuDelayIndicator.text = [NSString stringWithFormat:@"%1.00f s", self.spuDelaySlider.value];

640
    _currentAspectRatioMask = 0;
641 642 643
    _mediaPlayer.videoAspectRatio = NULL;

    /* some demuxers don't respect :start-time, so re-try here */
644
    if (lastPosition < .95 && _mediaPlayer.position < lastPosition && (duration * lastPosition - duration) < -60000)
645
        _mediaPlayer.position = lastPosition;
646

647
    [self _resetIdleTimer];
648
    _playerIsSetup = YES;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
649 650
}

651
- (void)viewWillDisappear:(BOOL)animated
Felix Paul Kühne's avatar
Felix Paul Kühne committed
652
{
653
    [self _stopPlayback];
654
    _viewAppeared = NO;
655
    if (_idleTimer) {
656
        [_idleTimer invalidate];
657 658
        _idleTimer = nil;
    }
659
    [self.navigationController setNavigationBarHidden:NO animated:YES];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
660
    if (!SYSTEM_RUNS_IOS7_OR_LATER)
661
        [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackOpaque;
662
    [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
663
    [super viewWillDisappear:animated];
664

Felix Paul Kühne's avatar
Felix Paul Kühne committed
665 666 667 668 669 670
    // hide filter UI for next run
    if (!_videoFiltersHidden)
        _videoFiltersHidden = YES;

    if (!_playbackSpeedViewHidden)
        _playbackSpeedViewHidden = YES;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
671 672
}

673 674 675
- (void)_stopPlayback
{
    if (_mediaPlayer) {
676 677 678 679 680 681 682 683
        @try {
            [_mediaPlayer removeObserver:self forKeyPath:@"time"];
            [_mediaPlayer removeObserver:self forKeyPath:@"remainingTime"];
        }
        @catch (NSException *exception) {
            APLog(@"we weren't an observer yet");
        }

684
        if (_mediaPlayer.media) {
685 686 687
            [_mediaPlayer pause];
            [self _saveCurrentState];
            [_mediaPlayer stop];
688
        }
689 690
        if (_mediaPlayer)
            _mediaPlayer = nil;
691 692
        if (_listPlayer)
            _listPlayer = nil;
693
    }
694 695
    if (_fileFromMediaLibrary)
        _fileFromMediaLibrary = nil;
696 697 698 699
    if (_mediaList)
        _mediaList = nil;
    if (_url)
        _url = nil;
700
    if (_pathToExternalSubtitlesFile) {
701
            NSFileManager *fileManager = [NSFileManager defaultManager];
702 703 704
            if ([fileManager fileExistsAtPath:_pathToExternalSubtitlesFile])
                [fileManager removeItemAtPath:_pathToExternalSubtitlesFile error:nil];
            _pathToExternalSubtitlesFile = nil;
705
    }
706 707 708
    _playerIsSetup = NO;
}

709 710
- (void)_saveCurrentState
{
711
    if (self.fileFromMediaLibrary) {
712
        @try {
713
            MLFile *item = self.fileFromMediaLibrary;
714 715 716 717 718 719 720
            item.lastPosition = @([_mediaPlayer position]);
            item.lastAudioTrack = @(_mediaPlayer.currentAudioTrackIndex);
            item.lastSubtitleTrack = @(_mediaPlayer.currentVideoSubTitleIndex);
        }
        @catch (NSException *exception) {
            APLog(@"failed to save current media state - file removed?");
        }
721 722 723 724 725 726 727 728
    } else {
        NSArray *files = [MLFile fileForURL:[[_mediaPlayer.media url] absoluteString]];
        if (files.count > 0) {
            MLFile *fileFromList = files[0];
            fileFromList.lastPosition = @([_mediaPlayer position]);
            fileFromList.lastAudioTrack = @(_mediaPlayer.currentAudioTrackIndex);
            fileFromList.lastSubtitleTrack = @(_mediaPlayer.currentVideoSubTitleIndex);
        }
729 730 731
    }
}

732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770
- (NSString *)_resolveFontName
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    BOOL bold = [[defaults objectForKey:kVLCSettingSubtitlesBoldFont] boolValue];
    NSString *font = [defaults objectForKey:kVLCSettingSubtitlesFont];
    NSDictionary *fontMap = @{
                              @"AmericanTypewriter":   @"AmericanTypewriter-Bold",
                              @"ArialMT":              @"Arial-BoldMT",
                              @"ArialHebrew":          @"ArialHebrew-Bold",
                              @"ChalkboardSE-Regular": @"ChalkboardSE-Bold",
                              @"CourierNewPSMT":       @"CourierNewPS-BoldMT",
                              @"Georgia":              @"Georgia-Bold",
                              @"GillSans":             @"GillSans-Bold",
                              @"GujaratiSangamMN":     @"GujaratiSangamMN-Bold",
                              @"STHeitiSC-Light":      @"STHeitiSC-Medium",
                              @"STHeitiTC-Light":      @"STHeitiTC-Medium",
                              @"HelveticaNeue":        @"HelveticaNeue-Bold",
                              @"HiraKakuProN-W3":      @"HiraKakuProN-W6",
                              @"HiraMinProN-W3":       @"HiraMinProN-W6",
                              @"HoeflerText-Regular":  @"HoeflerText-Black",
                              @"Kailasa":              @"Kailasa-Bold",
                              @"KannadaSangamMN":      @"KannadaSangamMN-Bold",
                              @"MalayalamSangamMN":    @"MalayalamSangamMN-Bold",
                              @"OriyaSangamMN":        @"OriyaSangamMN-Bold",
                              @"SinhalaSangamMN":      @"SinhalaSangamMN-Bold",
                              @"SnellRoundhand":       @"SnellRoundhand-Bold",
                              @"TamilSangamMN":        @"TamilSangamMN-Bold",
                              @"TeluguSangamMN":       @"TeluguSangamMN-Bold",
                              @"TimesNewRomanPSMT":    @"TimesNewRomanPS-BoldMT",
                              @"Zapfino":              @"Zapfino"
                              };

    if (!bold) {
        return font;
    } else {
        return fontMap[font];
    }
}

771 772 773 774 775 776 777 778 779 780 781 782 783 784
#pragma mark - remote events

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    [self becomeFirstResponder];
}

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
    [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
    [self resignFirstResponder];
785 786

    [[NSUserDefaults standardUserDefaults] setBool:_displayRemainingTime forKey:kVLCShowRemainingTime];
787 788 789 790 791 792 793 794 795 796 797
}

- (BOOL)canBecomeFirstResponder
{
    return YES;
}

- (void)remoteControlReceivedWithEvent:(UIEvent *)event
{
    switch (event.subtype) {
        case UIEventSubtypeRemoteControlPlay:
798
            [_listPlayer play];
799
            break;
800

801
        case UIEventSubtypeRemoteControlPause:
802
            [_listPlayer pause];
803 804 805
            break;

        case UIEventSubtypeRemoteControlTogglePlayPause:
806
            [self playPause];
807 808
            break;

809
        case UIEventSubtypeRemoteControlNextTrack:
810
            [self forward:nil];
811
            break;
812 813

        case UIEventSubtypeRemoteControlPreviousTrack:
814
            [self backward:nil];
815
            break;
816

Felix Paul Kühne's avatar
Felix Paul Kühne committed
817 818
        case UIEventSubtypeRemoteControlStop:
            [self closePlayback:nil];
819
            break;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
820

821 822 823 824 825
        default:
            break;
    }
}

826 827
#pragma mark - controls visibility

828 829
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)recognizer
{
830 831
    LOCKCHECK;

832 833 834
    if (!_swipeGesturesEnabled)
        return;

835 836 837 838
    if (recognizer.velocity < 0.)
        [self closePlayback:nil];
}

839 840
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
841
    if (touch.view != self.view)
842 843 844 845 846
        return NO;

    return YES;
}

Ahmad Harb's avatar
Ahmad Harb committed
847 848 849 850 851
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

852
- (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated
853
{
854
    _controlsHidden = hidden;
855
    CGFloat alpha = _controlsHidden? 0.0f: 1.0f;
856

857 858
    if (!_controlsHidden) {
        _controllerPanel.alpha = 0.0f;
859
        _controllerPanel.hidden = !_videoFiltersHidden;
860 861
        _controllerPanelLandscape.alpha = 0.0f;
        _controllerPanelLandscape.hidden = !_videoFiltersHidden;
862 863
        _toolbar.alpha = 0.0f;
        _toolbar.hidden = NO;
864 865
        _videoFilterView.alpha = 0.0f;
        _videoFilterView.hidden = _videoFiltersHidden;
866 867
        _playbackSpeedView.alpha = 0.0f;
        _playbackSpeedView.hidden = _playbackSpeedViewHidden;
868 869
        _trackSelectorContainer.alpha = 0.0f;
        _trackSelectorContainer.hidden = YES;
870
    }
871

872 873
    void (^animationBlock)() = ^() {
        _controllerPanel.alpha = alpha;