VLCMovieViewController.m 64.8 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 27

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

29
#define INPUT_RATE_DEFAULT  1000.
30 31
#define FORWARD_SWIPE_DURATION 30
#define BACKWARD_SWIPE_DURATION 10
32

33
@interface VLCMovieViewController () <UIGestureRecognizerDelegate, AVAudioSessionDelegate, VLCMediaDelegate>
34
{
35
    VLCMediaListPlayer *_listPlayer;
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
    VLCMediaPlayer *_mediaPlayer;

    BOOL _controlsHidden;
    BOOL _videoFiltersHidden;
    BOOL _playbackSpeedViewHidden;

    UIActionSheet *_subtitleActionSheet;
    UIActionSheet *_audiotrackActionSheet;

    float _currentPlaybackRate;
    NSArray *_aspectRatios;
    NSUInteger _currentAspectRatioMask;

    NSTimer *_idleTimer;

51
    BOOL _shouldResumePlaying;
52
    BOOL _viewAppeared;
53
    BOOL _displayRemainingTime;
54
    BOOL _positionSet;
55
    BOOL _playerIsSetup;
56
    BOOL _isScrubbing;
Ahmad Harb's avatar
Ahmad Harb committed
57 58 59

    BOOL _swipeGesturesEnabled;
    NSString * panType;
60
    UIPinchGestureRecognizer *_pinchRecognizer;
Ahmad Harb's avatar
Ahmad Harb committed
61 62 63
    UIPanGestureRecognizer *_panRecognizer;
    UISwipeGestureRecognizer *_swipeRecognizerLeft;
    UISwipeGestureRecognizer *_swipeRecognizerRight;
64
    UITapGestureRecognizer *_tapRecognizer;
65
    UITapGestureRecognizer *_tapOnVideoRecognizer;
66 67
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
68 69
@property (nonatomic, strong) UIPopoverController *masterPopoverController;
@property (nonatomic, strong) UIWindow *externalWindow;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
70 71
@end

72
@implementation VLCMovieViewController
Felix Paul Kühne's avatar
Felix Paul Kühne committed
73

74 75 76 77 78 79 80
+ (void)initialize
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSDictionary *appDefaults = @{kVLCShowRemainingTime : @(YES)};
    [defaults registerDefaults:appDefaults];
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
81 82
- (void)dealloc
{
83 84
    [[NSNotificationCenter defaultCenter] removeObserver:self];

85
    if (_tapRecognizer)
86
        [self.view removeGestureRecognizer:_tapRecognizer];
Ahmad Harb's avatar
Ahmad Harb committed
87
    if (_swipeRecognizerLeft)
88
        [self.view removeGestureRecognizer:_swipeRecognizerLeft];
Ahmad Harb's avatar
Ahmad Harb committed
89
    if (_swipeRecognizerRight)
90
        [self.view removeGestureRecognizer:_swipeRecognizerRight];
Ahmad Harb's avatar
Ahmad Harb committed
91
    if (_panRecognizer)
92
        [self.view removeGestureRecognizer:_panRecognizer];
93 94 95 96
    if (_pinchRecognizer)
        [self.view removeGestureRecognizer:_pinchRecognizer];
    [self.view removeGestureRecognizer:_tapOnVideoRecognizer];

97 98 99 100
    _tapRecognizer = nil;
    _swipeRecognizerLeft = nil;
    _swipeRecognizerRight = nil;
    _panRecognizer = nil;
101 102 103 104
    _pinchRecognizer = nil;
    _tapOnVideoRecognizer = nil;

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

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

109
- (void)setFileFromMediaLibrary:(id)newFile
Felix Paul Kühne's avatar
Felix Paul Kühne committed
110
{
111
    if (_fileFromMediaLibrary != newFile) {
112
        [self _stopPlayback];
113
        _fileFromMediaLibrary = newFile;
114 115 116
        if (_viewAppeared)
            [self _startPlayback];
    }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
117

118
    if (self.masterPopoverController != nil)
Felix Paul Kühne's avatar
Felix Paul Kühne committed
119 120 121
        [self.masterPopoverController dismissPopoverAnimated:YES];
}

122 123
- (void)setUrl:(NSURL *)url
{
124 125 126 127 128
    [self _stopPlayback];
    _url = url;
    _playerIsSetup = NO;
    if (_viewAppeared)
        [self _startPlayback];
129 130
}

131 132 133 134 135 136 137 138 139
- (void)setMediaList:(VLCMediaList *)mediaList
{
    [self _stopPlayback];
    _mediaList = mediaList;
    _playerIsSetup = NO;
    if (_viewAppeared)
        [self _startPlayback];
}

140
- (void)viewDidLoad
Felix Paul Kühne's avatar
Felix Paul Kühne committed
141
{
142
    [super viewDidLoad];
143 144
    self.wantsFullScreenLayout = YES;

145 146
    self.videoFilterView.hidden = YES;
    _videoFiltersHidden = YES;
147
    _hueLabel.text = NSLocalizedString(@"VFILTER_HUE", nil);
148 149
    _hueSlider.accessibilityLabel = _hueLabel.text;
    _hueSlider.isAccessibilityElement = YES;
150
    _contrastLabel.text = NSLocalizedString(@"VFILTER_CONTRAST", nil);
151 152
    _contrastSlider.accessibilityLabel = _contrastLabel.text;
    _contrastSlider.isAccessibilityElement = YES;
153
    _brightnessLabel.text = NSLocalizedString(@"VFILTER_BRIGHTNESS", nil);
154 155
    _brightnessSlider.accessibilityLabel = _brightnessLabel.text;
    _brightnessSlider.isAccessibilityElement = YES;
156
    _saturationLabel.text = NSLocalizedString(@"VFILTER_SATURATION", nil);
157 158
    _saturationSlider.accessibilityLabel = _saturationLabel.text;
    _saturationSlider.isAccessibilityElement = YES;
159
    _gammaLabel.text = NSLocalizedString(@"VFILTER_GAMMA", nil);
160 161
    _gammaSlider.accessibilityLabel = _gammaLabel.text;
    _gammaSlider.isAccessibilityElement = YES;
162
    _playbackSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SPEED", nil);
163 164
    _playbackSpeedSlider.accessibilityLabel = _playbackSpeedLabel.text;
    _playbackSpeedSlider.isAccessibilityElement = YES;
165 166 167 168 169 170
    _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;
171

172
    _positionSlider.accessibilityLabel = NSLocalizedString(@"PLAYBACK_POSITION", nil);
173 174 175
    _positionSlider.isAccessibilityElement = YES;
    _timeDisplay.isAccessibilityElement = YES;

176
    _audioSwitcherButton.accessibilityLabel = NSLocalizedString(@"CHOOSE_AUDIO_TRACK", nil);
177
    _audioSwitcherButton.isAccessibilityElement = YES;
178
    _audioSwitcherButtonLandscape.accessibilityLabel = NSLocalizedString(@"CHOOSE_AUDIO_TRACK", nil);
179
    _audioSwitcherButtonLandscape.isAccessibilityElement = YES;
180
    _subtitleSwitcherButton.accessibilityLabel = NSLocalizedString(@"CHOOSE_SUBTITLE_TRACK", nil);
181
    _subtitleSwitcherButton.isAccessibilityElement = YES;
182
    _subtitleSwitcherButtonLandscape.accessibilityLabel = NSLocalizedString(@"CHOOSE_SUBTITLE_TRACK", nil);
183
    _subtitleSwitcherButtonLandscape.isAccessibilityElement = YES;
184 185
    _playbackSpeedButton.accessibilityLabel = _playbackSpeedLabel.text;
    _playbackSpeedButton.isAccessibilityElement = YES;
186 187
    _playbackSpeedButtonLandscape.accessibilityLabel = _playbackSpeedLabel.text;
    _playbackSpeedButtonLandscape.isAccessibilityElement = YES;
188
    _videoFilterButton.accessibilityLabel = NSLocalizedString(@"VIDEO_FILTER", nil);
189
    _videoFilterButton.isAccessibilityElement = YES;
190
    _videoFilterButtonLandscape.accessibilityLabel = NSLocalizedString(@"VIDEO_FILTER", nil);
191
    _videoFilterButtonLandscape.isAccessibilityElement = YES;
192
    _resetVideoFilterButton.accessibilityLabel = NSLocalizedString(@"VIDEO_FILTER_RESET_BUTTON", nil);
193
    _resetVideoFilterButton.isAccessibilityElement = YES;
194
    _aspectRatioButton.accessibilityLabel = NSLocalizedString(@"VIDEO_ASPECT_RATIO_BUTTON", nil);
195
    _aspectRatioButton.isAccessibilityElement = YES;
196
    _playPauseButton.accessibilityLabel = NSLocalizedString(@"PLAY_PAUSE_BUTTON", nil);
197
    _playPauseButton.isAccessibilityElement = YES;
198
    _playPauseButtonLandscape.accessibilityLabel = NSLocalizedString(@"PLAY_PAUSE_BUTTON", nil);
199
    _playPauseButtonLandscape.isAccessibilityElement = YES;
200
    _bwdButton.accessibilityLabel = NSLocalizedString(@"BWD_BUTTON", nil);
201
    _bwdButton.isAccessibilityElement = YES;
202
    _bwdButtonLandscape.accessibilityLabel = NSLocalizedString(@"BWD_BUTTON", nil);
203
    _bwdButtonLandscape.isAccessibilityElement = YES;
204
    _fwdButton.accessibilityLabel = NSLocalizedString(@"FWD_BUTTON", nil);
205
    _fwdButton.isAccessibilityElement = YES;
206
    _fwdButtonLandscape.accessibilityLabel = NSLocalizedString(@"FWD_BUTTON", nil);
207
    _fwdButtonLandscape.isAccessibilityElement = YES;
208
    _repeatButton.accessibilityLabel = NSLocalizedString(@"BUTTON_REPEAT", nil);
Felix Paul Kühne's avatar
Felix Paul Kühne committed
209
    _repeatButton.isAccessibilityElement = YES;
210

211
    _scrubHelpLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HELP", nil);
212

213 214
    self.playbackSpeedView.hidden = YES;
    _playbackSpeedViewHidden = YES;
215

216 217 218 219 220
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(handleExternalScreenDidConnect:)
                   name:UIScreenDidConnectNotification object:nil];
    [center addObserver:self selector:@selector(handleExternalScreenDidDisconnect:)
                   name:UIScreenDidDisconnectNotification object:nil];
221 222 223 224 225 226
    [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];
227 228
    [center addObserver:self selector:@selector(audioSessionRouteChange:)
                   name:AVAudioSessionRouteChangeNotification object:nil];
229

230 231
    _playingExternallyTitle.text = NSLocalizedString(@"PLAYING_EXTERNALLY_TITLE", nil);
    _playingExternallyDescription.text = NSLocalizedString(@"PLAYING_EXTERNALLY_DESC", nil);
232
    if ([self hasExternalDisplay])
233
        [self showOnExternalDisplay];
234

235 236
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";

237
    _movieView.userInteractionEnabled = NO;
238 239 240
    _tapOnVideoRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleControlsVisible)];
    _tapOnVideoRecognizer.delegate = self;
    [self.view addGestureRecognizer:_tapOnVideoRecognizer];
241

242 243 244
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    _displayRemainingTime = [[defaults objectForKey:kVLCShowRemainingTime] boolValue];
    _swipeGesturesEnabled = [[defaults objectForKey:kVLCSettingPlaybackGestures] boolValue];
245

246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
    _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;
272

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

275
    [self.aspectRatioButton setImage:[UIImage imageNamed:@"ratioIcon"] forState:UIControlStateNormal];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
276
    if (SYSTEM_RUNS_IOS7_OR_LATER) {
277
        self.backButton.tintColor = [UIColor colorWithRed:(190.0f/255.0f) green:(190.0f/255.0f) blue:(190.0f/255.0f) alpha:1.];
278
        self.toolbar.tintColor = [UIColor whiteColor];
279
        self.toolbar.barStyle = UIBarStyleBlack;
280

281
        CGRect rect = self.resetVideoFilterButton.frame;
282 283
        rect.origin.y = rect.origin.y + 5.;
        self.resetVideoFilterButton.frame = rect;
284 285 286 287
        rect = self.toolbar.frame;
        rect.size.height = rect.size.height + rect.origin.y;
        rect.origin.y = 0;
        self.toolbar.frame = rect;
288 289 290 291 292 293 294 295 296 297
        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;
298
    } else {
299 300 301
        CGRect rect = self.positionSlider.frame;
        rect.origin.y = rect.origin.y + 3.;
        self.positionSlider.frame = rect;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
302 303
        [self.aspectRatioButton setBackgroundImage:[UIImage imageNamed:@"ratioButton"] forState:UIControlStateNormal];
        [self.aspectRatioButton setBackgroundImage:[UIImage imageNamed:@"ratioButtonHighlight"] forState:UIControlStateHighlighted];
304
        [self.toolbar setBackgroundImage:[UIImage imageNamed:@"seekbarBg"] forBarMetrics:UIBarMetricsDefault];
305 306 307
        [self.backButton setBackgroundImage:[UIImage imageNamed:@"playbackDoneButton"] forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
        [self.backButton setBackgroundImage:[UIImage imageNamed:@"playbackDoneButtonHighlight"] forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
    }
308

309
    /* FIXME: there is a saner iOS 6+ API for this! */
310
    /* this looks a bit weird, but we need to support iOS 5 and should show the same appearance */
311 312 313 314 315 316 317
    void (^initVolumeSlider)(MPVolumeView *) = ^(MPVolumeView *volumeView){
        UISlider *volumeSlider = nil;
        for (id aView in volumeView.subviews){
            if ([[[aView class] description] isEqualToString:@"MPVolumeSlider"]){
                volumeSlider = (UISlider *)aView;
                break;
            }
318
        }
319 320 321 322 323 324 325 326 327 328 329 330 331
        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);
332

333
    [[AVAudioSession sharedInstance] setDelegate:self];
334 335 336

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

    _playerIsSetup = NO;
339

340 341
    [self.movieView setAccessibilityLabel:NSLocalizedString(@"VO_VIDEOPLAYER_TITLE", nil)];
    [self.movieView setAccessibilityHint:NSLocalizedString(@"VO_VIDEOPLAYER_DOUBLETAP", nil)];
342
}
Felix Paul Kühne's avatar
Felix Paul Kühne committed
343

344 345 346 347 348
- (BOOL)_blobCheck
{
    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *directoryPath = searchPaths[0];

349
    if (![[NSFileManager defaultManager] fileExistsAtPath:[directoryPath stringByAppendingPathComponent:@"blob.bin"]])
350 351
        return NO;

352
    NSData *data = [NSData dataWithContentsOfFile:[directoryPath stringByAppendingPathComponent:@"blob.bin"]];
353
    uint8_t digest[CC_SHA1_DIGEST_LENGTH];
354
    CC_SHA1(data.bytes, (unsigned int)data.length, digest);
355 356 357 358 359 360 361 362 363 364 365 366

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

367 368
- (void)viewWillAppear:(BOOL)animated
{
369
    [super viewWillAppear:animated];
370

371 372 373 374 375 376
    /* reset audio meta data views */
    self.artworkImageView.image = nil;
    self.trackNameLabel.text = nil;
    self.artistNameLabel.text = nil;
    self.albumNameLabel.text = nil;

377 378
    _swipeGesturesEnabled = [[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingPlaybackGestures] boolValue];

379
    [self.navigationController setNavigationBarHidden:YES animated:YES];
380

Felix Paul Kühne's avatar
Felix Paul Kühne committed
381
    if (!SYSTEM_RUNS_IOS7_OR_LATER) {
382 383 384
        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
            [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackTranslucent;
    }
385

386 387 388 389 390 391
    [self _startPlayback];

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

392 393 394 395 396
- (void)viewWillLayoutSubviews
{
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
        CGSize viewSize = self.view.frame.size;
        if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) {
397 398 399
            [_controllerPanel removeFromSuperview];
            _controllerPanelLandscape.frame = (CGRect){CGPointMake(0, viewSize.height - _controllerPanelLandscape.frame.size.height), CGSizeMake(viewSize.width, _controllerPanelLandscape.frame.size.height)};
            [self.view addSubview:_controllerPanelLandscape];
400
        } else {
401 402 403
            [_controllerPanelLandscape removeFromSuperview];
            _controllerPanel.frame = (CGRect){CGPointMake(0, viewSize.height - _controllerPanel.frame.size.height), CGSizeMake(viewSize.width, _controllerPanel.frame.size.height)};
            [self.view addSubview:_controllerPanel];
404 405 406 407
        }
    }
}

408 409 410 411 412
- (void)_startPlayback
{
    if (_playerIsSetup)
        return;

413 414
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

415
    if (!self.fileFromMediaLibrary && !self.url && !self.mediaList) {
416
        [self _stopPlayback];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
417
        return;
418
    }
419 420 421 422
    if (self.pathToExternalSubtitlesFile)
        _listPlayer = [[VLCMediaListPlayer alloc] initWithOptions:@[[NSString stringWithFormat:@"--%@=%@", kVLCSettingSubtitlesFilePath, self.pathToExternalSubtitlesFile]]];
    else
        _listPlayer = [[VLCMediaListPlayer alloc] init];
423

424
    _mediaPlayer = _listPlayer.mediaPlayer;
425 426
    [_mediaPlayer setDelegate:self];
    [_mediaPlayer setDrawable:self.movieView];
427 428
    if ([[defaults objectForKey:kVLCSettingPlaybackSpeedDefaultValue] floatValue] != 0)
        [_mediaPlayer setRate: [[defaults objectForKey:kVLCSettingPlaybackSpeedDefaultValue] floatValue]];
429 430 431 432
    if ([[defaults objectForKey:kVLCSettingDeinterlace] intValue] != 0)
        [_mediaPlayer setDeinterlaceFilter:@"blend"];
    else
        [_mediaPlayer setDeinterlaceFilter:nil];
433 434 435
    if (self.pathToExternalSubtitlesFile)
        [_mediaPlayer openVideoSubTitlesFromFile:self.pathToExternalSubtitlesFile];

436
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";
437

438
    VLCMedia *media;
439 440
    if (self.fileFromMediaLibrary) {
        MLFile *item = self.fileFromMediaLibrary;
441
        media = [VLCMedia mediaWithURL:[NSURL URLWithString:item.url]];
442 443 444 445
    } else if (self.mediaList) {
        media = [self.mediaList mediaAtIndex:self.itemInMediaListToBePlayedFirst];
        [media parse];
    } else {
446
        media = [VLCMedia mediaWithURL:self.url];
447 448
        [media parse];
    }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
449

450 451
    NSMutableDictionary *mediaDictionary = [[NSMutableDictionary alloc] init];
    [mediaDictionary setObject:[defaults objectForKey:kVLCSettingNetworkCaching] forKey:kVLCSettingNetworkCaching];
452 453 454
    [mediaDictionary setObject:[[defaults objectForKey:kVLCSettingStretchAudio] boolValue] ? kVLCSettingStretchAudioOnValue : kVLCSettingStretchAudioOffValue forKey:kVLCSettingStretchAudio];
    [mediaDictionary setObject:[defaults objectForKey:kVLCSettingTextEncoding] forKey:kVLCSettingTextEncoding];
    [mediaDictionary setObject:[defaults objectForKey:kVLCSettingSkipLoopFilter] forKey:kVLCSettingSkipLoopFilter];
455

456 457 458 459
    [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"];

460
    if ([tzNames containsObject:tzName] || [[tzName stringByDeletingLastPathComponent] isEqualToString:@"US"]) {
461 462 463 464 465 466 467 468 469 470 471 472
        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:
473 474
                    case 1684566644:
                    case 2126701:
475
                    {
476
                        if (![self _blobCheck]) {
477
                            [mediaDictionary setObject:[NSNull null] forKey:@"no-audio"];
478 479
                            APLog(@"audio playback disabled because an unsupported codec was found");
                        }
480 481 482 483 484 485 486 487 488 489
                        break;
                    }

                    default:
                        break;
                }
            }
        }
    }

490 491 492 493 494 495 496 497 498 499
    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];
    }
500
    [_listPlayer setRepeatMode:VLCDoNotRepeat];
501

502
    self.positionSlider.value = 0.;
503
    [self.timeDisplay setTitle:@"" forState:UIControlStateNormal];
504
    self.timeDisplay.accessibilityLabel = @"";
505
    [self.repeatButton setImage:[UIImage imageNamed:@"repeat"] forState:UIControlStateNormal];
506
    [self.repeatButtonLandscape setImage:[UIImage imageNamed:@"repeat"] forState:UIControlStateNormal];
507

508
    if (![self _isMediaSuitableForDevice]) {
509
        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];
510 511 512
        [alert show];
    } else
        [self _playNewMedia];
513

514 515
    if (![self hasExternalDisplay])
        self.brightnessSlider.value = [UIScreen mainScreen].brightness * 2.;
516 517 518 519
}

- (BOOL)_isMediaSuitableForDevice
{
520
    if (!self.fileFromMediaLibrary)
521 522
        return YES;

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

525
    NSInteger speedCategory = [[UIDevice currentDevice] speedCategory];
526

527
    if (speedCategory == 1) {
528 529
        // iPhone 3GS, iPhone 4, first gen. iPad, 3rd and 4th generation iPod touch
        return (totalNumberOfPixels < 600000); // between 480p and 720p
530
    } else if (speedCategory == 2) {
531 532
        // iPhone 4S, iPad 2 and 3, iPod 4 and 5
        return (totalNumberOfPixels < 922000); // 720p
533
    } else if (speedCategory == 3) {
534 535
        // iPhone 5, iPad 4
        return (totalNumberOfPixels < 2074000); // 1080p
536 537 538
    } else if (speedCategory == 4) {
        // iPhone 6, 2014 iPads
        return (totalNumberOfPixels < 8850000); // 4K
539 540 541 542 543 544 545 546 547
    }

    return YES;
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (buttonIndex == 1)
        [self _playNewMedia];
548 549
    else {
        [self _stopPlayback];
550
        [self closePlayback:nil];
551
    }
552 553 554 555
}

- (void)_playNewMedia
{
556
    NSNumber *playbackPositionInTime = @(0);
557
    CGFloat lastPosition = .0;
558 559 560 561 562 563 564 565 566 567 568 569 570 571 572
    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;
573 574 575
    if (lastPosition < .95) {
        if (duration != 0)
            playbackPositionInTime = @(lastPosition * (duration / 1000.));
576
    }
577
    if (playbackPositionInTime.intValue > 0 && (duration * lastPosition - duration) < -60000) {
578 579 580
        [_mediaPlayer.media addOptions:@{@"start-time": playbackPositionInTime}];
        APLog(@"set starttime to %i", playbackPositionInTime.intValue);
    }
581

582 583 584
    [_mediaPlayer addObserver:self forKeyPath:@"time" options:0 context:nil];
    [_mediaPlayer addObserver:self forKeyPath:@"remainingTime" options:0 context:nil];

585
    if (self.mediaList)
586
        [_listPlayer playItemAtIndex:self.itemInMediaListToBePlayedFirst];
587
    else
588
        [_listPlayer playMedia:_listPlayer.rootMedia];
589

590 591 592 593 594
    if (matchedFile) {
        if (matchedFile.lastAudioTrack.intValue > 0)
            _mediaPlayer.currentAudioTrackIndex = matchedFile.lastAudioTrack.intValue;
        if (matchedFile.lastSubtitleTrack.intValue > 0)
            _mediaPlayer.currentVideoSubTitleIndex = matchedFile.lastSubtitleTrack.intValue;
595
    }
596

597 598
    self.playbackSpeedSlider.value = [self _playbackSpeed];
    [self _updatePlaybackSpeedIndicator];
599

600 601 602 603 604 605
    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];

606
    _currentAspectRatioMask = 0;
607 608 609
    _mediaPlayer.videoAspectRatio = NULL;

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

613
    [self _resetIdleTimer];
614
    _playerIsSetup = YES;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
615 616
}

617
- (void)viewWillDisappear:(BOOL)animated
Felix Paul Kühne's avatar
Felix Paul Kühne committed
618
{
619
    [self _stopPlayback];
620
    _viewAppeared = NO;
621
    if (_idleTimer) {
622
        [_idleTimer invalidate];
623 624
        _idleTimer = nil;
    }
625
    [self.navigationController setNavigationBarHidden:NO animated:YES];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
626
    if (!SYSTEM_RUNS_IOS7_OR_LATER)
627
        [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackOpaque;
628
    [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
629
    [super viewWillDisappear:animated];
630

Felix Paul Kühne's avatar
Felix Paul Kühne committed
631 632 633 634 635 636
    // 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
637 638
}

639 640 641
- (void)_stopPlayback
{
    if (_mediaPlayer) {
642 643 644 645 646 647 648 649
        @try {
            [_mediaPlayer removeObserver:self forKeyPath:@"time"];
            [_mediaPlayer removeObserver:self forKeyPath:@"remainingTime"];
        }
        @catch (NSException *exception) {
            APLog(@"we weren't an observer yet");
        }

650
        if (_mediaPlayer.media) {
651 652 653
            [_mediaPlayer pause];
            [self _saveCurrentState];
            [_mediaPlayer stop];
654
        }
655 656
        if (_mediaPlayer)
            _mediaPlayer = nil;
657 658
        if (_listPlayer)
            _listPlayer = nil;
659
    }
660 661
    if (_fileFromMediaLibrary)
        _fileFromMediaLibrary = nil;
662 663 664 665
    if (_mediaList)
        _mediaList = nil;
    if (_url)
        _url = nil;
666
    if (_pathToExternalSubtitlesFile) {
667
            NSFileManager *fileManager = [NSFileManager defaultManager];
668 669 670
            if ([fileManager fileExistsAtPath:_pathToExternalSubtitlesFile])
                [fileManager removeItemAtPath:_pathToExternalSubtitlesFile error:nil];
            _pathToExternalSubtitlesFile = nil;
671
    }
672 673 674
    _playerIsSetup = NO;
}

675 676
- (void)_saveCurrentState
{
677
    if (self.fileFromMediaLibrary) {
678
        @try {
679
            MLFile *item = self.fileFromMediaLibrary;
680 681 682 683 684 685 686
            item.lastPosition = @([_mediaPlayer position]);
            item.lastAudioTrack = @(_mediaPlayer.currentAudioTrackIndex);
            item.lastSubtitleTrack = @(_mediaPlayer.currentVideoSubTitleIndex);
        }
        @catch (NSException *exception) {
            APLog(@"failed to save current media state - file removed?");
        }
687 688 689 690 691 692 693 694
    } 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);
        }
695 696 697
    }
}

698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736
- (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];
    }
}

737 738 739 740 741 742 743 744 745 746 747 748 749 750
#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];
751 752

    [[NSUserDefaults standardUserDefaults] setBool:_displayRemainingTime forKey:kVLCShowRemainingTime];
753 754 755 756 757 758 759 760 761 762 763
}

- (BOOL)canBecomeFirstResponder
{
    return YES;
}

- (void)remoteControlReceivedWithEvent:(UIEvent *)event
{
    switch (event.subtype) {
        case UIEventSubtypeRemoteControlPlay:
764
            [_listPlayer play];
765
            break;
766

767
        case UIEventSubtypeRemoteControlPause:
768
            [_listPlayer pause];
769 770 771
            break;

        case UIEventSubtypeRemoteControlTogglePlayPause:
772
            [self playPause];
773 774
            break;

775
        case UIEventSubtypeRemoteControlNextTrack:
776
            [self forward:nil];
777
            break;
778 779

        case UIEventSubtypeRemoteControlPreviousTrack:
780
            [self backward:nil];
781
            break;
782

Felix Paul Kühne's avatar
Felix Paul Kühne committed
783 784
        case UIEventSubtypeRemoteControlStop:
            [self closePlayback:nil];
785
            break;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
786

787 788 789 790 791
        default:
            break;
    }
}

792 793
#pragma mark - controls visibility

794 795
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)recognizer
{
796 797 798
    if (!_swipeGesturesEnabled)
        return;

799 800 801 802
    if (recognizer.velocity < 0.)
        [self closePlayback:nil];
}

803 804
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
805
    if (touch.view != self.view)
806 807 808 809 810
        return NO;

    return YES;
}

Ahmad Harb's avatar
Ahmad Harb committed
811 812 813 814 815
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

816
- (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated
817
{
818
    _controlsHidden = hidden;
819
    CGFloat alpha = _controlsHidden? 0.0f: 1.0f;
820

821 822
    if (!_controlsHidden) {
        _controllerPanel.alpha = 0.0f;
823
        _controllerPanel.hidden = !_videoFiltersHidden;
824 825
        _controllerPanelLandscape.alpha = 0.0f;
        _controllerPanelLandscape.hidden = !_videoFiltersHidden;
826 827
        _toolbar.alpha = 0.0f;
        _toolbar.hidden = NO;
828 829
        _videoFilterView.alpha = 0.0f;
        _videoFilterView.hidden = _videoFiltersHidden;
830 831
        _playbackSpeedView.alpha = 0.0f;
        _playbackSpeedView.hidden = _playbackSpeedViewHidden;
832
    }
833

834 835
    void (^animationBlock)() = ^() {
        _controllerPanel.alpha = alpha;
836
        _controllerPanelLandscape.alpha = alpha;
837
        _toolbar.alpha = alpha;
838
        _videoFilterView.alpha = alpha;
839
        _playbackSpeedView.alpha = alpha;
840
    };
841

842
    void (^completionBlock)(BOOL finished) = ^(BOOL finished) {
843 844
        _controllerPanel.hidden = _videoFiltersHidden ? _controlsHidden : NO;
        _controllerPanelLandscape.hidden = _videoFiltersHidden ? _controlsHidden : NO;
845
        _toolbar.hidden = _controlsHidden;
846
        _videoFilterView.hidden = _videoFiltersHidden;
847
        _playbackSpeedView.hidden = _playbackSpeedViewHidden;
848
    };
849

850 851
    UIStatusBarAnimation animationType = animated? UIStatusBarAnimationFade: UIStatusBarAnimationNone;
    NSTimeInterval animationDuration = animated? 0.3: 0.0;
852 853

    [[UIApplication sharedApplication] setStatusBarHidden:_viewAppeared ? _controlsHidden : NO withAnimation:animationType];
Gleb Pinigin's avatar