VLCMovieViewController.m 80.7 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
    VLCMediaPlayer *_mediaPlayer;

    BOOL _controlsHidden;
    BOOL _videoFiltersHidden;
    BOOL _playbackSpeedViewHidden;

    UIActionSheet *_subtitleActionSheet;
    UIActionSheet *_audiotrackActionSheet;

    float _currentPlaybackRate;
    NSArray *_aspectRatios;
    NSUInteger _currentAspectRatioMask;

    NSTimer *_idleTimer;
67
    NSTimer *_sleepTimer;
68

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

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

    UIView *_trackSelectorContainer;
    UITableView *_trackSelectorTableView;
89 90

    VLCEqualizerView *_equalizerView;
91 92 93 94

    UIView *_sleepTimerContainer;
    UIDatePicker *_sleepTimeDatePicker;

95
    NSInteger _mediaDuration;
96 97
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
98 99
@property (nonatomic, strong) UIPopoverController *masterPopoverController;
@property (nonatomic, strong) UIWindow *externalWindow;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
100 101
@end

102
@implementation VLCMovieViewController
Felix Paul Kühne's avatar
Felix Paul Kühne committed
103

104 105 106 107 108 109 110
+ (void)initialize
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSDictionary *appDefaults = @{kVLCShowRemainingTime : @(YES)};
    [defaults registerDefaults:appDefaults];
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
111 112
- (void)dealloc
{
113 114
    [[NSNotificationCenter defaultCenter] removeObserver:self];

115
    if (_tapRecognizer)
116
        [self.view removeGestureRecognizer:_tapRecognizer];
Ahmad Harb's avatar
Ahmad Harb committed
117
    if (_swipeRecognizerLeft)
118
        [self.view removeGestureRecognizer:_swipeRecognizerLeft];
Ahmad Harb's avatar
Ahmad Harb committed
119
    if (_swipeRecognizerRight)
120
        [self.view removeGestureRecognizer:_swipeRecognizerRight];
Ahmad Harb's avatar
Ahmad Harb committed
121
    if (_panRecognizer)
122
        [self.view removeGestureRecognizer:_panRecognizer];
123 124 125 126
    if (_pinchRecognizer)
        [self.view removeGestureRecognizer:_pinchRecognizer];
    [self.view removeGestureRecognizer:_tapOnVideoRecognizer];

127 128 129 130
    _tapRecognizer = nil;
    _swipeRecognizerLeft = nil;
    _swipeRecognizerRight = nil;
    _panRecognizer = nil;
131 132 133 134
    _pinchRecognizer = nil;
    _tapOnVideoRecognizer = nil;

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

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

139
- (void)setFileFromMediaLibrary:(id)newFile
Felix Paul Kühne's avatar
Felix Paul Kühne committed
140
{
141
    if (_fileFromMediaLibrary != newFile) {
142
        [self _stopPlayback];
143
        _fileFromMediaLibrary = newFile;
144 145 146
        if (_viewAppeared)
            [self _startPlayback];
    }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
147

148
    if (self.masterPopoverController != nil)
Felix Paul Kühne's avatar
Felix Paul Kühne committed
149 150 151
        [self.masterPopoverController dismissPopoverAnimated:YES];
}

152 153
- (void)setUrl:(NSURL *)url
{
154 155 156 157 158
    [self _stopPlayback];
    _url = url;
    _playerIsSetup = NO;
    if (_viewAppeared)
        [self _startPlayback];
159 160
}

161 162 163 164 165 166 167 168 169
- (void)setMediaList:(VLCMediaList *)mediaList
{
    [self _stopPlayback];
    _mediaList = mediaList;
    _playerIsSetup = NO;
    if (_viewAppeared)
        [self _startPlayback];
}

170
- (void)viewDidLoad
Felix Paul Kühne's avatar
Felix Paul Kühne committed
171
{
172
    [super viewDidLoad];
173 174
    CGRect rect;

175 176
    self.wantsFullScreenLayout = YES;

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

204
    _positionSlider.accessibilityLabel = NSLocalizedString(@"PLAYBACK_POSITION", nil);
205 206 207
    _positionSlider.isAccessibilityElement = YES;
    _timeDisplay.isAccessibilityElement = YES;

208 209 210 211
    _trackSwitcherButton.accessibilityLabel = NSLocalizedString(@"OPEN_TRACK_PANEL", nil);
    _trackSwitcherButton.isAccessibilityElement = YES;
    _trackSwitcherButtonLandscape.accessibilityLabel = NSLocalizedString(@"OPEN_TRACK_PANEL", nil);
    _trackSwitcherButtonLandscape.isAccessibilityElement = YES;
212 213 214 215
    _chapterButton.accessibilityLabel = NSLocalizedString(@"JUMP_TO_TITLE_OR_CHAPTER", nil);
    _chapterButton.isAccessibilityElement = YES;
    _chapterButtonLandscape.accessibilityLabel = NSLocalizedString(@"JUMP_TO_TITLE_OR_CHAPTER", nil);
    _chapterButtonLandscape.isAccessibilityElement = YES;
216 217
    _playbackSpeedButton.accessibilityLabel = _playbackSpeedLabel.text;
    _playbackSpeedButton.isAccessibilityElement = YES;
218 219
    _playbackSpeedButtonLandscape.accessibilityLabel = _playbackSpeedLabel.text;
    _playbackSpeedButtonLandscape.isAccessibilityElement = YES;
220
    _videoFilterButton.accessibilityLabel = NSLocalizedString(@"VIDEO_FILTER", nil);
221
    _videoFilterButton.isAccessibilityElement = YES;
222
    _videoFilterButtonLandscape.accessibilityLabel = NSLocalizedString(@"VIDEO_FILTER", nil);
223
    _videoFilterButtonLandscape.isAccessibilityElement = YES;
224
    _resetVideoFilterButton.accessibilityLabel = NSLocalizedString(@"VIDEO_FILTER_RESET_BUTTON", nil);
225
    _resetVideoFilterButton.isAccessibilityElement = YES;
226
    _aspectRatioButton.accessibilityLabel = NSLocalizedString(@"VIDEO_ASPECT_RATIO_BUTTON", nil);
227
    _aspectRatioButton.isAccessibilityElement = YES;
228
    _playPauseButton.accessibilityLabel = NSLocalizedString(@"PLAY_PAUSE_BUTTON", nil);
229
    _playPauseButton.isAccessibilityElement = YES;
230
    _playPauseButtonLandscape.accessibilityLabel = NSLocalizedString(@"PLAY_PAUSE_BUTTON", nil);
231
    _playPauseButtonLandscape.isAccessibilityElement = YES;
232
    _bwdButton.accessibilityLabel = NSLocalizedString(@"BWD_BUTTON", nil);
233
    _bwdButton.isAccessibilityElement = YES;
234
    _bwdButtonLandscape.accessibilityLabel = NSLocalizedString(@"BWD_BUTTON", nil);
235
    _bwdButtonLandscape.isAccessibilityElement = YES;
236
    _fwdButton.accessibilityLabel = NSLocalizedString(@"FWD_BUTTON", nil);
237
    _fwdButton.isAccessibilityElement = YES;
238
    _fwdButtonLandscape.accessibilityLabel = NSLocalizedString(@"FWD_BUTTON", nil);
239
    _fwdButtonLandscape.isAccessibilityElement = YES;
240
    _repeatButton.accessibilityLabel = NSLocalizedString(@"BUTTON_REPEAT", nil);
Felix Paul Kühne's avatar
Felix Paul Kühne committed
241
    _repeatButton.isAccessibilityElement = YES;
242 243
    _equalizerButton.accessibilityLabel = NSLocalizedString(@"BUTTON_EQUALIZER", nil);
    _equalizerButton.isAccessibilityElement = YES;
244 245 246
    _sleepTimerButton.accessibilityLabel = NSLocalizedString(@"BUTTON_SLEEP_TIMER", nil);
    _sleepTimerButton.isAccessibilityElement = YES;
    [_sleepTimerButton setTitle:NSLocalizedString(@"BUTTON_SLEEP_TIMER", nil) forState:UIControlStateNormal];
247

248
    _scrubHelpLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HELP", nil);
249

250 251
    self.playbackSpeedView.hidden = YES;
    _playbackSpeedViewHidden = YES;
252

253 254 255 256 257
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(handleExternalScreenDidConnect:)
                   name:UIScreenDidConnectNotification object:nil];
    [center addObserver:self selector:@selector(handleExternalScreenDidDisconnect:)
                   name:UIScreenDidDisconnectNotification object:nil];
258 259 260 261 262 263
    [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];
264 265
    [center addObserver:self selector:@selector(audioSessionRouteChange:)
                   name:AVAudioSessionRouteChangeNotification object:nil];
266

267 268
    _playingExternallyTitle.text = NSLocalizedString(@"PLAYING_EXTERNALLY_TITLE", nil);
    _playingExternallyDescription.text = NSLocalizedString(@"PLAYING_EXTERNALLY_DESC", nil);
269
    if ([self hasExternalDisplay])
270
        [self showOnExternalDisplay];
271

272 273
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";

274
    _movieView.userInteractionEnabled = NO;
275 276 277
    _tapOnVideoRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleControlsVisible)];
    _tapOnVideoRecognizer.delegate = self;
    [self.view addGestureRecognizer:_tapOnVideoRecognizer];
278

279 280 281
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    _displayRemainingTime = [[defaults objectForKey:kVLCShowRemainingTime] boolValue];
    _swipeGesturesEnabled = [[defaults objectForKey:kVLCSettingPlaybackGestures] boolValue];
282

283 284 285 286 287 288
    _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];
289 290

    _currentPanType = VLCPanTypeNone;
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
    _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;
311

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

314
    [self.aspectRatioButton setImage:[UIImage imageNamed:@"ratioIcon"] forState:UIControlStateNormal];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
315
    if (SYSTEM_RUNS_IOS7_OR_LATER) {
316
        self.backButton.tintColor = [UIColor colorWithRed:(190.0f/255.0f) green:(190.0f/255.0f) blue:(190.0f/255.0f) alpha:1.];
317
        self.toolbar.tintColor = [UIColor whiteColor];
318
        self.toolbar.barStyle = UIBarStyleBlack;
319

320
        rect = self.resetVideoFilterButton.frame;
321 322
        rect.origin.y = rect.origin.y + 5.;
        self.resetVideoFilterButton.frame = rect;
323 324 325 326
        rect = self.toolbar.frame;
        rect.size.height = rect.size.height + rect.origin.y;
        rect.origin.y = 0;
        self.toolbar.frame = rect;
327 328 329 330 331 332 333 334 335 336
        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;
337
    } else {
338
        rect = self.positionSlider.frame;
339 340
        rect.origin.y = rect.origin.y + 3.;
        self.positionSlider.frame = rect;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
341 342
        [self.aspectRatioButton setBackgroundImage:[UIImage imageNamed:@"ratioButton"] forState:UIControlStateNormal];
        [self.aspectRatioButton setBackgroundImage:[UIImage imageNamed:@"ratioButtonHighlight"] forState:UIControlStateHighlighted];
343
        [self.toolbar setBackgroundImage:[UIImage imageNamed:@"seekbarBg"] forBarMetrics:UIBarMetricsDefault];
344 345 346
        [self.backButton setBackgroundImage:[UIImage imageNamed:@"playbackDoneButton"] forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
        [self.backButton setBackgroundImage:[UIImage imageNamed:@"playbackDoneButtonHighlight"] forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
    }
347

348
    /* FIXME: there is a saner iOS 6+ API for this! */
349
    /* this looks a bit weird, but we need to support iOS 5 and should show the same appearance */
350 351 352 353 354 355 356
    void (^initVolumeSlider)(MPVolumeView *) = ^(MPVolumeView *volumeView){
        UISlider *volumeSlider = nil;
        for (id aView in volumeView.subviews){
            if ([[[aView class] description] isEqualToString:@"MPVolumeSlider"]){
                volumeSlider = (UISlider *)aView;
                break;
            }
357
        }
358 359 360 361 362 363 364 365 366 367 368 369 370
        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);
371

372
    [[AVAudioSession sharedInstance] setDelegate:self];
373 374 375

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

    _playerIsSetup = NO;
378

379 380
    [self.movieView setAccessibilityLabel:NSLocalizedString(@"VO_VIDEOPLAYER_TITLE", nil)];
    [self.movieView setAccessibilityHint:NSLocalizedString(@"VO_VIDEOPLAYER_DOUBLETAP", nil)];
381 382

    rect = self.view.frame;
383 384 385 386 387 388 389 390 391
    CGFloat width;
    CGFloat height;
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
        width = 300.;
        height = 320.;
    } else {
        width = 450.;
        height = 470.;
    }
392

393
    _trackSelectorTableView = [[UITableView alloc] initWithFrame:CGRectMake(0., 0., width, height) style:UITableViewStylePlain];
394 395
    _trackSelectorTableView.delegate = self;
    _trackSelectorTableView.dataSource = self;
396
    _trackSelectorTableView.separatorColor = [UIColor clearColor];
397 398 399
    _trackSelectorTableView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
    [_trackSelectorTableView registerClass:[VLCTrackSelectorTableViewCell class] forCellReuseIdentifier:TRACK_SELECTOR_TABLEVIEW_CELL];
    [_trackSelectorTableView registerClass:[VLCTrackSelectorHeaderView class] forHeaderFooterViewReuseIdentifier:TRACK_SELECTOR_TABLEVIEW_SECTIONHEADER];
400
    _trackSelectorTableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
401

402
    _trackSelectorContainer = [[VLCFrostedGlasView alloc] initWithFrame:CGRectMake((rect.size.width - width) / 2., (rect.size.height - height) / 2., width, height)];
403
    [_trackSelectorContainer addSubview:_trackSelectorTableView];
404
    _trackSelectorContainer.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleHeight;
405 406 407 408 409 410
    _trackSelectorContainer.hidden = YES;

    if ([[UIDevice currentDevice] speedCategory] >= 3) {
        _trackSelectorTableView.opaque = NO;
        _trackSelectorTableView.backgroundColor = [UIColor clearColor];
    } else
411
        _trackSelectorTableView.backgroundColor = [UIColor blackColor];
412 413

    [self.view addSubview:_trackSelectorContainer];
414

415 416 417 418 419
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
        _equalizerView = [[VLCEqualizerView alloc] initWithFrame:CGRectMake((rect.size.width - 450.) / 2., self.controllerPanel.frame.origin.y - 200., 450., 240.)];
    } else {
        _equalizerView = [[VLCEqualizerView alloc] initWithFrame:CGRectMake((rect.size.width - 450.) / 2., self.controllerPanel.frame.origin.y - 240., 450., 240.)];
    }
420 421 422 423
    _equalizerView.delegate = self;
    _equalizerView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin;
    _equalizerView.hidden = YES;
    [self.view addSubview:_equalizerView];
424
}
Felix Paul Kühne's avatar
Felix Paul Kühne committed
425

426 427 428 429 430
- (BOOL)_blobCheck
{
    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *directoryPath = searchPaths[0];

431
    if (![[NSFileManager defaultManager] fileExistsAtPath:[directoryPath stringByAppendingPathComponent:@"blob.bin"]])
432 433
        return NO;

434
    NSData *data = [NSData dataWithContentsOfFile:[directoryPath stringByAppendingPathComponent:@"blob.bin"]];
435
    uint8_t digest[CC_SHA1_DIGEST_LENGTH];
436
    CC_SHA1(data.bytes, (unsigned int)data.length, digest);
437 438 439 440 441 442 443 444 445 446 447 448

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

449 450
- (void)viewWillAppear:(BOOL)animated
{
451
    [super viewWillAppear:animated];
452

453 454 455 456 457 458
    /* reset audio meta data views */
    self.artworkImageView.image = nil;
    self.trackNameLabel.text = nil;
    self.artistNameLabel.text = nil;
    self.albumNameLabel.text = nil;

459 460
    _swipeGesturesEnabled = [[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingPlaybackGestures] boolValue];

461
    [self.navigationController setNavigationBarHidden:YES animated:YES];
462

Felix Paul Kühne's avatar
Felix Paul Kühne committed
463
    if (!SYSTEM_RUNS_IOS7_OR_LATER) {
464 465 466
        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
            [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackTranslucent;
    }
467

468 469 470 471 472 473
    [self _startPlayback];

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

474 475 476 477 478
- (void)viewWillLayoutSubviews
{
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
        CGSize viewSize = self.view.frame.size;
        if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) {
479 480 481
            [_controllerPanel removeFromSuperview];
            _controllerPanelLandscape.frame = (CGRect){CGPointMake(0, viewSize.height - _controllerPanelLandscape.frame.size.height), CGSizeMake(viewSize.width, _controllerPanelLandscape.frame.size.height)};
            [self.view addSubview:_controllerPanelLandscape];
482
        } else {
483 484 485
            [_controllerPanelLandscape removeFromSuperview];
            _controllerPanel.frame = (CGRect){CGPointMake(0, viewSize.height - _controllerPanel.frame.size.height), CGSizeMake(viewSize.width, _controllerPanel.frame.size.height)};
            [self.view addSubview:_controllerPanel];
486 487 488 489
        }
    }
}

490 491 492 493 494
- (void)_startPlayback
{
    if (_playerIsSetup)
        return;

495 496
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

497
    if (!self.fileFromMediaLibrary && !self.url && !self.mediaList) {
498
        [self _stopPlayback];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
499
        return;
500
    }
501 502 503
    if (self.pathToExternalSubtitlesFile)
        _listPlayer = [[VLCMediaListPlayer alloc] initWithOptions:@[[NSString stringWithFormat:@"--%@=%@", kVLCSettingSubtitlesFilePath, self.pathToExternalSubtitlesFile]]];
    else
504
        _listPlayer = [[VLCMediaListPlayer alloc] initWithOptions:@[@"-vvvv"]];
505

506
    _mediaPlayer = _listPlayer.mediaPlayer;
507 508
    [_mediaPlayer setDelegate:self];
    [_mediaPlayer setDrawable:self.movieView];
509 510
    if ([[defaults objectForKey:kVLCSettingPlaybackSpeedDefaultValue] floatValue] != 0)
        [_mediaPlayer setRate: [[defaults objectForKey:kVLCSettingPlaybackSpeedDefaultValue] floatValue]];
511 512 513 514
    if ([[defaults objectForKey:kVLCSettingDeinterlace] intValue] != 0)
        [_mediaPlayer setDeinterlaceFilter:@"blend"];
    else
        [_mediaPlayer setDeinterlaceFilter:nil];
515 516 517
    if (self.pathToExternalSubtitlesFile)
        [_mediaPlayer openVideoSubTitlesFromFile:self.pathToExternalSubtitlesFile];

518
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";
519

520
    VLCMedia *media;
521 522
    if (self.fileFromMediaLibrary) {
        MLFile *item = self.fileFromMediaLibrary;
523
        media = [VLCMedia mediaWithURL:[NSURL URLWithString:item.url]];
524 525 526 527
    } else if (self.mediaList) {
        media = [self.mediaList mediaAtIndex:self.itemInMediaListToBePlayedFirst];
        [media parse];
    } else {
528
        media = [VLCMedia mediaWithURL:self.url];
529 530
        [media parse];
    }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
531

532 533
    NSMutableDictionary *mediaDictionary = [[NSMutableDictionary alloc] init];
    [mediaDictionary setObject:[defaults objectForKey:kVLCSettingNetworkCaching] forKey:kVLCSettingNetworkCaching];
534 535 536
    [mediaDictionary setObject:[[defaults objectForKey:kVLCSettingStretchAudio] boolValue] ? kVLCSettingStretchAudioOnValue : kVLCSettingStretchAudioOffValue forKey:kVLCSettingStretchAudio];
    [mediaDictionary setObject:[defaults objectForKey:kVLCSettingTextEncoding] forKey:kVLCSettingTextEncoding];
    [mediaDictionary setObject:[defaults objectForKey:kVLCSettingSkipLoopFilter] forKey:kVLCSettingSkipLoopFilter];
537

538 539 540 541
    [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"];

542
    if ([tzNames containsObject:tzName] || [[tzName stringByDeletingLastPathComponent] isEqualToString:@"US"]) {
543 544 545 546 547 548 549 550 551 552 553 554
        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:
555 556
                    case 1684566644:
                    case 2126701:
557
                    {
558
                        if (![self _blobCheck]) {
559
                            [mediaDictionary setObject:[NSNull null] forKey:@"no-audio"];
560 561
                            APLog(@"audio playback disabled because an unsupported codec was found");
                        }
562 563 564 565 566 567 568 569 570 571
                        break;
                    }

                    default:
                        break;
                }
            }
        }
    }

572 573 574 575 576 577 578 579 580 581
    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];
    }
582
    [_listPlayer setRepeatMode:VLCDoNotRepeat];
583

584
    self.positionSlider.value = 0.;
585
    [self.timeDisplay setTitle:@"" forState:UIControlStateNormal];
586
    self.timeDisplay.accessibilityLabel = @"";
587
    [self.repeatButton setImage:[UIImage imageNamed:@"repeat"] forState:UIControlStateNormal];
588
    [self.repeatButtonLandscape setImage:[UIImage imageNamed:@"repeat"] forState:UIControlStateNormal];
589

590
    if (![self _isMediaSuitableForDevice]) {
591
        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];
592 593 594
        [alert show];
    } else
        [self _playNewMedia];
595

596 597
    if (![self hasExternalDisplay])
        self.brightnessSlider.value = [UIScreen mainScreen].brightness * 2.;
598 599 600 601
}

- (BOOL)_isMediaSuitableForDevice
{
602
    if (!self.fileFromMediaLibrary)
603 604
        return YES;

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

607
    NSInteger speedCategory = [[UIDevice currentDevice] speedCategory];
608

609
    if (speedCategory == 1) {
610 611
        // iPhone 3GS, iPhone 4, first gen. iPad, 3rd and 4th generation iPod touch
        return (totalNumberOfPixels < 600000); // between 480p and 720p
612
    } else if (speedCategory == 2) {
613 614
        // iPhone 4S, iPad 2 and 3, iPod 4 and 5
        return (totalNumberOfPixels < 922000); // 720p
615
    } else if (speedCategory == 3) {
616 617
        // iPhone 5, iPad 4
        return (totalNumberOfPixels < 2074000); // 1080p
618 619 620
    } else if (speedCategory == 4) {
        // iPhone 6, 2014 iPads
        return (totalNumberOfPixels < 8850000); // 4K
621 622 623 624 625 626 627 628 629
    }

    return YES;
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (buttonIndex == 1)
        [self _playNewMedia];
630 631
    else {
        [self _stopPlayback];
632
        [self closePlayback:nil];
633
    }
634 635 636 637
}

- (void)_playNewMedia
{
638
    NSNumber *playbackPositionInTime = @(0);
639
    CGFloat lastPosition = .0;
640 641 642
    NSInteger duration = 0;
    MLFile *matchedFile;

643 644 645 646 647 648
    // Set last selected equalizer profile
    unsigned int profile = (unsigned int)[[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingEqualizerProfile] integerValue];
    [_mediaPlayer resetEqualizerFromProfile:profile];
    [_mediaPlayer setPreAmplification:[_mediaPlayer preAmplification]];
    [_equalizerView reloadData];

649 650 651
    if (self.fileFromMediaLibrary)
        matchedFile = self.fileFromMediaLibrary;
    else if (self.mediaList) {
652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667
        /* TODO: move this code to MLKit */
        NSString *path = [[[self.mediaList mediaAtIndex:self.itemInMediaListToBePlayedFirst] url] absoluteString];
        NSString *componentString = @"";
        NSArray *pathComponents = [path componentsSeparatedByString:@"/"];
        NSUInteger componentCount = pathComponents.count;

        if ([pathComponents[componentCount - 2] isEqualToString:@"Documents"])
            componentString = [path lastPathComponent];
        else {
            NSUInteger firstElement = [pathComponents indexOfObject:@"Documents"] + 1;
            for (NSUInteger x = 0; x < componentCount - firstElement; x++) {
                if (x == 0)
                componentString = [componentString stringByAppendingFormat:@"%@", pathComponents[firstElement + x]];
                else
                componentString = [componentString stringByAppendingFormat:@"/%@", pathComponents[firstElement + x]];
            }
668
        }
669 670 671 672 673 674 675 676 677 678 679 680 681

        NSFetchRequest *request = [[NSFetchRequest alloc] init];
        NSManagedObjectContext *moc = [[MLMediaLibrary sharedMediaLibrary] managedObjectContext];
        NSEntityDescription *entity = [NSEntityDescription entityForName:@"File" inManagedObjectContext:moc];
        [request setEntity:entity];
        [request setPredicate:[NSPredicate predicateWithFormat:@"url CONTAINS %@", componentString]];

        NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:@"title" ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)];
        [request setSortDescriptors:@[descriptor]];

        NSArray *matches = [moc executeFetchRequest:request error:nil];
        if (matches.count > 0)
            matchedFile = matches[0];
682 683 684 685
    }
    if (matchedFile.lastPosition)
        lastPosition = matchedFile.lastPosition.floatValue;
    duration = matchedFile.duration.intValue;
686 687 688
    if (lastPosition < .95) {
        if (duration != 0)
            playbackPositionInTime = @(lastPosition * (duration / 1000.));
689
    }
690
    if (playbackPositionInTime.intValue > 0 && (duration * lastPosition - duration) < -60000) {
691 692 693
        [_mediaPlayer.media addOptions:@{@"start-time": playbackPositionInTime}];
        APLog(@"set starttime to %i", playbackPositionInTime.intValue);
    }
694

695 696 697
    [_mediaPlayer addObserver:self forKeyPath:@"time" options:0 context:nil];
    [_mediaPlayer addObserver:self forKeyPath:@"remainingTime" options:0 context:nil];

698
    if (self.mediaList)
699
        [_listPlayer playItemAtIndex:self.itemInMediaListToBePlayedFirst];
700
    else
701
        [_listPlayer playMedia:_listPlayer.rootMedia];
702

703 704 705 706 707
    if (matchedFile) {
        if (matchedFile.lastAudioTrack.intValue > 0)
            _mediaPlayer.currentAudioTrackIndex = matchedFile.lastAudioTrack.intValue;
        if (matchedFile.lastSubtitleTrack.intValue > 0)
            _mediaPlayer.currentVideoSubTitleIndex = matchedFile.lastSubtitleTrack.intValue;
708
    }
709

710 711
    self.playbackSpeedSlider.value = [self _playbackSpeed];
    [self _updatePlaybackSpeedIndicator];
712

713 714 715 716 717 718
    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];

719
    _currentAspectRatioMask = 0;
720 721 722
    _mediaPlayer.videoAspectRatio = NULL;

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

726
    [self _resetIdleTimer];
727
    _playerIsSetup = YES;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
728 729
}

730
- (void)viewWillDisappear:(BOOL)animated
Felix Paul Kühne's avatar
Felix Paul Kühne committed
731
{
732
    [self _stopPlayback];
733
    _viewAppeared = NO;
734
    if (_idleTimer) {
735
        [_idleTimer invalidate];
736 737
        _idleTimer = nil;
    }
738
    [self.navigationController setNavigationBarHidden:NO animated:YES];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
739
    if (!SYSTEM_RUNS_IOS7_OR_LATER)
740
        [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackOpaque;
741
    [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
742
    [super viewWillDisappear:animated];
743

Felix Paul Kühne's avatar
Felix Paul Kühne committed
744 745 746 747 748 749
    // 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
750 751
}

752 753 754
- (void)_stopPlayback
{
    if (_mediaPlayer) {
755 756 757 758 759 760 761 762
        @try {
            [_mediaPlayer removeObserver:self forKeyPath:@"time"];
            [_mediaPlayer removeObserver:self forKeyPath:@"remainingTime"];
        }
        @catch (NSException *exception) {
            APLog(@"we weren't an observer yet");
        }

763
        if (_mediaPlayer.media) {
764 765 766
            [_mediaPlayer pause];
            [self _saveCurrentState];
            [_mediaPlayer stop];
767
        }
768 769
        if (_mediaPlayer)
            _mediaPlayer = nil;
770 771
        if (_listPlayer)
            _listPlayer = nil;
772
    }
773 774
    if (_fileFromMediaLibrary)
        _fileFromMediaLibrary = nil;
775 776 777 778
    if (_mediaList)
        _mediaList = nil;
    if (_url)
        _url = nil;
779
    if (_pathToExternalSubtitlesFile) {
780
            NSFileManager *fileManager = [NSFileManager defaultManager];
781 782 783
            if ([fileManager fileExistsAtPath:_pathToExternalSubtitlesFile])
                [fileManager removeItemAtPath:_pathToExternalSubtitlesFile error:nil];
            _pathToExternalSubtitlesFile = nil;
784
    }
785 786 787
    _playerIsSetup = NO;
}

788 789
- (void)_saveCurrentState
{
790
    if (self.fileFromMediaLibrary) {
791
        @try {
792
            MLFile *item = self.fileFromMediaLibrary;
793 794 795 796 797 798 799
            item.lastPosition = @([_mediaPlayer position]);
            item.lastAudioTrack = @(_mediaPlayer.currentAudioTrackIndex);
            item.lastSubtitleTrack = @(_mediaPlayer.currentVideoSubTitleIndex);
        }
        @catch (NSException *exception) {
            APLog(@"failed to save current media state - file removed?");
        }
800 801 802 803 804 805 806 807
    } 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);
        }
808 809 810
    }
}

811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849
- (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];
    }
}

850 851 852 853 854 855 856 857 858 859 860 861 862 863
#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];
864 865

    [[NSUserDefaults standardUserDefaults] setBool:_displayRemainingTime forKey:kVLCShowRemainingTime];
866 867 868 869 870 871 872 873 874 875 876
}

- (BOOL)canBecomeFirstResponder
{
    return YES;
}

- (void)remoteControlReceivedWithEvent:(UIEvent *)event
{
    switch (event.subtype) {
        case UIEventSubtypeRemoteControlPlay:
877
            [_listPlayer play];
878
            break;
879

880
        case UIEventSubtypeRemoteControlPause:
881
            [_listPlayer pause];
882