VLCMovieViewController.m 67.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
#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 38
#define TRACK_SELECTOR_TABLEVIEW_CELL @"track selector table view cell"
#define TRACK_SELECTOR_TABLEVIEW_SECTIONHEADER @"track selector table view section header"

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

    BOOL _controlsHidden;
    BOOL _videoFiltersHidden;
    BOOL _playbackSpeedViewHidden;

    UIActionSheet *_subtitleActionSheet;
    UIActionSheet *_audiotrackActionSheet;

    float _currentPlaybackRate;
    NSArray *_aspectRatios;
    NSUInteger _currentAspectRatioMask;

    NSTimer *_idleTimer;

56
    BOOL _shouldResumePlaying;
57
    BOOL _viewAppeared;
58
    BOOL _displayRemainingTime;
59
    BOOL _positionSet;
60
    BOOL _playerIsSetup;
61
    BOOL _isScrubbing;
Ahmad Harb's avatar
Ahmad Harb committed
62 63

    BOOL _swipeGesturesEnabled;
64
    UIPinchGestureRecognizer *_pinchRecognizer;
Ahmad Harb's avatar
Ahmad Harb committed
65 66 67
    UIPanGestureRecognizer *_panRecognizer;
    UISwipeGestureRecognizer *_swipeRecognizerLeft;
    UISwipeGestureRecognizer *_swipeRecognizerRight;
68
    UITapGestureRecognizer *_tapRecognizer;
69
    UITapGestureRecognizer *_tapOnVideoRecognizer;
70 71 72

    UIView *_trackSelectorContainer;
    UITableView *_trackSelectorTableView;
73 74
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
75 76
@property (nonatomic, strong) UIPopoverController *masterPopoverController;
@property (nonatomic, strong) UIWindow *externalWindow;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
77 78
@end

79
@implementation VLCMovieViewController
Felix Paul Kühne's avatar
Felix Paul Kühne committed
80

81 82 83 84 85 86 87
+ (void)initialize
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSDictionary *appDefaults = @{kVLCShowRemainingTime : @(YES)};
    [defaults registerDefaults:appDefaults];
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
88 89
- (void)dealloc
{
90 91
    [[NSNotificationCenter defaultCenter] removeObserver:self];

92
    if (_tapRecognizer)
93
        [self.view removeGestureRecognizer:_tapRecognizer];
Ahmad Harb's avatar
Ahmad Harb committed
94
    if (_swipeRecognizerLeft)
95
        [self.view removeGestureRecognizer:_swipeRecognizerLeft];
Ahmad Harb's avatar
Ahmad Harb committed
96
    if (_swipeRecognizerRight)
97
        [self.view removeGestureRecognizer:_swipeRecognizerRight];
Ahmad Harb's avatar
Ahmad Harb committed
98
    if (_panRecognizer)
99
        [self.view removeGestureRecognizer:_panRecognizer];
100 101 102 103
    if (_pinchRecognizer)
        [self.view removeGestureRecognizer:_pinchRecognizer];
    [self.view removeGestureRecognizer:_tapOnVideoRecognizer];

104 105 106 107
    _tapRecognizer = nil;
    _swipeRecognizerLeft = nil;
    _swipeRecognizerRight = nil;
    _panRecognizer = nil;
108 109 110 111
    _pinchRecognizer = nil;
    _tapOnVideoRecognizer = nil;

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

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

116
- (void)setFileFromMediaLibrary:(id)newFile
Felix Paul Kühne's avatar
Felix Paul Kühne committed
117
{
118
    if (_fileFromMediaLibrary != newFile) {
119
        [self _stopPlayback];
120
        _fileFromMediaLibrary = newFile;
121 122 123
        if (_viewAppeared)
            [self _startPlayback];
    }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
124

125
    if (self.masterPopoverController != nil)
Felix Paul Kühne's avatar
Felix Paul Kühne committed
126 127 128
        [self.masterPopoverController dismissPopoverAnimated:YES];
}

129 130
- (void)setUrl:(NSURL *)url
{
131 132 133 134 135
    [self _stopPlayback];
    _url = url;
    _playerIsSetup = NO;
    if (_viewAppeared)
        [self _startPlayback];
136 137
}

138 139 140 141 142 143 144 145 146
- (void)setMediaList:(VLCMediaList *)mediaList
{
    [self _stopPlayback];
    _mediaList = mediaList;
    _playerIsSetup = NO;
    if (_viewAppeared)
        [self _startPlayback];
}

147
- (void)viewDidLoad
Felix Paul Kühne's avatar
Felix Paul Kühne committed
148
{
149
    [super viewDidLoad];
150 151
    CGRect rect;

152 153
    self.wantsFullScreenLayout = YES;

154 155
    self.videoFilterView.hidden = YES;
    _videoFiltersHidden = YES;
156
    _hueLabel.text = NSLocalizedString(@"VFILTER_HUE", nil);
157 158
    _hueSlider.accessibilityLabel = _hueLabel.text;
    _hueSlider.isAccessibilityElement = YES;
159
    _contrastLabel.text = NSLocalizedString(@"VFILTER_CONTRAST", nil);
160 161
    _contrastSlider.accessibilityLabel = _contrastLabel.text;
    _contrastSlider.isAccessibilityElement = YES;
162
    _brightnessLabel.text = NSLocalizedString(@"VFILTER_BRIGHTNESS", nil);
163 164
    _brightnessSlider.accessibilityLabel = _brightnessLabel.text;
    _brightnessSlider.isAccessibilityElement = YES;
165
    _saturationLabel.text = NSLocalizedString(@"VFILTER_SATURATION", nil);
166 167
    _saturationSlider.accessibilityLabel = _saturationLabel.text;
    _saturationSlider.isAccessibilityElement = YES;
168
    _gammaLabel.text = NSLocalizedString(@"VFILTER_GAMMA", nil);
169 170
    _gammaSlider.accessibilityLabel = _gammaLabel.text;
    _gammaSlider.isAccessibilityElement = YES;
171
    _playbackSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SPEED", nil);
172 173
    _playbackSpeedSlider.accessibilityLabel = _playbackSpeedLabel.text;
    _playbackSpeedSlider.isAccessibilityElement = YES;
174 175 176 177 178 179
    _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;
180

181
    _positionSlider.accessibilityLabel = NSLocalizedString(@"PLAYBACK_POSITION", nil);
182 183 184
    _positionSlider.isAccessibilityElement = YES;
    _timeDisplay.isAccessibilityElement = YES;

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

216
    _scrubHelpLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HELP", nil);
217

218 219
    self.playbackSpeedView.hidden = YES;
    _playbackSpeedViewHidden = YES;
220

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

235 236
    _playingExternallyTitle.text = NSLocalizedString(@"PLAYING_EXTERNALLY_TITLE", nil);
    _playingExternallyDescription.text = NSLocalizedString(@"PLAYING_EXTERNALLY_DESC", nil);
237
    if ([self hasExternalDisplay])
238
        [self showOnExternalDisplay];
239

240 241
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";

242
    _movieView.userInteractionEnabled = NO;
243 244 245
    _tapOnVideoRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleControlsVisible)];
    _tapOnVideoRecognizer.delegate = self;
    [self.view addGestureRecognizer:_tapOnVideoRecognizer];
246

247 248 249
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    _displayRemainingTime = [[defaults objectForKey:kVLCShowRemainingTime] boolValue];
    _swipeGesturesEnabled = [[defaults objectForKey:kVLCSettingPlaybackGestures] boolValue];
250

251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
    _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;
277

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

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

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

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

338
    [[AVAudioSession sharedInstance] setDelegate:self];
339 340 341

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

    _playerIsSetup = NO;
344

345 346
    [self.movieView setAccessibilityLabel:NSLocalizedString(@"VO_VIDEOPLAYER_TITLE", nil)];
    [self.movieView setAccessibilityHint:NSLocalizedString(@"VO_VIDEOPLAYER_DOUBLETAP", nil)];
347 348 349 350 351 352 353 354 355 356

    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];
357
    _trackSelectorTableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
358 359 360

    _trackSelectorContainer = [[VLCFrostedGlasView alloc] initWithFrame:CGRectMake((rect.size.width - 300.) / 2., (rect.size.height - 320.) / 2., 300., 320.)];
    [_trackSelectorContainer addSubview:_trackSelectorTableView];
361
    _trackSelectorContainer.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleHeight;
362 363 364 365 366 367
    _trackSelectorContainer.hidden = YES;

    if ([[UIDevice currentDevice] speedCategory] >= 3) {
        _trackSelectorTableView.opaque = NO;
        _trackSelectorTableView.backgroundColor = [UIColor clearColor];
    } else
368
        _trackSelectorTableView.backgroundColor = [UIColor blackColor];
369 370

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

373 374 375 376 377
- (BOOL)_blobCheck
{
    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *directoryPath = searchPaths[0];

378
    if (![[NSFileManager defaultManager] fileExistsAtPath:[directoryPath stringByAppendingPathComponent:@"blob.bin"]])
379 380
        return NO;

381
    NSData *data = [NSData dataWithContentsOfFile:[directoryPath stringByAppendingPathComponent:@"blob.bin"]];
382
    uint8_t digest[CC_SHA1_DIGEST_LENGTH];
383
    CC_SHA1(data.bytes, (unsigned int)data.length, digest);
384 385 386 387 388 389 390 391 392 393 394 395

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

396 397
- (void)viewWillAppear:(BOOL)animated
{
398
    [super viewWillAppear:animated];
399

400 401 402 403 404 405
    /* reset audio meta data views */
    self.artworkImageView.image = nil;
    self.trackNameLabel.text = nil;
    self.artistNameLabel.text = nil;
    self.albumNameLabel.text = nil;

406 407
    _swipeGesturesEnabled = [[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingPlaybackGestures] boolValue];

408
    [self.navigationController setNavigationBarHidden:YES animated:YES];
409

Felix Paul Kühne's avatar
Felix Paul Kühne committed
410
    if (!SYSTEM_RUNS_IOS7_OR_LATER) {
411 412 413
        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
            [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackTranslucent;
    }
414

415 416 417 418 419 420
    [self _startPlayback];

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

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

437 438 439 440 441
- (void)_startPlayback
{
    if (_playerIsSetup)
        return;

442 443
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

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

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

465
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";
466

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

479 480
    NSMutableDictionary *mediaDictionary = [[NSMutableDictionary alloc] init];
    [mediaDictionary setObject:[defaults objectForKey:kVLCSettingNetworkCaching] forKey:kVLCSettingNetworkCaching];
481 482 483
    [mediaDictionary setObject:[[defaults objectForKey:kVLCSettingStretchAudio] boolValue] ? kVLCSettingStretchAudioOnValue : kVLCSettingStretchAudioOffValue forKey:kVLCSettingStretchAudio];
    [mediaDictionary setObject:[defaults objectForKey:kVLCSettingTextEncoding] forKey:kVLCSettingTextEncoding];
    [mediaDictionary setObject:[defaults objectForKey:kVLCSettingSkipLoopFilter] forKey:kVLCSettingSkipLoopFilter];
484

485 486 487 488
    [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"];

489
    if ([tzNames containsObject:tzName] || [[tzName stringByDeletingLastPathComponent] isEqualToString:@"US"]) {
490 491 492 493 494 495 496 497 498 499 500 501
        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:
502 503
                    case 1684566644:
                    case 2126701:
504
                    {
505
                        if (![self _blobCheck]) {
506
                            [mediaDictionary setObject:[NSNull null] forKey:@"no-audio"];
507 508
                            APLog(@"audio playback disabled because an unsupported codec was found");
                        }
509 510 511 512 513 514 515 516 517 518
                        break;
                    }

                    default:
                        break;
                }
            }
        }
    }

519 520 521 522 523 524 525 526 527 528
    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];
    }
529
    [_listPlayer setRepeatMode:VLCDoNotRepeat];
530

531
    self.positionSlider.value = 0.;
532
    [self.timeDisplay setTitle:@"" forState:UIControlStateNormal];
533
    self.timeDisplay.accessibilityLabel = @"";
534
    [self.repeatButton setImage:[UIImage imageNamed:@"repeat"] forState:UIControlStateNormal];
535
    [self.repeatButtonLandscape setImage:[UIImage imageNamed:@"repeat"] forState:UIControlStateNormal];
536

537
    if (![self _isMediaSuitableForDevice]) {
538
        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];
539 540 541
        [alert show];
    } else
        [self _playNewMedia];
542

543 544
    if (![self hasExternalDisplay])
        self.brightnessSlider.value = [UIScreen mainScreen].brightness * 2.;
545 546 547 548
}

- (BOOL)_isMediaSuitableForDevice
{
549
    if (!self.fileFromMediaLibrary)
550 551
        return YES;

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

554
    NSInteger speedCategory = [[UIDevice currentDevice] speedCategory];
555

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

    return YES;
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (buttonIndex == 1)
        [self _playNewMedia];
577 578
    else {
        [self _stopPlayback];
579
        [self closePlayback:nil];
580
    }
581 582 583 584
}

- (void)_playNewMedia
{
585
    NSNumber *playbackPositionInTime = @(0);
586
    CGFloat lastPosition = .0;
587 588 589 590 591 592 593 594 595 596 597 598 599 600 601
    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;
602 603 604
    if (lastPosition < .95) {
        if (duration != 0)
            playbackPositionInTime = @(lastPosition * (duration / 1000.));
605
    }
606
    if (playbackPositionInTime.intValue > 0 && (duration * lastPosition - duration) < -60000) {
607 608 609
        [_mediaPlayer.media addOptions:@{@"start-time": playbackPositionInTime}];
        APLog(@"set starttime to %i", playbackPositionInTime.intValue);
    }
610

611 612 613
    [_mediaPlayer addObserver:self forKeyPath:@"time" options:0 context:nil];
    [_mediaPlayer addObserver:self forKeyPath:@"remainingTime" options:0 context:nil];

614
    if (self.mediaList)
615
        [_listPlayer playItemAtIndex:self.itemInMediaListToBePlayedFirst];
616
    else
617
        [_listPlayer playMedia:_listPlayer.rootMedia];
618

619 620 621 622 623
    if (matchedFile) {
        if (matchedFile.lastAudioTrack.intValue > 0)
            _mediaPlayer.currentAudioTrackIndex = matchedFile.lastAudioTrack.intValue;
        if (matchedFile.lastSubtitleTrack.intValue > 0)
            _mediaPlayer.currentVideoSubTitleIndex = matchedFile.lastSubtitleTrack.intValue;
624
    }
625

626 627
    self.playbackSpeedSlider.value = [self _playbackSpeed];
    [self _updatePlaybackSpeedIndicator];
628

629 630 631 632 633 634
    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];

635
    _currentAspectRatioMask = 0;
636 637 638
    _mediaPlayer.videoAspectRatio = NULL;

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

642
    [self _resetIdleTimer];
643
    _playerIsSetup = YES;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
644 645
}

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

Felix Paul Kühne's avatar
Felix Paul Kühne committed
660 661 662 663 664 665
    // 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
666 667
}

668 669 670
- (void)_stopPlayback
{
    if (_mediaPlayer) {
671 672 673 674 675 676 677 678
        @try {
            [_mediaPlayer removeObserver:self forKeyPath:@"time"];
            [_mediaPlayer removeObserver:self forKeyPath:@"remainingTime"];
        }
        @catch (NSException *exception) {
            APLog(@"we weren't an observer yet");
        }

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

704 705
- (void)_saveCurrentState
{
706
    if (self.fileFromMediaLibrary) {
707
        @try {
708
            MLFile *item = self.fileFromMediaLibrary;
709 710 711 712 713 714 715
            item.lastPosition = @([_mediaPlayer position]);
            item.lastAudioTrack = @(_mediaPlayer.currentAudioTrackIndex);
            item.lastSubtitleTrack = @(_mediaPlayer.currentVideoSubTitleIndex);
        }
        @catch (NSException *exception) {
            APLog(@"failed to save current media state - file removed?");
        }
716 717 718 719 720 721 722 723
    } 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);
        }
724 725 726
    }
}

727 728 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
- (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];
    }
}

766 767 768 769 770 771 772 773 774 775 776 777 778 779
#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];
780 781

    [[NSUserDefaults standardUserDefaults] setBool:_displayRemainingTime forKey:kVLCShowRemainingTime];
782 783 784 785 786 787 788 789 790 791 792
}

- (BOOL)canBecomeFirstResponder
{
    return YES;
}

- (void)remoteControlReceivedWithEvent:(UIEvent *)event
{
    switch (event.subtype) {
        case UIEventSubtypeRemoteControlPlay:
793
            [_listPlayer play];
794
            break;
795

796
        case UIEventSubtypeRemoteControlPause:
797
            [_listPlayer pause];
798 799 800
            break;

        case UIEventSubtypeRemoteControlTogglePlayPause:
801
            [self playPause];
802 803
            break;

804
        case UIEventSubtypeRemoteControlNextTrack:
805
            [self forward:nil];
806
            break;
807 808

        case UIEventSubtypeRemoteControlPreviousTrack:
809
            [self backward:nil];
810
            break;
811

Felix Paul Kühne's avatar
Felix Paul Kühne committed
812 813
        case UIEventSubtypeRemoteControlStop:
            [self closePlayback:nil];
814
            break;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
815

816 817 818 819 820
        default:
            break;
    }
}

821 822
#pragma mark - controls visibility

823 824
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)recognizer
{
825 826 827
    if (!_swipeGesturesEnabled)
        return;

828 829 830 831
    if (recognizer.velocity < 0.)
        [self closePlayback:nil];
}

832 833
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
834
    if (touch.view != self.view)
835 836 837 838 839
        return NO;

    return YES;
}

Ahmad Harb's avatar
Ahmad Harb committed
840 841 842 843 844
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

845
- (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated
846
{
847
    _controlsHidden = hidden;
848
    CGFloat alpha = _controlsHidden? 0.0f: 1.0f;
849

850 851
    if (!_controlsHidden) {
        _controllerPanel.alpha = 0.0f;
852
        _controllerPanel.hidden = !_videoFiltersHidden;
853 854
        _controllerPanelLandscape.alpha = 0.0f;
        _controllerPanelLandscape.hidden = !_videoFiltersHidden;
855 856
        _toolbar.alpha = 0.0f;
        _toolbar.hidden = NO;
857 858
        _videoFilterView.alpha = 0.0f;
        _videoFilterView.hidden = _videoFiltersHidden;
859 860
        _playbackSpeedView.alpha = 0.0f;
        _playbackSpeedView.hidden = _playbackSpeedViewHidden;
861 862
        _trackSelectorContainer.alpha = 0.0f;
        _trackSelectorContainer.hidden = YES;
863
    }
864

865 866
    void (^animationBlock)() = ^() {
        _controllerPanel.alpha = alpha;
867
        _controllerPanelLandscape.alpha = alpha;
868
        _toolbar.alpha = alpha;
869
        _videoFilterView.alpha = alpha;
870
        _playbackSpeedView.alpha = alpha;