VLCMovieViewController.m 74.2 KB
Newer Older
1 2 3 4
/*****************************************************************************
 * VLCMovieViewController.m
 * VLC for iOS
 *****************************************************************************
5
 * Copyright (c) 2013-2015 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>
Felix Paul Kühne's avatar
Felix Paul Kühne committed
22
#import "UIDevice+VLC.h"
23
#import "VLCBugreporter.h"
24
#import "VLCThumbnailsCache.h"
25 26
#import "VLCTrackSelectorTableViewCell.h"
#import "VLCTrackSelectorHeaderView.h"
27
#import "VLCEqualizerView.h"
28 29 30

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

32
#define INPUT_RATE_DEFAULT  1000.
33 34
#define FORWARD_SWIPE_DURATION 30
#define BACKWARD_SWIPE_DURATION 10
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"

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

43 44 45 46 47 48 49
typedef NS_ENUM(NSInteger, VLCPanType) {
  VLCPanTypeNone,
  VLCPanTypeBrightness,
  VLCPanTypeSeek,
  VLCPanTypeVolume,
};

50
@interface VLCMovieViewController () <UIGestureRecognizerDelegate, AVAudioSessionDelegate, VLCMediaDelegate, UITableViewDataSource, UITableViewDelegate, VLCEqualizerViewDelegate>
51
{
52
    VLCMediaListPlayer *_listPlayer;
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
    VLCMediaPlayer *_mediaPlayer;

    BOOL _controlsHidden;
    BOOL _videoFiltersHidden;
    BOOL _playbackSpeedViewHidden;

    UIActionSheet *_subtitleActionSheet;
    UIActionSheet *_audiotrackActionSheet;

    float _currentPlaybackRate;
    NSArray *_aspectRatios;
    NSUInteger _currentAspectRatioMask;

    NSTimer *_idleTimer;

68
    BOOL _shouldResumePlaying;
69
    BOOL _viewAppeared;
70
    BOOL _displayRemainingTime;
71
    BOOL _positionSet;
72
    BOOL _playerIsSetup;
73
    BOOL _isScrubbing;
74
    BOOL _interfaceIsLocked;
75
    BOOL _switchingTracksNotChapters;
Ahmad Harb's avatar
Ahmad Harb committed
76 77

    BOOL _swipeGesturesEnabled;
78
    UIPinchGestureRecognizer *_pinchRecognizer;
79
    VLCPanType _currentPanType;
Ahmad Harb's avatar
Ahmad Harb committed
80 81 82
    UIPanGestureRecognizer *_panRecognizer;
    UISwipeGestureRecognizer *_swipeRecognizerLeft;
    UISwipeGestureRecognizer *_swipeRecognizerRight;
83
    UITapGestureRecognizer *_tapRecognizer;
84
    UITapGestureRecognizer *_tapOnVideoRecognizer;
85 86 87

    UIView *_trackSelectorContainer;
    UITableView *_trackSelectorTableView;
88 89

    VLCEqualizerView *_equalizerView;
90 91
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
92 93
@property (nonatomic, strong) UIPopoverController *masterPopoverController;
@property (nonatomic, strong) UIWindow *externalWindow;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
94 95
@end

96
@implementation VLCMovieViewController
Felix Paul Kühne's avatar
Felix Paul Kühne committed
97

98 99 100 101 102 103 104
+ (void)initialize
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSDictionary *appDefaults = @{kVLCShowRemainingTime : @(YES)};
    [defaults registerDefaults:appDefaults];
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
105 106
- (void)dealloc
{
107 108
    [[NSNotificationCenter defaultCenter] removeObserver:self];

109
    if (_tapRecognizer)
110
        [self.view removeGestureRecognizer:_tapRecognizer];
Ahmad Harb's avatar
Ahmad Harb committed
111
    if (_swipeRecognizerLeft)
112
        [self.view removeGestureRecognizer:_swipeRecognizerLeft];
Ahmad Harb's avatar
Ahmad Harb committed
113
    if (_swipeRecognizerRight)
114
        [self.view removeGestureRecognizer:_swipeRecognizerRight];
Ahmad Harb's avatar
Ahmad Harb committed
115
    if (_panRecognizer)
116
        [self.view removeGestureRecognizer:_panRecognizer];
117 118 119 120
    if (_pinchRecognizer)
        [self.view removeGestureRecognizer:_pinchRecognizer];
    [self.view removeGestureRecognizer:_tapOnVideoRecognizer];

121 122 123 124
    _tapRecognizer = nil;
    _swipeRecognizerLeft = nil;
    _swipeRecognizerRight = nil;
    _panRecognizer = nil;
125 126 127 128
    _pinchRecognizer = nil;
    _tapOnVideoRecognizer = nil;

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

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

133
- (void)setFileFromMediaLibrary:(id)newFile
Felix Paul Kühne's avatar
Felix Paul Kühne committed
134
{
135
    if (_fileFromMediaLibrary != newFile) {
136
        [self _stopPlayback];
137
        _fileFromMediaLibrary = newFile;
138 139 140
        if (_viewAppeared)
            [self _startPlayback];
    }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
141

142
    if (self.masterPopoverController != nil)
Felix Paul Kühne's avatar
Felix Paul Kühne committed
143 144 145
        [self.masterPopoverController dismissPopoverAnimated:YES];
}

146 147
- (void)setUrl:(NSURL *)url
{
148 149 150 151 152
    [self _stopPlayback];
    _url = url;
    _playerIsSetup = NO;
    if (_viewAppeared)
        [self _startPlayback];
153 154
}

155 156 157 158 159 160 161 162 163
- (void)setMediaList:(VLCMediaList *)mediaList
{
    [self _stopPlayback];
    _mediaList = mediaList;
    _playerIsSetup = NO;
    if (_viewAppeared)
        [self _startPlayback];
}

164
- (void)viewDidLoad
Felix Paul Kühne's avatar
Felix Paul Kühne committed
165
{
166
    [super viewDidLoad];
167 168
    CGRect rect;

169 170
    self.wantsFullScreenLayout = YES;

171 172
    self.videoFilterView.hidden = YES;
    _videoFiltersHidden = YES;
173
    _hueLabel.text = NSLocalizedString(@"VFILTER_HUE", nil);
174 175
    _hueSlider.accessibilityLabel = _hueLabel.text;
    _hueSlider.isAccessibilityElement = YES;
176
    _contrastLabel.text = NSLocalizedString(@"VFILTER_CONTRAST", nil);
177 178
    _contrastSlider.accessibilityLabel = _contrastLabel.text;
    _contrastSlider.isAccessibilityElement = YES;
179
    _brightnessLabel.text = NSLocalizedString(@"VFILTER_BRIGHTNESS", nil);
180 181
    _brightnessSlider.accessibilityLabel = _brightnessLabel.text;
    _brightnessSlider.isAccessibilityElement = YES;
182
    _saturationLabel.text = NSLocalizedString(@"VFILTER_SATURATION", nil);
183 184
    _saturationSlider.accessibilityLabel = _saturationLabel.text;
    _saturationSlider.isAccessibilityElement = YES;
185
    _gammaLabel.text = NSLocalizedString(@"VFILTER_GAMMA", nil);
186 187
    _gammaSlider.accessibilityLabel = _gammaLabel.text;
    _gammaSlider.isAccessibilityElement = YES;
188
    _playbackSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SPEED", nil);
189 190
    _playbackSpeedSlider.accessibilityLabel = _playbackSpeedLabel.text;
    _playbackSpeedSlider.isAccessibilityElement = YES;
191 192 193 194 195 196
    _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;
197

198
    _positionSlider.accessibilityLabel = NSLocalizedString(@"PLAYBACK_POSITION", nil);
199 200 201
    _positionSlider.isAccessibilityElement = YES;
    _timeDisplay.isAccessibilityElement = YES;

202 203 204 205
    _trackSwitcherButton.accessibilityLabel = NSLocalizedString(@"OPEN_TRACK_PANEL", nil);
    _trackSwitcherButton.isAccessibilityElement = YES;
    _trackSwitcherButtonLandscape.accessibilityLabel = NSLocalizedString(@"OPEN_TRACK_PANEL", nil);
    _trackSwitcherButtonLandscape.isAccessibilityElement = YES;
206 207 208 209
    _chapterButton.accessibilityLabel = NSLocalizedString(@"JUMP_TO_TITLE_OR_CHAPTER", nil);
    _chapterButton.isAccessibilityElement = YES;
    _chapterButtonLandscape.accessibilityLabel = NSLocalizedString(@"JUMP_TO_TITLE_OR_CHAPTER", nil);
    _chapterButtonLandscape.isAccessibilityElement = YES;
210 211
    _playbackSpeedButton.accessibilityLabel = _playbackSpeedLabel.text;
    _playbackSpeedButton.isAccessibilityElement = YES;
212 213
    _playbackSpeedButtonLandscape.accessibilityLabel = _playbackSpeedLabel.text;
    _playbackSpeedButtonLandscape.isAccessibilityElement = YES;
214
    _videoFilterButton.accessibilityLabel = NSLocalizedString(@"VIDEO_FILTER", nil);
215
    _videoFilterButton.isAccessibilityElement = YES;
216
    _videoFilterButtonLandscape.accessibilityLabel = NSLocalizedString(@"VIDEO_FILTER", nil);
217
    _videoFilterButtonLandscape.isAccessibilityElement = YES;
218
    _resetVideoFilterButton.accessibilityLabel = NSLocalizedString(@"VIDEO_FILTER_RESET_BUTTON", nil);
219
    _resetVideoFilterButton.isAccessibilityElement = YES;
220
    _aspectRatioButton.accessibilityLabel = NSLocalizedString(@"VIDEO_ASPECT_RATIO_BUTTON", nil);
221
    _aspectRatioButton.isAccessibilityElement = YES;
222
    _playPauseButton.accessibilityLabel = NSLocalizedString(@"PLAY_PAUSE_BUTTON", nil);
223
    _playPauseButton.isAccessibilityElement = YES;
224
    _playPauseButtonLandscape.accessibilityLabel = NSLocalizedString(@"PLAY_PAUSE_BUTTON", nil);
225
    _playPauseButtonLandscape.isAccessibilityElement = YES;
226
    _bwdButton.accessibilityLabel = NSLocalizedString(@"BWD_BUTTON", nil);
227
    _bwdButton.isAccessibilityElement = YES;
228
    _bwdButtonLandscape.accessibilityLabel = NSLocalizedString(@"BWD_BUTTON", nil);
229
    _bwdButtonLandscape.isAccessibilityElement = YES;
230
    _fwdButton.accessibilityLabel = NSLocalizedString(@"FWD_BUTTON", nil);
231
    _fwdButton.isAccessibilityElement = YES;
232
    _fwdButtonLandscape.accessibilityLabel = NSLocalizedString(@"FWD_BUTTON", nil);
233
    _fwdButtonLandscape.isAccessibilityElement = YES;
234
    _repeatButton.accessibilityLabel = NSLocalizedString(@"BUTTON_REPEAT", nil);
Felix Paul Kühne's avatar
Felix Paul Kühne committed
235
    _repeatButton.isAccessibilityElement = YES;
236

237
    _scrubHelpLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HELP", nil);
238

239 240
    self.playbackSpeedView.hidden = YES;
    _playbackSpeedViewHidden = YES;
241

242 243 244 245 246
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(handleExternalScreenDidConnect:)
                   name:UIScreenDidConnectNotification object:nil];
    [center addObserver:self selector:@selector(handleExternalScreenDidDisconnect:)
                   name:UIScreenDidDisconnectNotification object:nil];
247 248 249 250 251 252
    [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];
253 254
    [center addObserver:self selector:@selector(audioSessionRouteChange:)
                   name:AVAudioSessionRouteChangeNotification object:nil];
255

256 257
    _playingExternallyTitle.text = NSLocalizedString(@"PLAYING_EXTERNALLY_TITLE", nil);
    _playingExternallyDescription.text = NSLocalizedString(@"PLAYING_EXTERNALLY_DESC", nil);
258
    if ([self hasExternalDisplay])
259
        [self showOnExternalDisplay];
260

261 262
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";

263
    _movieView.userInteractionEnabled = NO;
264 265 266
    _tapOnVideoRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleControlsVisible)];
    _tapOnVideoRecognizer.delegate = self;
    [self.view addGestureRecognizer:_tapOnVideoRecognizer];
267

268 269 270
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    _displayRemainingTime = [[defaults objectForKey:kVLCShowRemainingTime] boolValue];
    _swipeGesturesEnabled = [[defaults objectForKey:kVLCSettingPlaybackGestures] boolValue];
271

272 273 274 275 276 277
    _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];
278 279

    _currentPanType = VLCPanTypeNone;
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
    _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;
300

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

303
    [self.aspectRatioButton setImage:[UIImage imageNamed:@"ratioIcon"] forState:UIControlStateNormal];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
304
    if (SYSTEM_RUNS_IOS7_OR_LATER) {
305
        self.backButton.tintColor = [UIColor colorWithRed:(190.0f/255.0f) green:(190.0f/255.0f) blue:(190.0f/255.0f) alpha:1.];
306
        self.toolbar.tintColor = [UIColor whiteColor];
307
        self.toolbar.barStyle = UIBarStyleBlack;
308

309
        rect = self.resetVideoFilterButton.frame;
310 311
        rect.origin.y = rect.origin.y + 5.;
        self.resetVideoFilterButton.frame = rect;
312 313 314 315
        rect = self.toolbar.frame;
        rect.size.height = rect.size.height + rect.origin.y;
        rect.origin.y = 0;
        self.toolbar.frame = rect;
316 317 318 319 320 321 322 323 324 325
        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;
326
    } else {
327
        rect = self.positionSlider.frame;
328 329
        rect.origin.y = rect.origin.y + 3.;
        self.positionSlider.frame = rect;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
330 331
        [self.aspectRatioButton setBackgroundImage:[UIImage imageNamed:@"ratioButton"] forState:UIControlStateNormal];
        [self.aspectRatioButton setBackgroundImage:[UIImage imageNamed:@"ratioButtonHighlight"] forState:UIControlStateHighlighted];
332
        [self.toolbar setBackgroundImage:[UIImage imageNamed:@"seekbarBg"] forBarMetrics:UIBarMetricsDefault];
333 334 335
        [self.backButton setBackgroundImage:[UIImage imageNamed:@"playbackDoneButton"] forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
        [self.backButton setBackgroundImage:[UIImage imageNamed:@"playbackDoneButtonHighlight"] forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
    }
336

337
    /* FIXME: there is a saner iOS 6+ API for this! */
338
    /* this looks a bit weird, but we need to support iOS 5 and should show the same appearance */
339 340 341 342 343 344 345
    void (^initVolumeSlider)(MPVolumeView *) = ^(MPVolumeView *volumeView){
        UISlider *volumeSlider = nil;
        for (id aView in volumeView.subviews){
            if ([[[aView class] description] isEqualToString:@"MPVolumeSlider"]){
                volumeSlider = (UISlider *)aView;
                break;
            }
346
        }
347 348 349 350 351 352 353 354 355 356 357 358 359
        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);
360

361
    [[AVAudioSession sharedInstance] setDelegate:self];
362 363 364

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

    _playerIsSetup = NO;
367

368 369
    [self.movieView setAccessibilityLabel:NSLocalizedString(@"VO_VIDEOPLAYER_TITLE", nil)];
    [self.movieView setAccessibilityHint:NSLocalizedString(@"VO_VIDEOPLAYER_DOUBLETAP", nil)];
370 371

    rect = self.view.frame;
372 373 374 375 376 377 378 379 380
    CGFloat width;
    CGFloat height;
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
        width = 300.;
        height = 320.;
    } else {
        width = 450.;
        height = 470.;
    }
381

382
    _trackSelectorTableView = [[UITableView alloc] initWithFrame:CGRectMake(0., 0., width, height) style:UITableViewStylePlain];
383 384
    _trackSelectorTableView.delegate = self;
    _trackSelectorTableView.dataSource = self;
385
    _trackSelectorTableView.separatorColor = [UIColor clearColor];
386 387 388
    _trackSelectorTableView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
    [_trackSelectorTableView registerClass:[VLCTrackSelectorTableViewCell class] forCellReuseIdentifier:TRACK_SELECTOR_TABLEVIEW_CELL];
    [_trackSelectorTableView registerClass:[VLCTrackSelectorHeaderView class] forHeaderFooterViewReuseIdentifier:TRACK_SELECTOR_TABLEVIEW_SECTIONHEADER];
389
    _trackSelectorTableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
390

391
    _trackSelectorContainer = [[VLCFrostedGlasView alloc] initWithFrame:CGRectMake((rect.size.width - width) / 2., (rect.size.height - height) / 2., width, height)];
392
    [_trackSelectorContainer addSubview:_trackSelectorTableView];
393
    _trackSelectorContainer.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleHeight;
394 395 396 397 398 399
    _trackSelectorContainer.hidden = YES;

    if ([[UIDevice currentDevice] speedCategory] >= 3) {
        _trackSelectorTableView.opaque = NO;
        _trackSelectorTableView.backgroundColor = [UIColor clearColor];
    } else
400
        _trackSelectorTableView.backgroundColor = [UIColor blackColor];
401 402

    [self.view addSubview:_trackSelectorContainer];
403 404 405 406 407 408

    _equalizerView = [[VLCEqualizerView alloc] initWithFrame:CGRectMake((rect.size.width - 450.) / 2., self.controllerPanel.frame.origin.y - 120., 450., 120.)];
    _equalizerView.delegate = self;
    _equalizerView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin;
    _equalizerView.hidden = YES;
    [self.view addSubview:_equalizerView];
409
}
Felix Paul Kühne's avatar
Felix Paul Kühne committed
410

411 412 413 414 415
- (BOOL)_blobCheck
{
    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *directoryPath = searchPaths[0];

416
    if (![[NSFileManager defaultManager] fileExistsAtPath:[directoryPath stringByAppendingPathComponent:@"blob.bin"]])
417 418
        return NO;

419
    NSData *data = [NSData dataWithContentsOfFile:[directoryPath stringByAppendingPathComponent:@"blob.bin"]];
420
    uint8_t digest[CC_SHA1_DIGEST_LENGTH];
421
    CC_SHA1(data.bytes, (unsigned int)data.length, digest);
422 423 424 425 426 427 428 429 430 431 432 433

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

434 435
- (void)viewWillAppear:(BOOL)animated
{
436
    [super viewWillAppear:animated];
437

438 439 440 441 442 443
    /* reset audio meta data views */
    self.artworkImageView.image = nil;
    self.trackNameLabel.text = nil;
    self.artistNameLabel.text = nil;
    self.albumNameLabel.text = nil;

444 445
    _swipeGesturesEnabled = [[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingPlaybackGestures] boolValue];

446
    [self.navigationController setNavigationBarHidden:YES animated:YES];
447

Felix Paul Kühne's avatar
Felix Paul Kühne committed
448
    if (!SYSTEM_RUNS_IOS7_OR_LATER) {
449 450 451
        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
            [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackTranslucent;
    }
452

453 454 455 456 457 458
    [self _startPlayback];

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

459 460 461 462 463
- (void)viewWillLayoutSubviews
{
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
        CGSize viewSize = self.view.frame.size;
        if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) {
464 465 466
            [_controllerPanel removeFromSuperview];
            _controllerPanelLandscape.frame = (CGRect){CGPointMake(0, viewSize.height - _controllerPanelLandscape.frame.size.height), CGSizeMake(viewSize.width, _controllerPanelLandscape.frame.size.height)};
            [self.view addSubview:_controllerPanelLandscape];
467
        } else {
468 469 470
            [_controllerPanelLandscape removeFromSuperview];
            _controllerPanel.frame = (CGRect){CGPointMake(0, viewSize.height - _controllerPanel.frame.size.height), CGSizeMake(viewSize.width, _controllerPanel.frame.size.height)};
            [self.view addSubview:_controllerPanel];
471 472 473 474
        }
    }
}

475 476 477 478 479
- (void)_startPlayback
{
    if (_playerIsSetup)
        return;

480 481
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

482
    if (!self.fileFromMediaLibrary && !self.url && !self.mediaList) {
483
        [self _stopPlayback];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
484
        return;
485
    }
486 487 488
    if (self.pathToExternalSubtitlesFile)
        _listPlayer = [[VLCMediaListPlayer alloc] initWithOptions:@[[NSString stringWithFormat:@"--%@=%@", kVLCSettingSubtitlesFilePath, self.pathToExternalSubtitlesFile]]];
    else
489
        _listPlayer = [[VLCMediaListPlayer alloc] initWithOptions:@[@"-vvvv"]];
490

491
    _mediaPlayer = _listPlayer.mediaPlayer;
492 493
    [_mediaPlayer setDelegate:self];
    [_mediaPlayer setDrawable:self.movieView];
494 495
    if ([[defaults objectForKey:kVLCSettingPlaybackSpeedDefaultValue] floatValue] != 0)
        [_mediaPlayer setRate: [[defaults objectForKey:kVLCSettingPlaybackSpeedDefaultValue] floatValue]];
496 497 498 499
    if ([[defaults objectForKey:kVLCSettingDeinterlace] intValue] != 0)
        [_mediaPlayer setDeinterlaceFilter:@"blend"];
    else
        [_mediaPlayer setDeinterlaceFilter:nil];
500 501 502
    if (self.pathToExternalSubtitlesFile)
        [_mediaPlayer openVideoSubTitlesFromFile:self.pathToExternalSubtitlesFile];

503
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";
504

505
    VLCMedia *media;
506 507
    if (self.fileFromMediaLibrary) {
        MLFile *item = self.fileFromMediaLibrary;
508
        media = [VLCMedia mediaWithURL:[NSURL URLWithString:item.url]];
509 510 511 512
    } else if (self.mediaList) {
        media = [self.mediaList mediaAtIndex:self.itemInMediaListToBePlayedFirst];
        [media parse];
    } else {
513
        media = [VLCMedia mediaWithURL:self.url];
514 515
        [media parse];
    }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
516

517 518
    NSMutableDictionary *mediaDictionary = [[NSMutableDictionary alloc] init];
    [mediaDictionary setObject:[defaults objectForKey:kVLCSettingNetworkCaching] forKey:kVLCSettingNetworkCaching];
519 520 521
    [mediaDictionary setObject:[[defaults objectForKey:kVLCSettingStretchAudio] boolValue] ? kVLCSettingStretchAudioOnValue : kVLCSettingStretchAudioOffValue forKey:kVLCSettingStretchAudio];
    [mediaDictionary setObject:[defaults objectForKey:kVLCSettingTextEncoding] forKey:kVLCSettingTextEncoding];
    [mediaDictionary setObject:[defaults objectForKey:kVLCSettingSkipLoopFilter] forKey:kVLCSettingSkipLoopFilter];
522

523 524 525 526
    [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"];

527
    if ([tzNames containsObject:tzName] || [[tzName stringByDeletingLastPathComponent] isEqualToString:@"US"]) {
528 529 530 531 532 533 534 535 536 537 538 539
        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:
540 541
                    case 1684566644:
                    case 2126701:
542
                    {
543
                        if (![self _blobCheck]) {
544
                            [mediaDictionary setObject:[NSNull null] forKey:@"no-audio"];
545 546
                            APLog(@"audio playback disabled because an unsupported codec was found");
                        }
547 548 549 550 551 552 553 554 555 556
                        break;
                    }

                    default:
                        break;
                }
            }
        }
    }

557 558 559 560 561 562 563 564 565 566
    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];
    }
567
    [_listPlayer setRepeatMode:VLCDoNotRepeat];
568

569
    self.positionSlider.value = 0.;
570
    [self.timeDisplay setTitle:@"" forState:UIControlStateNormal];
571
    self.timeDisplay.accessibilityLabel = @"";
572
    [self.repeatButton setImage:[UIImage imageNamed:@"repeat"] forState:UIControlStateNormal];
573
    [self.repeatButtonLandscape setImage:[UIImage imageNamed:@"repeat"] forState:UIControlStateNormal];
574

575
    if (![self _isMediaSuitableForDevice]) {
576
        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];
577 578 579
        [alert show];
    } else
        [self _playNewMedia];
580

581 582
    if (![self hasExternalDisplay])
        self.brightnessSlider.value = [UIScreen mainScreen].brightness * 2.;
583 584 585 586
}

- (BOOL)_isMediaSuitableForDevice
{
587
    if (!self.fileFromMediaLibrary)
588 589
        return YES;

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

592
    NSInteger speedCategory = [[UIDevice currentDevice] speedCategory];
593

594
    if (speedCategory == 1) {
595 596
        // iPhone 3GS, iPhone 4, first gen. iPad, 3rd and 4th generation iPod touch
        return (totalNumberOfPixels < 600000); // between 480p and 720p
597
    } else if (speedCategory == 2) {
598 599
        // iPhone 4S, iPad 2 and 3, iPod 4 and 5
        return (totalNumberOfPixels < 922000); // 720p
600
    } else if (speedCategory == 3) {
601 602
        // iPhone 5, iPad 4
        return (totalNumberOfPixels < 2074000); // 1080p
603 604 605
    } else if (speedCategory == 4) {
        // iPhone 6, 2014 iPads
        return (totalNumberOfPixels < 8850000); // 4K
606 607 608 609 610 611 612 613 614
    }

    return YES;
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (buttonIndex == 1)
        [self _playNewMedia];
615 616
    else {
        [self _stopPlayback];
617
        [self closePlayback:nil];
618
    }
619 620 621 622
}

- (void)_playNewMedia
{
623
    NSNumber *playbackPositionInTime = @(0);
624
    CGFloat lastPosition = .0;
625 626 627 628 629 630 631 632 633 634 635 636 637 638 639
    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;
640 641 642
    if (lastPosition < .95) {
        if (duration != 0)
            playbackPositionInTime = @(lastPosition * (duration / 1000.));
643
    }
644
    if (playbackPositionInTime.intValue > 0 && (duration * lastPosition - duration) < -60000) {
645 646 647
        [_mediaPlayer.media addOptions:@{@"start-time": playbackPositionInTime}];
        APLog(@"set starttime to %i", playbackPositionInTime.intValue);
    }
648

649 650 651
    [_mediaPlayer addObserver:self forKeyPath:@"time" options:0 context:nil];
    [_mediaPlayer addObserver:self forKeyPath:@"remainingTime" options:0 context:nil];

652
    if (self.mediaList)
653
        [_listPlayer playItemAtIndex:self.itemInMediaListToBePlayedFirst];
654
    else
655
        [_listPlayer playMedia:_listPlayer.rootMedia];
656

657 658 659 660 661
    if (matchedFile) {
        if (matchedFile.lastAudioTrack.intValue > 0)
            _mediaPlayer.currentAudioTrackIndex = matchedFile.lastAudioTrack.intValue;
        if (matchedFile.lastSubtitleTrack.intValue > 0)
            _mediaPlayer.currentVideoSubTitleIndex = matchedFile.lastSubtitleTrack.intValue;
662
    }
663

664 665
    self.playbackSpeedSlider.value = [self _playbackSpeed];
    [self _updatePlaybackSpeedIndicator];
666

667 668 669 670 671 672
    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];

673
    _currentAspectRatioMask = 0;
674 675 676
    _mediaPlayer.videoAspectRatio = NULL;

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

680
    [self _resetIdleTimer];
681
    _playerIsSetup = YES;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
682 683
}

684
- (void)viewWillDisappear:(BOOL)animated
Felix Paul Kühne's avatar
Felix Paul Kühne committed
685
{
686
    [self _stopPlayback];
687
    _viewAppeared = NO;
688
    if (_idleTimer) {
689
        [_idleTimer invalidate];
690 691
        _idleTimer = nil;
    }
692
    [self.navigationController setNavigationBarHidden:NO animated:YES];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
693
    if (!SYSTEM_RUNS_IOS7_OR_LATER)
694
        [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackOpaque;
695
    [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
696
    [super viewWillDisappear:animated];
697

Felix Paul Kühne's avatar
Felix Paul Kühne committed
698 699 700 701 702 703
    // 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
704 705
}

706 707 708
- (void)_stopPlayback
{
    if (_mediaPlayer) {
709 710 711 712 713 714 715 716
        @try {
            [_mediaPlayer removeObserver:self forKeyPath:@"time"];
            [_mediaPlayer removeObserver:self forKeyPath:@"remainingTime"];
        }
        @catch (NSException *exception) {
            APLog(@"we weren't an observer yet");
        }

717
        if (_mediaPlayer.media) {
718 719 720
            [_mediaPlayer pause];
            [self _saveCurrentState];
            [_mediaPlayer stop];
721
        }
722 723
        if (_mediaPlayer)
            _mediaPlayer = nil;
724 725
        if (_listPlayer)
            _listPlayer = nil;
726
    }
727 728
    if (_fileFromMediaLibrary)
        _fileFromMediaLibrary = nil;
729 730 731 732
    if (_mediaList)
        _mediaList = nil;
    if (_url)
        _url = nil;
733
    if (_pathToExternalSubtitlesFile) {
734
            NSFileManager *fileManager = [NSFileManager defaultManager];
735 736 737
            if ([fileManager fileExistsAtPath:_pathToExternalSubtitlesFile])
                [fileManager removeItemAtPath:_pathToExternalSubtitlesFile error:nil];
            _pathToExternalSubtitlesFile = nil;
738
    }
739 740 741
    _playerIsSetup = NO;
}

742 743
- (void)_saveCurrentState
{
744
    if (self.fileFromMediaLibrary) {
745
        @try {
746
            MLFile *item = self.fileFromMediaLibrary;
747 748 749 750 751 752 753
            item.lastPosition = @([_mediaPlayer position]);
            item.lastAudioTrack = @(_mediaPlayer.currentAudioTrackIndex);
            item.lastSubtitleTrack = @(_mediaPlayer.currentVideoSubTitleIndex);
        }
        @catch (NSException *exception) {
            APLog(@"failed to save current media state - file removed?");
        }
754 755 756 757 758 759 760 761
    } 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);
        }
762 763 764
    }
}

765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803
- (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];
    }
}

804 805 806 807 808 809 810 811 812 813 814 815 816 817
#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];
818 819

    [[NSUserDefaults standardUserDefaults] setBool:_displayRemainingTime forKey:kVLCShowRemainingTime];
820 821 822 823 824 825 826 827 828 829 830
}

- (BOOL)canBecomeFirstResponder
{
    return YES;
}

- (void)remoteControlReceivedWithEvent:(UIEvent *)event
{
    switch (event.subtype) {
        case UIEventSubtypeRemoteControlPlay:
831
            [_listPlayer play];
832
            break;
833

834
        case UIEventSubtypeRemoteControlPause:
835
            [_listPlayer pause];
836 837 838
            break;

        case UIEventSubtypeRemoteControlTogglePlayPause:
839
            [self playPause];
840 841
            break;

842
        case UIEventSubtypeRemoteControlNextTrack:
843
            [self forward:nil];
844
            break;
845 846

        case UIEventSubtypeRemoteControlPreviousTrack:
847
            [self backward:nil];
848
            break;
849

Felix Paul Kühne's avatar
Felix Paul Kühne committed
850 851
        case UIEventSubtypeRemoteControlStop:
            [self closePlayback:nil];
852
            break;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
853

854 855 856 857 858
        default:
            break;
    }
}

859 860
#pragma mark - controls visibility

861 862
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)recognizer
{
863 864
    LOCKCHECK;

865 866 867
    if (!_swipeGesturesEnabled)
        return;

868 869 870 871
    if (recognizer.velocity < 0.)
        [self closePlayback:nil];
}

872 873
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
874
    if (touch.view != self.view)