VLCMovieViewController.m 56.2 KB
Newer Older
1 2 3 4
/*****************************************************************************
 * VLCMovieViewController.m
 * VLC for iOS
 *****************************************************************************
Felix Paul Kühne's avatar
Felix Paul Kühne committed
5
 * Copyright (c) 2013-2014 VideoLAN. All rights reserved.
6 7 8 9 10 11 12 13 14 15 16
 * $Id$
 *
 * Authors: Felix Paul Kühne <fkuehne # videolan.org>
 *          Gleb Pinigin <gpinigin # gmail.com>
 *          Ahmad Harb <harb.dev.leb # gmail.com>
 *          Fabio Ritrovato <sephiroth87 # videolan.org>
 *          Pierre SAGASPE <pierre.sagaspe # me.com>
 *          Jean-Baptiste Kempf <jb # videolan.org>
 *
 * Refer to the COPYING file of the official project for license.
 *****************************************************************************/
Felix Paul Kühne's avatar
Felix Paul Kühne committed
17

18
#import "VLCMovieViewController.h"
19
#import "VLCExternalDisplayController.h"
20
#import <AVFoundation/AVFoundation.h>
21
#import <CommonCrypto/CommonDigest.h>
22
#import "UIDevice+SpeedCategory.h"
23
#import "VLCBugreporter.h"
24
#import "VLCThumbnailsCache.h"
25 26 27

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

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

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

    BOOL _controlsHidden;
    BOOL _videoFiltersHidden;
    BOOL _playbackSpeedViewHidden;

    UIActionSheet *_subtitleActionSheet;
    UIActionSheet *_audiotrackActionSheet;

    float _currentPlaybackRate;
    NSArray *_aspectRatios;
    NSUInteger _currentAspectRatioMask;

    NSTimer *_idleTimer;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    _positionSlider.accessibilityLabel = NSLocalizedString(@"PLAYBACK_POSITION", @"");
    _positionSlider.isAccessibilityElement = YES;
    _timeDisplay.isAccessibilityElement = YES;

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

205 206
    _scrubHelpLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HELP", @"");

207 208
    self.playbackSpeedView.hidden = YES;
    _playbackSpeedViewHidden = YES;
209

210 211 212 213 214
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(handleExternalScreenDidConnect:)
                   name:UIScreenDidConnectNotification object:nil];
    [center addObserver:self selector:@selector(handleExternalScreenDidDisconnect:)
                   name:UIScreenDidDisconnectNotification object:nil];
215 216 217 218 219 220
    [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];
221

222 223
    _playingExternallyTitle.text = NSLocalizedString(@"PLAYING_EXTERNALLY_TITLE", @"");
    _playingExternallyDescription.text = NSLocalizedString(@"PLAYING_EXTERNALLY_DESC", @"");
224
    if ([self hasExternalDisplay])
225
        [self showOnExternalDisplay];
226

227 228
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";

229
    _movieView.userInteractionEnabled = NO;
230 231 232
    _tapOnVideoRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleControlsVisible)];
    _tapOnVideoRecognizer.delegate = self;
    [self.view addGestureRecognizer:_tapOnVideoRecognizer];
233

234 235 236
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    _displayRemainingTime = [[defaults objectForKey:kVLCShowRemainingTime] boolValue];
    _swipeGesturesEnabled = [[defaults objectForKey:kVLCSettingPlaybackGestures] boolValue];
237

Ahmad Harb's avatar
Ahmad Harb committed
238
    if (_swipeGesturesEnabled) {
239 240 241 242
        _pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchGesture:)];
        _pinchRecognizer.delegate = self;
        [self.view addGestureRecognizer:_pinchRecognizer];

243 244
        _tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapRecognized)];
        [_tapRecognizer setNumberOfTouchesRequired:2];
Ahmad Harb's avatar
Ahmad Harb committed
245 246 247 248 249 250 251 252 253
        _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;

254 255 256 257
        [self.view addGestureRecognizer:_swipeRecognizerLeft];
        [self.view addGestureRecognizer:_swipeRecognizerRight];
        [self.view addGestureRecognizer:_panRecognizer];
        [self.view addGestureRecognizer:_tapRecognizer];
Ahmad Harb's avatar
Ahmad Harb committed
258 259 260 261 262 263
        [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerLeft];
        [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerRight];

        _panRecognizer.delegate = self;
        _swipeRecognizerRight.delegate = self;
        _swipeRecognizerLeft.delegate = self;
264
        _tapRecognizer.delegate = self;
Ahmad Harb's avatar
Ahmad Harb committed
265
    }
266

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

269
    [self.aspectRatioButton setImage:[UIImage imageNamed:@"ratioIcon"] forState:UIControlStateNormal];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
270
    if (SYSTEM_RUNS_IOS7_OR_LATER) {
271
        self.backButton.tintColor = [UIColor colorWithRed:(190.0f/255.0f) green:(190.0f/255.0f) blue:(190.0f/255.0f) alpha:1.];
272
        self.toolbar.tintColor = [UIColor whiteColor];
273
        self.toolbar.barStyle = UIBarStyleBlack;
274

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

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

327
    [[AVAudioSession sharedInstance] setDelegate:self];
328 329 330

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

    _playerIsSetup = NO;
333 334 335

    [self.movieView setAccessibilityLabel:NSLocalizedString(@"VO_VIDEOPLAYER_TITLE", @"")];
    [self.movieView setAccessibilityHint:NSLocalizedString(@"VO_VIDEOPLAYER_DOUBLETAP", @"")];
336
}
Felix Paul Kühne's avatar
Felix Paul Kühne committed
337

338 339 340 341 342
- (BOOL)_blobCheck
{
    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *directoryPath = searchPaths[0];

343
    if (![[NSFileManager defaultManager] fileExistsAtPath:[directoryPath stringByAppendingPathComponent:@"blob.bin"]])
344 345
        return NO;

346
    NSData *data = [NSData dataWithContentsOfFile:[directoryPath stringByAppendingPathComponent:@"blob.bin"]];
347
    uint8_t digest[CC_SHA1_DIGEST_LENGTH];
348
    CC_SHA1(data.bytes, (unsigned int)data.length, digest);
349 350 351 352 353 354 355 356 357 358 359 360

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

361 362
- (void)viewWillAppear:(BOOL)animated
{
363
    [super viewWillAppear:animated];
364

365 366
    _swipeGesturesEnabled = [[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingPlaybackGestures] boolValue];

367
    [self.navigationController setNavigationBarHidden:YES animated:YES];
368

Felix Paul Kühne's avatar
Felix Paul Kühne committed
369
    if (!SYSTEM_RUNS_IOS7_OR_LATER) {
370 371 372
        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
            [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackTranslucent;
    }
373

374 375 376 377 378 379
    [self _startPlayback];

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

380 381 382 383 384
- (void)viewWillLayoutSubviews
{
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
        CGSize viewSize = self.view.frame.size;
        if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) {
385 386 387
            [_controllerPanel removeFromSuperview];
            _controllerPanelLandscape.frame = (CGRect){CGPointMake(0, viewSize.height - _controllerPanelLandscape.frame.size.height), CGSizeMake(viewSize.width, _controllerPanelLandscape.frame.size.height)};
            [self.view addSubview:_controllerPanelLandscape];
388
        } else {
389 390 391
            [_controllerPanelLandscape removeFromSuperview];
            _controllerPanel.frame = (CGRect){CGPointMake(0, viewSize.height - _controllerPanel.frame.size.height), CGSizeMake(viewSize.width, _controllerPanel.frame.size.height)};
            [self.view addSubview:_controllerPanel];
392 393 394 395
        }
    }
}

396 397 398 399 400
- (void)_startPlayback
{
    if (_playerIsSetup)
        return;

401 402
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

403
    if (!self.mediaItem && !self.url && !self.mediaList) {
404
        [self _stopPlayback];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
405
        return;
406
    }
407

408
    _listPlayer = [[VLCMediaListPlayer alloc] initWithOptions:@[[NSString stringWithFormat:@"--%@=%@", kVLCSettingSubtitlesFont, [defaults objectForKey:kVLCSettingSubtitlesFont]], [NSString stringWithFormat:@"--%@=%@", kVLCSettingSubtitlesFontColor, [defaults objectForKey:kVLCSettingSubtitlesFontColor]], [NSString stringWithFormat:@"--%@=%@", kVLCSettingSubtitlesFontSize, [defaults objectForKey:kVLCSettingSubtitlesFontSize]], [NSString stringWithFormat:@"--%@=%@", kVLCSettingDeinterlace, [defaults objectForKey:kVLCSettingDeinterlace]], [NSString stringWithFormat:@"--%@=%@", kVLCSettingNetworkCaching, [defaults objectForKey:kVLCSettingNetworkCaching]]]];
409
    _mediaPlayer = _listPlayer.mediaPlayer;
410 411
    [_mediaPlayer setDelegate:self];
    [_mediaPlayer setDrawable:self.movieView];
412
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";
413

414
    VLCMedia *media;
415
    if (self.mediaItem) {
416 417 418
        MLFile *item = self.mediaItem;
        media = [VLCMedia mediaWithURL:[NSURL URLWithString:item.url]];
        item.unread = @(NO);
419
    } else if (!self.mediaList) {
420
        media = [VLCMedia mediaWithURL:self.url];
421 422
        [media parse];
    }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
423

424 425 426 427 428
    NSMutableDictionary *mediaDictionary = [[NSMutableDictionary alloc] init];

    [mediaDictionary setObject:[[defaults objectForKey:kVLCSettingStretchAudio] boolValue] ? kVLCSettingStretchAudioOnValue : kVLCSettingStretchAudioOffValue forKey:kVLCSettingStretchAudio];
    [mediaDictionary setObject:[defaults objectForKey:kVLCSettingTextEncoding] forKey:kVLCSettingTextEncoding];
    [mediaDictionary setObject:[defaults objectForKey:kVLCSettingSkipLoopFilter] forKey:kVLCSettingSkipLoopFilter];
429

430 431 432 433
    [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"];

434
    if ([tzNames containsObject:tzName] || [[tzName stringByDeletingLastPathComponent] isEqualToString:@"US"]) {
435 436 437 438 439 440 441 442 443 444 445 446
        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:
447 448
                    case 1684566644:
                    case 2126701:
449
                    {
450
                        if (![self _blobCheck]) {
451
                            [mediaDictionary setObject:[NSNull null] forKey:@"no-audio"];
452 453
                            APLog(@"audio playback disabled because an unsupported codec was found");
                        }
454 455 456 457 458 459 460 461 462 463
                        break;
                    }

                    default:
                        break;
                }
            }
        }
    }

464 465 466 467 468 469 470 471 472 473
    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];
    }
474
    [_listPlayer setRepeatMode:VLCDoNotRepeat];
475

476
    self.positionSlider.value = 0.;
477
    [self.timeDisplay setTitle:@"" forState:UIControlStateNormal];
478
    self.timeDisplay.accessibilityLabel = @"";
479
    [self.repeatButton setImage:[UIImage imageNamed:@"repeat"] forState:UIControlStateNormal];
480
    [self.repeatButtonLandscape setImage:[UIImage imageNamed:@"repeat"] forState:UIControlStateNormal];
481

482 483 484 485 486
    if (![self _isMediaSuitableForDevice]) {
        UIAlertView * alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"DEVICE_TOOSLOW_TITLE", @"") message:[NSString stringWithFormat:NSLocalizedString(@"DEVICE_TOOSLOW", @""), [[UIDevice currentDevice] model], self.mediaItem.title] delegate:self cancelButtonTitle:NSLocalizedString(@"BUTTON_CANCEL", @"") otherButtonTitles:NSLocalizedString(@"BUTTON_OPEN", @""), nil];
        [alert show];
    } else
        [self _playNewMedia];
487

488 489
    if (![self hasExternalDisplay])
        self.brightnessSlider.value = [UIScreen mainScreen].brightness * 2.;
490 491 492 493 494 495 496 497 498
}

- (BOOL)_isMediaSuitableForDevice
{
    if (!self.mediaItem)
        return YES;

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

499
    NSInteger speedCategory = [[UIDevice currentDevice] speedCategory];
500

501
    if (speedCategory == 1) {
502 503
        // iPhone 3GS, iPhone 4, first gen. iPad, 3rd and 4th generation iPod touch
        return (totalNumberOfPixels < 600000); // between 480p and 720p
504
    } else if (speedCategory == 2) {
505 506
        // iPhone 4S, iPad 2 and 3, iPod 4 and 5
        return (totalNumberOfPixels < 922000); // 720p
507
    } else if (speedCategory == 3) {
508 509 510 511 512 513 514 515 516 517 518
        // iPhone 5, iPad 4
        return (totalNumberOfPixels < 2074000); // 1080p
    }

    return YES;
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (buttonIndex == 1)
        [self _playNewMedia];
519 520
    else {
        [self _stopPlayback];
521
        [self closePlayback:nil];
522
    }
523 524 525 526
}

- (void)_playNewMedia
{
527
    NSNumber *playbackPositionInTime = @(0);
528 529 530 531 532 533 534 535
    CGFloat lastPosition = .0;
    NSInteger duration = self.mediaItem.duration.intValue;

    if (self.mediaItem.lastPosition)
        lastPosition = self.mediaItem.lastPosition.floatValue;
    if (lastPosition < .95) {
        if (duration != 0)
            playbackPositionInTime = @(lastPosition * (duration / 1000.));
536
    }
537
    if (playbackPositionInTime.intValue > 0 && (duration * lastPosition - duration) < -60000) {
538
        /* start time is not supported for media lists */
539 540 541
        [_mediaPlayer.media addOptions:@{@"start-time": playbackPositionInTime}];
        APLog(@"set starttime to %i", playbackPositionInTime.intValue);
    }
542

543 544 545
    [_mediaPlayer addObserver:self forKeyPath:@"time" options:0 context:nil];
    [_mediaPlayer addObserver:self forKeyPath:@"remainingTime" options:0 context:nil];

546
    if (self.mediaList)
547
        [_listPlayer playItemAtIndex:self.itemInMediaListToBePlayedFirst];
548
    else
549
        [_listPlayer playMedia:_listPlayer.rootMedia];
550

551 552 553 554 555 556
    if (self.mediaItem) {
        if (self.mediaItem.lastAudioTrack.intValue > 0)
            _mediaPlayer.currentAudioTrackIndex = self.mediaItem.lastAudioTrack.intValue;
        if (self.mediaItem.lastSubtitleTrack.intValue > 0)
            _mediaPlayer.currentVideoSubTitleIndex = self.mediaItem.lastSubtitleTrack.intValue;
    }
557

558 559
    self.playbackSpeedSlider.value = [self _playbackSpeed];
    [self _updatePlaybackSpeedIndicator];
560

561
    _currentAspectRatioMask = 0;
562 563 564
    _mediaPlayer.videoAspectRatio = NULL;

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

568
    [self _resetIdleTimer];
569
    _playerIsSetup = YES;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
570 571
}

572
- (void)viewWillDisappear:(BOOL)animated
Felix Paul Kühne's avatar
Felix Paul Kühne committed
573
{
574
    [self _stopPlayback];
575
    _viewAppeared = NO;
576
    if (_idleTimer) {
577
        [_idleTimer invalidate];
578 579
        _idleTimer = nil;
    }
580
    [self.navigationController setNavigationBarHidden:NO animated:YES];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
581
    if (!SYSTEM_RUNS_IOS7_OR_LATER)
582
        [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackOpaque;
583
    [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
584
    [super viewWillDisappear:animated];
585

Felix Paul Kühne's avatar
Felix Paul Kühne committed
586 587 588 589 590 591
    // 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
592 593
}

594 595 596
- (void)_stopPlayback
{
    if (_mediaPlayer) {
597 598 599 600 601 602 603 604
        @try {
            [_mediaPlayer removeObserver:self forKeyPath:@"time"];
            [_mediaPlayer removeObserver:self forKeyPath:@"remainingTime"];
        }
        @catch (NSException *exception) {
            APLog(@"we weren't an observer yet");
        }

605
        if (_mediaPlayer.media) {
606 607 608
            [_mediaPlayer pause];
            [self _saveCurrentState];
            [_mediaPlayer stop];
609
        }
610 611
        if (_mediaPlayer)
            _mediaPlayer = nil;
612 613
        if (_listPlayer)
            _listPlayer = nil;
614 615 616
    }
    if (_mediaItem)
        _mediaItem = nil;
617 618 619 620
    if (_mediaList)
        _mediaList = nil;
    if (_url)
        _url = nil;
621 622 623 624

    _playerIsSetup = NO;
}

625 626 627
- (void)_saveCurrentState
{
    if (self.mediaItem) {
628 629 630 631 632 633 634 635 636
        @try {
            MLFile *item = self.mediaItem;
            item.lastPosition = @([_mediaPlayer position]);
            item.lastAudioTrack = @(_mediaPlayer.currentAudioTrackIndex);
            item.lastSubtitleTrack = @(_mediaPlayer.currentVideoSubTitleIndex);
        }
        @catch (NSException *exception) {
            APLog(@"failed to save current media state - file removed?");
        }
637 638 639
    }
}

640 641 642 643 644 645 646 647 648 649 650 651 652 653
#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];
654 655

    [[NSUserDefaults standardUserDefaults] setBool:_displayRemainingTime forKey:kVLCShowRemainingTime];
656 657 658 659 660 661 662 663 664 665 666
}

- (BOOL)canBecomeFirstResponder
{
    return YES;
}

- (void)remoteControlReceivedWithEvent:(UIEvent *)event
{
    switch (event.subtype) {
        case UIEventSubtypeRemoteControlPlay:
667
            [_listPlayer play];
668
            break;
669

670
        case UIEventSubtypeRemoteControlPause:
671
            [_listPlayer pause];
672 673 674
            break;

        case UIEventSubtypeRemoteControlTogglePlayPause:
675
            [self playPause];
676 677
            break;

678
        case UIEventSubtypeRemoteControlNextTrack:
679
            [self forward:nil];
680 681

        case UIEventSubtypeRemoteControlPreviousTrack:
682
            [self backward:nil];
683

Felix Paul Kühne's avatar
Felix Paul Kühne committed
684 685 686
        case UIEventSubtypeRemoteControlStop:
            [self closePlayback:nil];

687 688 689 690 691
        default:
            break;
    }
}

692 693
#pragma mark - controls visibility

694 695
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)recognizer
{
696 697 698
    if (!_swipeGesturesEnabled)
        return;

699 700 701 702
    if (recognizer.velocity < 0.)
        [self closePlayback:nil];
}

703 704
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
705 706 707
    if (!_swipeGesturesEnabled)
        return NO;

Felix Paul Kühne's avatar
Felix Paul Kühne committed
708
    if (touch.view != self.view)
709 710 711 712 713
        return NO;

    return YES;
}

Ahmad Harb's avatar
Ahmad Harb committed
714 715
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
716 717 718
    if (!_swipeGesturesEnabled)
        return NO;

Ahmad Harb's avatar
Ahmad Harb committed
719 720 721
    return YES;
}

722
- (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated
723
{
724
    _controlsHidden = hidden;
725
    CGFloat alpha = _controlsHidden? 0.0f: 1.0f;
726

727 728
    if (!_controlsHidden) {
        _controllerPanel.alpha = 0.0f;
729
        _controllerPanel.hidden = !_videoFiltersHidden;
730 731
        _controllerPanelLandscape.alpha = 0.0f;
        _controllerPanelLandscape.hidden = !_videoFiltersHidden;
732 733
        _toolbar.alpha = 0.0f;
        _toolbar.hidden = NO;
734 735
        _videoFilterView.alpha = 0.0f;
        _videoFilterView.hidden = _videoFiltersHidden;
736 737
        _playbackSpeedView.alpha = 0.0f;
        _playbackSpeedView.hidden = _playbackSpeedViewHidden;
738
    }
739

740 741
    void (^animationBlock)() = ^() {
        _controllerPanel.alpha = alpha;
742
        _controllerPanelLandscape.alpha = alpha;
743
        _toolbar.alpha = alpha;
744
        _videoFilterView.alpha = alpha;
745
        _playbackSpeedView.alpha = alpha;
746
    };
747

748
    void (^completionBlock)(BOOL finished) = ^(BOOL finished) {
749 750
        _controllerPanel.hidden = _videoFiltersHidden ? _controlsHidden : NO;
        _controllerPanelLandscape.hidden = _videoFiltersHidden ? _controlsHidden : NO;
751
        _toolbar.hidden = _controlsHidden;
752
        _videoFilterView.hidden = _videoFiltersHidden;
753
        _playbackSpeedView.hidden = _playbackSpeedViewHidden;
754
    };
755

756 757
    UIStatusBarAnimation animationType = animated? UIStatusBarAnimationFade: UIStatusBarAnimationNone;
    NSTimeInterval animationDuration = animated? 0.3: 0.0;
758 759

    [[UIApplication sharedApplication] setStatusBarHidden:_viewAppeared ? _controlsHidden : NO withAnimation:animationType];
760
    [UIView animateWithDuration:animationDuration animations:animationBlock completion:completionBlock];
761

762
    _volumeView.hidden = _volumeViewLandscape.hidden = _controllerPanel.hidden;
763
}
764

765
- (void)toggleControlsVisible
766
{
767 768 769
    if (_controlsHidden && !_videoFiltersHidden)
        _videoFiltersHidden = YES;

770
    [self setControlsHidden:!_controlsHidden animated:YES];
771 772
}

773
- (void)_resetIdleTimer
774 775
{
    if (!_idleTimer)
776
        _idleTimer = [NSTimer scheduledTimerWithTimeInterval:4.
777 778 779 780 781
                                                      target:self
                                                    selector:@selector(idleTimerExceeded)
                                                    userInfo:nil
                                                     repeats:NO];
    else {
782 783
        if (fabs([_idleTimer.fireDate timeIntervalSinceNow]) < 4.)
            [_idleTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:4.]];
784 785 786 787 788 789 790
    }
}

- (void)idleTimerExceeded
{
    _idleTimer = nil;
    if (!_controlsHidden)
791
        [self toggleControlsVisible];
792

793 794 795 796 797 798
    if (!_videoFiltersHidden)
        _videoFiltersHidden = YES;

    if (!_playbackSpeedViewHidden)
        _playbackSpeedViewHidden = YES;

Felix Paul Kühne's avatar
Felix Paul Kühne committed
799 800
    if (self.scrubIndicatorView.hidden == NO)
        self.scrubIndicatorView.hidden = YES;
801 802
}

803 804
- (UIResponder *)nextResponder
{
805
    [self _resetIdleTimer];
806 807 808
    return [super nextResponder];
}

809 810 811 812
#pragma mark - controls

- (IBAction)closePlayback:(id)sender
{
813
    [self setControlsHidden:NO animated:NO];
814
    [self.navigationController dismissViewControllerAnimated:YES completion:nil];
815 816 817 818
}

- (IBAction)positionSliderAction:(UISlider *)sender
{
819 820 821
    /* we need to limit the number of events sent by the slider, since otherwise, the user
     * wouldn't see the I-frames when seeking on current mobile devices. This isn't a problem
     * within the Simulator, but especially on older ARMv7 devices, it's clearly noticeable. */
822
    [self performSelector:@selector(_setPositionForReal) withObject:nil afterDelay:0.3];
823
    VLCTime *newPosition = [VLCTime timeWithInt:(int)(_positionSlider.value * self.mediaItem.duration.intValue)];
824
    [self.timeDisplay setTitle:newPosition.stringValue forState:UIControlStateNormal];
825
    self.timeDisplay.accessibilityLabel = [NSString stringWithFormat:@"%@: %@", NSLocalizedString(@"PLAYBACK_POSITION", @""), newPosition.stringValue];
826
    _positionSet = NO;
827
    [self _resetIdleTimer];
828 829
}

830 831 832 833 834 835 836 837
- (void)_setPositionForReal
{
    if (!_positionSet) {
        _mediaPlayer.position = _positionSlider.value;
        _positionSet = YES;
    }
}

838 839 840 841
- (IBAction)positionSliderTouchDown:(id)sender
{
    [self _updateScrubLabel];
    self.scrubIndicatorView.hidden = NO;
842
    _isScrubbing = YES;
843 844 845 846 847
}

- (IBAction)positionSliderTouchUp:(id)sender
{
    self.scrubIndicatorView.hidden = YES;
848
    _isScrubbing = NO;
849 850 851 852 853 854 855 856 857 858 859 860 861
}

- (void)_updateScrubLabel
{
    float speed = self.positionSlider.scrubbingSpeed;
    if (speed == 1.)
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HIGH", @"");
    else if (speed == .5)
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HALF", @"");
    else if (speed == .25)
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_QUARTER", @"");
    else
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_FINE", @"");
862 863

    [self _resetIdleTimer];
864 865 866 867 868 869 870
}

- (IBAction)positionSliderDrag:(id)sender
{
    [self _updateScrubLabel];
}

871 872 873 874 875
- (IBAction)volumeSliderAction:(id)sender
{
    [self _resetIdleTimer];
}

876