VLCMovieViewController.m 68.9 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

    rect = self.view.frame;
354 355 356 357 358 359 360 361 362
    CGFloat width;
    CGFloat height;
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
        width = 300.;
        height = 320.;
    } else {
        width = 450.;
        height = 470.;
    }
363

364
    _trackSelectorTableView = [[UITableView alloc] initWithFrame:CGRectMake(0., 0., width, height) style:UITableViewStylePlain];
365 366
    _trackSelectorTableView.delegate = self;
    _trackSelectorTableView.dataSource = self;
367
    _trackSelectorTableView.separatorColor = [UIColor clearColor];
368 369 370
    _trackSelectorTableView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
    [_trackSelectorTableView registerClass:[VLCTrackSelectorTableViewCell class] forCellReuseIdentifier:TRACK_SELECTOR_TABLEVIEW_CELL];
    [_trackSelectorTableView registerClass:[VLCTrackSelectorHeaderView class] forHeaderFooterViewReuseIdentifier:TRACK_SELECTOR_TABLEVIEW_SECTIONHEADER];
371
    _trackSelectorTableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
372

373
    _trackSelectorContainer = [[VLCFrostedGlasView alloc] initWithFrame:CGRectMake((rect.size.width - width) / 2., (rect.size.height - height) / 2., width, height)];
374
    [_trackSelectorContainer addSubview:_trackSelectorTableView];
375
    _trackSelectorContainer.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleHeight;
376 377 378 379 380 381
    _trackSelectorContainer.hidden = YES;

    if ([[UIDevice currentDevice] speedCategory] >= 3) {
        _trackSelectorTableView.opaque = NO;
        _trackSelectorTableView.backgroundColor = [UIColor clearColor];
    } else
382
        _trackSelectorTableView.backgroundColor = [UIColor blackColor];
383 384

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

387 388 389 390 391
- (BOOL)_blobCheck
{
    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *directoryPath = searchPaths[0];

392
    if (![[NSFileManager defaultManager] fileExistsAtPath:[directoryPath stringByAppendingPathComponent:@"blob.bin"]])
393 394
        return NO;

395
    NSData *data = [NSData dataWithContentsOfFile:[directoryPath stringByAppendingPathComponent:@"blob.bin"]];
396
    uint8_t digest[CC_SHA1_DIGEST_LENGTH];
397
    CC_SHA1(data.bytes, (unsigned int)data.length, digest);
398 399 400 401 402 403 404 405 406 407 408 409

    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;
}

410 411
- (void)viewWillAppear:(BOOL)animated
{
412
    [super viewWillAppear:animated];
413

414 415 416 417 418 419
    /* reset audio meta data views */
    self.artworkImageView.image = nil;
    self.trackNameLabel.text = nil;
    self.artistNameLabel.text = nil;
    self.albumNameLabel.text = nil;

420 421
    _swipeGesturesEnabled = [[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingPlaybackGestures] boolValue];

422
    [self.navigationController setNavigationBarHidden:YES animated:YES];
423

Felix Paul Kühne's avatar
Felix Paul Kühne committed
424
    if (!SYSTEM_RUNS_IOS7_OR_LATER) {
425 426 427
        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
            [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackTranslucent;
    }
428

429 430 431 432 433 434
    [self _startPlayback];

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

435 436 437 438 439
- (void)viewWillLayoutSubviews
{
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
        CGSize viewSize = self.view.frame.size;
        if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) {
440 441 442
            [_controllerPanel removeFromSuperview];
            _controllerPanelLandscape.frame = (CGRect){CGPointMake(0, viewSize.height - _controllerPanelLandscape.frame.size.height), CGSizeMake(viewSize.width, _controllerPanelLandscape.frame.size.height)};
            [self.view addSubview:_controllerPanelLandscape];
443
        } else {
444 445 446
            [_controllerPanelLandscape removeFromSuperview];
            _controllerPanel.frame = (CGRect){CGPointMake(0, viewSize.height - _controllerPanel.frame.size.height), CGSizeMake(viewSize.width, _controllerPanel.frame.size.height)};
            [self.view addSubview:_controllerPanel];
447 448 449 450
        }
    }
}

451 452 453 454 455
- (void)_startPlayback
{
    if (_playerIsSetup)
        return;

456 457
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

458
    if (!self.fileFromMediaLibrary && !self.url && !self.mediaList) {
459
        [self _stopPlayback];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
460
        return;
461
    }
462 463 464 465
    if (self.pathToExternalSubtitlesFile)
        _listPlayer = [[VLCMediaListPlayer alloc] initWithOptions:@[[NSString stringWithFormat:@"--%@=%@", kVLCSettingSubtitlesFilePath, self.pathToExternalSubtitlesFile]]];
    else
        _listPlayer = [[VLCMediaListPlayer alloc] init];
466

467
    _mediaPlayer = _listPlayer.mediaPlayer;
468 469
    [_mediaPlayer setDelegate:self];
    [_mediaPlayer setDrawable:self.movieView];
470 471
    if ([[defaults objectForKey:kVLCSettingPlaybackSpeedDefaultValue] floatValue] != 0)
        [_mediaPlayer setRate: [[defaults objectForKey:kVLCSettingPlaybackSpeedDefaultValue] floatValue]];
472 473 474 475
    if ([[defaults objectForKey:kVLCSettingDeinterlace] intValue] != 0)
        [_mediaPlayer setDeinterlaceFilter:@"blend"];
    else
        [_mediaPlayer setDeinterlaceFilter:nil];
476 477 478
    if (self.pathToExternalSubtitlesFile)
        [_mediaPlayer openVideoSubTitlesFromFile:self.pathToExternalSubtitlesFile];

479
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";
480

481
    VLCMedia *media;
482 483
    if (self.fileFromMediaLibrary) {
        MLFile *item = self.fileFromMediaLibrary;
484
        media = [VLCMedia mediaWithURL:[NSURL URLWithString:item.url]];
485 486 487 488
    } else if (self.mediaList) {
        media = [self.mediaList mediaAtIndex:self.itemInMediaListToBePlayedFirst];
        [media parse];
    } else {
489
        media = [VLCMedia mediaWithURL:self.url];
490 491
        [media parse];
    }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
492

493 494
    NSMutableDictionary *mediaDictionary = [[NSMutableDictionary alloc] init];
    [mediaDictionary setObject:[defaults objectForKey:kVLCSettingNetworkCaching] forKey:kVLCSettingNetworkCaching];
495 496 497
    [mediaDictionary setObject:[[defaults objectForKey:kVLCSettingStretchAudio] boolValue] ? kVLCSettingStretchAudioOnValue : kVLCSettingStretchAudioOffValue forKey:kVLCSettingStretchAudio];
    [mediaDictionary setObject:[defaults objectForKey:kVLCSettingTextEncoding] forKey:kVLCSettingTextEncoding];
    [mediaDictionary setObject:[defaults objectForKey:kVLCSettingSkipLoopFilter] forKey:kVLCSettingSkipLoopFilter];
498

499 500 501 502
    [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"];

503
    if ([tzNames containsObject:tzName] || [[tzName stringByDeletingLastPathComponent] isEqualToString:@"US"]) {
504 505 506 507 508 509 510 511 512 513 514 515
        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:
516 517
                    case 1684566644:
                    case 2126701:
518
                    {
519
                        if (![self _blobCheck]) {
520
                            [mediaDictionary setObject:[NSNull null] forKey:@"no-audio"];
521 522
                            APLog(@"audio playback disabled because an unsupported codec was found");
                        }
523 524 525 526 527 528 529 530 531 532
                        break;
                    }

                    default:
                        break;
                }
            }
        }
    }

533 534 535 536 537 538 539 540 541 542
    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];
    }
543
    [_listPlayer setRepeatMode:VLCDoNotRepeat];
544

545
    self.positionSlider.value = 0.;
546
    [self.timeDisplay setTitle:@"" forState:UIControlStateNormal];
547
    self.timeDisplay.accessibilityLabel = @"";
548
    [self.repeatButton setImage:[UIImage imageNamed:@"repeat"] forState:UIControlStateNormal];
549
    [self.repeatButtonLandscape setImage:[UIImage imageNamed:@"repeat"] forState:UIControlStateNormal];
550

551
    if (![self _isMediaSuitableForDevice]) {
552
        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];
553 554 555
        [alert show];
    } else
        [self _playNewMedia];
556

557 558
    if (![self hasExternalDisplay])
        self.brightnessSlider.value = [UIScreen mainScreen].brightness * 2.;
559 560 561 562
}

- (BOOL)_isMediaSuitableForDevice
{
563
    if (!self.fileFromMediaLibrary)
564 565
        return YES;

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

568
    NSInteger speedCategory = [[UIDevice currentDevice] speedCategory];
569

570
    if (speedCategory == 1) {
571 572
        // iPhone 3GS, iPhone 4, first gen. iPad, 3rd and 4th generation iPod touch
        return (totalNumberOfPixels < 600000); // between 480p and 720p
573
    } else if (speedCategory == 2) {
574 575
        // iPhone 4S, iPad 2 and 3, iPod 4 and 5
        return (totalNumberOfPixels < 922000); // 720p
576
    } else if (speedCategory == 3) {
577 578
        // iPhone 5, iPad 4
        return (totalNumberOfPixels < 2074000); // 1080p
579 580 581
    } else if (speedCategory == 4) {
        // iPhone 6, 2014 iPads
        return (totalNumberOfPixels < 8850000); // 4K
582 583 584 585 586 587 588 589 590
    }

    return YES;
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (buttonIndex == 1)
        [self _playNewMedia];
591 592
    else {
        [self _stopPlayback];
593
        [self closePlayback:nil];
594
    }
595 596 597 598
}

- (void)_playNewMedia
{
599
    NSNumber *playbackPositionInTime = @(0);
600
    CGFloat lastPosition = .0;
601 602 603 604 605 606 607 608 609 610 611 612 613 614 615
    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;
616 617 618
    if (lastPosition < .95) {
        if (duration != 0)
            playbackPositionInTime = @(lastPosition * (duration / 1000.));
619
    }
620
    if (playbackPositionInTime.intValue > 0 && (duration * lastPosition - duration) < -60000) {
621 622 623
        [_mediaPlayer.media addOptions:@{@"start-time": playbackPositionInTime}];
        APLog(@"set starttime to %i", playbackPositionInTime.intValue);
    }
624

625 626 627
    [_mediaPlayer addObserver:self forKeyPath:@"time" options:0 context:nil];
    [_mediaPlayer addObserver:self forKeyPath:@"remainingTime" options:0 context:nil];

628
    if (self.mediaList)
629
        [_listPlayer playItemAtIndex:self.itemInMediaListToBePlayedFirst];
630
    else
631
        [_listPlayer playMedia:_listPlayer.rootMedia];
632

633 634 635 636 637
    if (matchedFile) {
        if (matchedFile.lastAudioTrack.intValue > 0)
            _mediaPlayer.currentAudioTrackIndex = matchedFile.lastAudioTrack.intValue;
        if (matchedFile.lastSubtitleTrack.intValue > 0)
            _mediaPlayer.currentVideoSubTitleIndex = matchedFile.lastSubtitleTrack.intValue;
638
    }
639

640 641
    self.playbackSpeedSlider.value = [self _playbackSpeed];
    [self _updatePlaybackSpeedIndicator];
642

643 644 645 646 647 648
    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];

649
    _currentAspectRatioMask = 0;
650 651 652
    _mediaPlayer.videoAspectRatio = NULL;

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

656
    [self _resetIdleTimer];
657
    _playerIsSetup = YES;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
658 659
}

660
- (void)viewWillDisappear:(BOOL)animated
Felix Paul Kühne's avatar
Felix Paul Kühne committed
661
{
662
    [self _stopPlayback];
663
    _viewAppeared = NO;
664
    if (_idleTimer) {
665
        [_idleTimer invalidate];
666 667
        _idleTimer = nil;
    }
668
    [self.navigationController setNavigationBarHidden:NO animated:YES];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
669
    if (!SYSTEM_RUNS_IOS7_OR_LATER)
670
        [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackOpaque;
671
    [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
672
    [super viewWillDisappear:animated];
673

Felix Paul Kühne's avatar
Felix Paul Kühne committed
674 675 676 677 678 679
    // 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
680 681
}

682 683 684
- (void)_stopPlayback
{
    if (_mediaPlayer) {
685 686 687 688 689 690 691 692
        @try {
            [_mediaPlayer removeObserver:self forKeyPath:@"time"];
            [_mediaPlayer removeObserver:self forKeyPath:@"remainingTime"];
        }
        @catch (NSException *exception) {
            APLog(@"we weren't an observer yet");
        }

693
        if (_mediaPlayer.media) {
694 695 696
            [_mediaPlayer pause];
            [self _saveCurrentState];
            [_mediaPlayer stop];
697
        }
698 699
        if (_mediaPlayer)
            _mediaPlayer = nil;
700 701
        if (_listPlayer)
            _listPlayer = nil;
702
    }
703 704
    if (_fileFromMediaLibrary)
        _fileFromMediaLibrary = nil;
705 706 707 708
    if (_mediaList)
        _mediaList = nil;
    if (_url)
        _url = nil;
709
    if (_pathToExternalSubtitlesFile) {
710
            NSFileManager *fileManager = [NSFileManager defaultManager];
711 712 713
            if ([fileManager fileExistsAtPath:_pathToExternalSubtitlesFile])
                [fileManager removeItemAtPath:_pathToExternalSubtitlesFile error:nil];
            _pathToExternalSubtitlesFile = nil;
714
    }
715 716 717
    _playerIsSetup = NO;
}

718 719
- (void)_saveCurrentState
{
720
    if (self.fileFromMediaLibrary) {
721
        @try {
722
            MLFile *item = self.fileFromMediaLibrary;
723 724 725 726 727 728 729
            item.lastPosition = @([_mediaPlayer position]);
            item.lastAudioTrack = @(_mediaPlayer.currentAudioTrackIndex);
            item.lastSubtitleTrack = @(_mediaPlayer.currentVideoSubTitleIndex);
        }
        @catch (NSException *exception) {
            APLog(@"failed to save current media state - file removed?");
        }
730 731 732 733 734 735 736 737
    } 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);
        }
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 771 772 773 774 775 776 777 778 779
- (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];
    }
}

780 781 782 783 784 785 786 787 788 789 790 791 792 793
#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];
794 795

    [[NSUserDefaults standardUserDefaults] setBool:_displayRemainingTime forKey:kVLCShowRemainingTime];
796 797 798 799 800 801 802 803 804 805 806
}

- (BOOL)canBecomeFirstResponder
{
    return YES;
}

- (void)remoteControlReceivedWithEvent:(UIEvent *)event
{
    switch (event.subtype) {
        case UIEventSubtypeRemoteControlPlay:
807
            [_listPlayer play];
808
            break;
809

810
        case UIEventSubtypeRemoteControlPause:
811
            [_listPlayer pause];
812 813 814
            break;

        case UIEventSubtypeRemoteControlTogglePlayPause:
815
            [self playPause];
816 817
            break;

818
        case UIEventSubtypeRemoteControlNextTrack:
819
            [self forward:nil];
820
            break;
821 822

        case UIEventSubtypeRemoteControlPreviousTrack:
823
            [self backward:nil];
824
            break;
825

Felix Paul Kühne's avatar
Felix Paul Kühne committed
826 827
        case UIEventSubtypeRemoteControlStop:
            [self closePlayback:nil];
828
            break;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
829

830 831 832 833 834
        default:
            break;
    }
}

835 836
#pragma mark - controls visibility

837 838
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)recognizer
{
839 840
    LOCKCHECK;

841 842 843
    if (!_swipeGesturesEnabled)
        return;

844 845 846 847
    if (recognizer.velocity < 0.)
        [self closePlayback:nil];
}

848 849
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
850
    if (touch.view != self.view)
851 852 853 854 855
        return NO;

    return YES;
}

Ahmad Harb's avatar
Ahmad Harb committed
856 857 858 859 860
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

861
- (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated
862
{
863
    _controlsHidden = hidden;
864
    CGFloat alpha = _controlsHidden? 0.0f: 1.0f;
865

866 867
    if (!_controlsHidden) {
        _controllerPanel.alpha = 0.0f;
868
        _controllerPanel.hidden = !_videoFiltersHidden;
869 870
        _controllerPanelLandscape.alpha = 0.0f;
        _controllerPanelLandscape.hidden = !_videoFiltersHidden;
871