VLCMovieViewController.m 47.5 KB
Newer Older
Felix Paul Kühne's avatar
Felix Paul Kühne committed
1
//
2
//  VLCMovieViewController.m
Felix Paul Kühne's avatar
Felix Paul Kühne committed
3 4 5 6 7
//  AspenProject
//
//  Created by Felix Paul Kühne on 27.02.13.
//  Copyright (c) 2013 VideoLAN. All rights reserved.
//
8 9
//  Refer to the COPYING file of the official project for license.
//
Felix Paul Kühne's avatar
Felix Paul Kühne committed
10

11
#import "VLCMovieViewController.h"
12
#import "VLCExternalDisplayController.h"
13
#import <AVFoundation/AVFoundation.h>
14
#import <CommonCrypto/CommonDigest.h>
15
#import "UIDevice+SpeedCategory.h"
16
#import "VLCBugreporter.h"
17 18 19

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

21 22
#define INPUT_RATE_DEFAULT  1000.

23
@interface VLCMovieViewController () <UIGestureRecognizerDelegate, AVAudioSessionDelegate>
24
{
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
    VLCMediaPlayer *_mediaPlayer;

    BOOL _controlsHidden;
    BOOL _videoFiltersHidden;
    BOOL _playbackSpeedViewHidden;

    UIActionSheet *_subtitleActionSheet;
    UIActionSheet *_audiotrackActionSheet;

    float _currentPlaybackRate;
    NSArray *_aspectRatios;
    NSUInteger _currentAspectRatioMask;

    NSTimer *_idleTimer;

40
    BOOL _shouldResumePlaying;
41
    BOOL _viewAppeared;
42
    BOOL _displayRemainingTime;
43
    BOOL _positionSet;
44
    BOOL _playerIsSetup;
45
    BOOL _isScrubbing;
Ahmad Harb's avatar
Ahmad Harb committed
46 47 48 49 50 51 52 53 54 55 56

    BOOL _swipeGesturesEnabled;
    int _forwardDuration;
    int _backwardDuration;
    NSString * panType;
    UIView *_rootView;
    UIView *_splashView;
    UIPanGestureRecognizer *_panRecognizer;
    UISwipeGestureRecognizer *_swipeRecognizerLeft;
    UISwipeGestureRecognizer *_swipeRecognizerRight;
    UITapGestureRecognizer *_tapRecognizer;
57 58
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
59 60
@property (nonatomic, strong) UIPopoverController *masterPopoverController;
@property (nonatomic, strong) UIWindow *externalWindow;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
61 62
@end

63
@implementation VLCMovieViewController
Felix Paul Kühne's avatar
Felix Paul Kühne committed
64

65 66 67 68 69 70 71
+ (void)initialize
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSDictionary *appDefaults = @{kVLCShowRemainingTime : @(YES)};
    [defaults registerDefaults:appDefaults];
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
72 73
- (void)dealloc
{
Ahmad Harb's avatar
Ahmad Harb committed
74 75 76 77 78 79 80 81 82 83 84
    if (_splashView)
        [_splashView removeFromSuperview];
    if (_tapRecognizer)
        [_rootView removeGestureRecognizer:_tapRecognizer];
    if (_swipeRecognizerLeft)
        [_rootView removeGestureRecognizer:_swipeRecognizerLeft];
    if (_swipeRecognizerRight)
        [_rootView removeGestureRecognizer:_swipeRecognizerRight];
    if (_panRecognizer)
        [_rootView removeGestureRecognizer:_panRecognizer];

85
    [[NSNotificationCenter defaultCenter] removeObserver:self];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
86 87
}

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

90
- (void)setMediaItem:(id)newMediaItem
Felix Paul Kühne's avatar
Felix Paul Kühne committed
91
{
92 93
    if (_mediaItem != newMediaItem) {
        [self _stopPlayback];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
94
        _mediaItem = newMediaItem;
95 96 97
        if (_viewAppeared)
            [self _startPlayback];
    }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
98

99
    if (self.masterPopoverController != nil)
Felix Paul Kühne's avatar
Felix Paul Kühne committed
100 101 102
        [self.masterPopoverController dismissPopoverAnimated:YES];
}

103 104 105 106 107 108 109 110 111 112
- (void)setUrl:(NSURL *)url
{
    if (_url != url) {
        [self _stopPlayback];
        _url = url;
        if (_viewAppeared)
            [self _startPlayback];
    }
}

113
- (void)viewDidLoad
Felix Paul Kühne's avatar
Felix Paul Kühne committed
114
{
115
    [super viewDidLoad];
116 117
    self.wantsFullScreenLayout = YES;

118 119
    self.videoFilterView.hidden = YES;
    _videoFiltersHidden = YES;
120
    _hueLabel.text = NSLocalizedString(@"VFILTER_HUE", @"");
121 122
    _hueSlider.accessibilityLabel = _hueLabel.text;
    _hueSlider.isAccessibilityElement = YES;
123
    _contrastLabel.text = NSLocalizedString(@"VFILTER_CONTRAST", @"");
124 125
    _contrastSlider.accessibilityLabel = _contrastLabel.text;
    _contrastSlider.isAccessibilityElement = YES;
126
    _brightnessLabel.text = NSLocalizedString(@"VFILTER_BRIGHTNESS", @"");
127 128
    _brightnessSlider.accessibilityLabel = _brightnessLabel.text;
    _brightnessSlider.isAccessibilityElement = YES;
129
    _saturationLabel.text = NSLocalizedString(@"VFILTER_SATURATION", @"");
130 131
    _saturationSlider.accessibilityLabel = _saturationLabel.text;
    _saturationSlider.isAccessibilityElement = YES;
132
    _gammaLabel.text = NSLocalizedString(@"VFILTER_GAMMA", @"");
133 134
    _gammaSlider.accessibilityLabel = _gammaLabel.text;
    _gammaSlider.isAccessibilityElement = YES;
135
    _playbackSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SPEED", @"");
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
    _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;
    _subtitleSwitcherButton.accessibilityLabel = NSLocalizedString(@"CHOOSE_SUBTITLE_TRACK", @"");
    _subtitleSwitcherButton.isAccessibilityElement = YES;
    _playbackSpeedButton.accessibilityLabel = _playbackSpeedLabel.text;
    _playbackSpeedButton.isAccessibilityElement = YES;
    _videoFilterButton.accessibilityLabel = NSLocalizedString(@"VIDEO_FILTER", @"");
    _videoFilterButton.isAccessibilityElement = YES;
    _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;
    _bwdButton.accessibilityLabel = NSLocalizedString(@"BWD_BUTTON", @"");
    _bwdButton.isAccessibilityElement = YES;
    _fwdButton.accessibilityLabel = NSLocalizedString(@"FWD_BUTTON", @"");
    _fwdButton.isAccessibilityElement = YES;
161

162 163
    _scrubHelpLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HELP", @"");

164 165
    self.playbackSpeedView.hidden = YES;
    _playbackSpeedViewHidden = YES;
166

167 168 169 170 171
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(handleExternalScreenDidConnect:)
                   name:UIScreenDidConnectNotification object:nil];
    [center addObserver:self selector:@selector(handleExternalScreenDidDisconnect:)
                   name:UIScreenDidDisconnectNotification object:nil];
172 173 174 175 176 177
    [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];
178

179 180
    _playingExternallyTitle.text = NSLocalizedString(@"PLAYING_EXTERNALLY_TITLE", @"");
    _playingExternallyDescription.text = NSLocalizedString(@"PLAYING_EXTERNALLY_DESC", @"");
181
    if ([self hasExternalDisplay])
182
        [self showOnExternalDisplay];
183

184 185
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";

186
    _movieView.userInteractionEnabled = NO;
187 188 189 190 191
    UITapGestureRecognizer *tapOnVideoRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleControlsVisible)];
    tapOnVideoRecognizer.delegate = self;
    [self.view addGestureRecognizer:tapOnVideoRecognizer];

    _displayRemainingTime = [[[NSUserDefaults standardUserDefaults] objectForKey:kVLCShowRemainingTime] boolValue];
192

193 194 195 196
    UIPinchGestureRecognizer *pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchGesture:)];
    pinchRecognizer.delegate = self;
    [self.view addGestureRecognizer:pinchRecognizer];

Ahmad Harb's avatar
Ahmad Harb committed
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
    _swipeGesturesEnabled = YES;
    if (_swipeGesturesEnabled) {
        _forwardDuration = 30;
        _backwardDuration = 10;

        _tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapRecognized)];
        [_tapRecognizer setNumberOfTouchesRequired:2];
        _panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panRecognized:)];
        [_panRecognizer setMinimumNumberOfTouches:1];
        [_panRecognizer setMaximumNumberOfTouches:1];

        _swipeRecognizerLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRecognized:)];
        _swipeRecognizerLeft.direction = UISwipeGestureRecognizerDirectionLeft;
        _swipeRecognizerRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRecognized:)];
        _swipeRecognizerRight.direction = UISwipeGestureRecognizerDirectionRight;

        UIWindow * window = [[[UIApplication sharedApplication] delegate] window];
        _rootView = window.rootViewController.view;
        [_rootView addGestureRecognizer:_swipeRecognizerLeft];
        [_rootView addGestureRecognizer:_swipeRecognizerRight];
        [_rootView addGestureRecognizer:_panRecognizer];
        [_rootView addGestureRecognizer:_tapRecognizer];
        [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerLeft];
        [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerRight];

        _panRecognizer.delegate = self;
        _swipeRecognizerRight.delegate = self;
        _swipeRecognizerLeft.delegate = self;
        _tapRecognizer.delegate = self;
    }
227

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

230 231 232
    [self.aspectRatioButton setBackgroundImage:[UIImage imageNamed:@"ratioButton"] forState:UIControlStateNormal];
    [self.aspectRatioButton setBackgroundImage:[UIImage imageNamed:@"ratioButtonHighlight"] forState:UIControlStateHighlighted];
    [self.aspectRatioButton setImage:[UIImage imageNamed:@"ratioIcon"] forState:UIControlStateNormal];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
233
    if (SYSTEM_RUNS_IOS7_OR_LATER) {
234
        self.backButton.tintColor = [UIColor colorWithRed:(190.0f/255.0f) green:(190.0f/255.0f) blue:(190.0f/255.0f) alpha:1.];
235 236 237
        self.toolbar.tintColor = [UIColor whiteColor];
        self.toolbar.barTintColor = [UIColor colorWithWhite:0.f alpha:1.f];

238
        CGRect rect = self.positionSlider.frame;
239 240 241 242 243 244 245
        rect.origin.y = rect.origin.y - 5.;
        self.positionSlider.frame = rect;
        rect = self.resetVideoFilterButton.frame;
        rect.origin.y = rect.origin.y + 5.;
        self.resetVideoFilterButton.frame = rect;
    } else {
        [self.toolbar setBackgroundImage:[UIImage imageNamed:@"seekbarBg"] forBarMetrics:UIBarMetricsDefault];
246 247 248
        [self.backButton setBackgroundImage:[UIImage imageNamed:@"playbackDoneButton"] forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
        [self.backButton setBackgroundImage:[UIImage imageNamed:@"playbackDoneButtonHighlight"] forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
    }
249

250 251 252 253 254 255 256 257 258 259
    /* this looks a bit weird, but we need to support iOS 5 and should show the same appearance */
    UISlider *volumeSlider = nil;
    for (id aView in self.volumeView.subviews){
        if ([[[aView class] description] isEqualToString:@"MPVolumeSlider"]){
            volumeSlider = (UISlider *)aView;
            break;
        }
    }
    [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];
260
    [volumeSlider setThumbImage:[UIImage imageNamed:@"volumeballslider"] forState:UIControlStateNormal];
261 262 263
    [volumeSlider addTarget:self
                     action:@selector(volumeSliderAction:)
           forControlEvents:UIControlEventValueChanged];
264

265
    [[AVAudioSession sharedInstance] setDelegate:self];
266 267 268

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

    _playerIsSetup = NO;
271 272 273

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

276 277 278 279 280
- (BOOL)_blobCheck
{
    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *directoryPath = searchPaths[0];

281
    if (![[NSFileManager defaultManager] fileExistsAtPath:[directoryPath stringByAppendingPathComponent:@"blob.bin"]])
282 283
        return NO;

284
    NSData *data = [NSData dataWithContentsOfFile:[directoryPath stringByAppendingPathComponent:@"blob.bin"]];
285 286 287 288 289 290 291 292 293 294 295 296 297 298
    uint8_t digest[CC_SHA1_DIGEST_LENGTH];
    CC_SHA1(data.bytes, data.length, digest);

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

299 300
- (void)viewWillAppear:(BOOL)animated
{
301
    [super viewWillAppear:animated];
302

303
    [self.navigationController setNavigationBarHidden:YES animated:YES];
304

Felix Paul Kühne's avatar
Felix Paul Kühne committed
305
    if (!SYSTEM_RUNS_IOS7_OR_LATER) {
306 307 308
        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
            [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackTranslucent;
    }
309

310 311 312 313 314 315 316 317 318 319 320
    [self _startPlayback];

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

- (void)_startPlayback
{
    if (_playerIsSetup)
        return;

321 322
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

323 324
    if (!self.mediaItem && !self.url) {
        [self _stopPlayback];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
325
        return;
326
    }
327

328 329 330 331
    _mediaPlayer = [[VLCMediaPlayer 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]]]];
    [_mediaPlayer setDelegate:self];
    [_mediaPlayer setDrawable:self.movieView];

332
    VLCMedia *media;
333 334
    if (self.mediaItem) {
        self.title = [self.mediaItem title];
335 336 337 338 339 340 341 342
        MLFile *item = self.mediaItem;
        media = [VLCMedia mediaWithURL:[NSURL URLWithString:item.url]];
        item.unread = @(NO);

        if (item.isAlbumTrack) {
            self.trackNameLabel.text = item.albumTrack.title;
            self.artistNameLabel.text = item.albumTrack.artist;
            self.albumNameLabel.text = item.albumTrack.album.name;
343 344
        } else
            self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";
345
    } else {
346
        media = [VLCMedia mediaWithURL:self.url];
347 348
        self.title = @"Network Stream";
    }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
349

350 351
    [media addOptions:
     @{kVLCSettingStretchAudio :
352
     [[defaults objectForKey:kVLCSettingStretchAudio] boolValue] ? kVLCSettingStretchAudioOnValue : kVLCSettingStretchAudioOffValue, kVLCSettingTextEncoding : [defaults objectForKey:kVLCSettingTextEncoding], kVLCSettingSkipLoopFilter : [defaults objectForKey:kVLCSettingSkipLoopFilter]}];
353

354 355 356 357
    [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"];

358
    if ([tzNames containsObject:tzName] || [[tzName stringByDeletingLastPathComponent] isEqualToString:@"US"]) {
359 360 361 362 363 364 365 366 367 368 369 370
        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:
371 372
                    case 1684566644:
                    case 2126701:
373
                    {
374 375 376 377
                        if (![self _blobCheck]) {
                            [media addOptions:@{@"no-audio" : [NSNull null]}];
                            APLog(@"audio playback disabled because an unsupported codec was found");
                        }
378 379 380 381 382 383 384 385 386 387
                        break;
                    }

                    default:
                        break;
                }
            }
        }
    }

388 389
    [_mediaPlayer setMedia:media];

390
    self.positionSlider.value = 0.;
391
    [self.timeDisplay setTitle:@"" forState:UIControlStateNormal];
392
    self.timeDisplay.accessibilityLabel = @"";
393

394 395 396 397 398
    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];
399

400 401
    if (![self hasExternalDisplay])
        self.brightnessSlider.value = [UIScreen mainScreen].brightness * 2.;
402 403 404 405 406 407 408 409 410
}

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

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

411
    NSInteger speedCategory = [[UIDevice currentDevice] speedCategory];
412

413
    if (speedCategory == 1) {
414 415
        // iPhone 3GS, iPhone 4, first gen. iPad, 3rd and 4th generation iPod touch
        return (totalNumberOfPixels < 600000); // between 480p and 720p
416
    } else if (speedCategory == 2) {
417 418
        // iPhone 4S, iPad 2 and 3, iPod 4 and 5
        return (totalNumberOfPixels < 922000); // 720p
419
    } else if (speedCategory == 3) {
420 421 422 423 424 425 426 427 428 429 430
        // iPhone 5, iPad 4
        return (totalNumberOfPixels < 2074000); // 1080p
    }

    return YES;
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (buttonIndex == 1)
        [self _playNewMedia];
431 432
    else {
        [self _stopPlayback];
433
        [self closePlayback:nil];
434
    }
435 436 437 438
}

- (void)_playNewMedia
{
439
    NSNumber *playbackPositionInTime = @(0);
440
    if (self.mediaItem.lastPosition && [self.mediaItem.lastPosition floatValue] < .95) {
441 442
        if (self.mediaItem.duration.intValue != 0)
            playbackPositionInTime = @(self.mediaItem.lastPosition.floatValue * (self.mediaItem.duration.intValue / 1000.));
443
    }
444 445 446 447
    if (playbackPositionInTime.intValue > 0) {
        [_mediaPlayer.media addOptions:@{@"start-time": playbackPositionInTime}];
        APLog(@"set starttime to %i", playbackPositionInTime.intValue);
    }
448

449 450 451
    [_mediaPlayer addObserver:self forKeyPath:@"time" options:0 context:nil];
    [_mediaPlayer addObserver:self forKeyPath:@"remainingTime" options:0 context:nil];

Felix Paul Kühne's avatar
Felix Paul Kühne committed
452
    [_mediaPlayer play];
453

454 455 456 457 458 459
    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;
    }
460

461 462
    self.playbackSpeedSlider.value = [self _playbackSpeed];
    [self _updatePlaybackSpeedIndicator];
463

464 465
    [self performSelectorInBackground:@selector(_updateExportedPlaybackInformation) withObject:nil];

466 467
    _currentAspectRatioMask = 0;
    _mediaPlayer.videoAspectRatio =  NULL;
468

469
    [self _resetIdleTimer];
470
    _playerIsSetup = YES;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
471 472
}

473
- (void)viewWillDisappear:(BOOL)animated
Felix Paul Kühne's avatar
Felix Paul Kühne committed
474
{
475
    [self _stopPlayback];
476
    _viewAppeared = NO;
477
    if (_idleTimer) {
478
        [_idleTimer invalidate];
479 480
        _idleTimer = nil;
    }
481
    [self.navigationController setNavigationBarHidden:NO animated:YES];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
482
    if (!SYSTEM_RUNS_IOS7_OR_LATER)
483
        [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackOpaque;
484
    [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
485
    [super viewWillDisappear:animated];
486

Felix Paul Kühne's avatar
Felix Paul Kühne committed
487 488 489 490 491 492
    // 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
493 494
}

495 496 497
- (void)_stopPlayback
{
    if (_mediaPlayer) {
498 499 500 501 502 503 504 505
        @try {
            [_mediaPlayer removeObserver:self forKeyPath:@"time"];
            [_mediaPlayer removeObserver:self forKeyPath:@"remainingTime"];
        }
        @catch (NSException *exception) {
            APLog(@"we weren't an observer yet");
        }

506
        if (_mediaPlayer.media) {
507 508 509
            [_mediaPlayer pause];
            [self _saveCurrentState];
            [_mediaPlayer stop];
510
        }
511 512
        if (_mediaPlayer)
            _mediaPlayer = nil;
513 514 515 516 517 518 519
    }
    if (_mediaItem)
        _mediaItem = nil;

    _playerIsSetup = NO;
}

520 521 522
- (void)_saveCurrentState
{
    if (self.mediaItem) {
523 524 525 526 527 528 529 530 531
        @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?");
        }
532 533 534
    }
}

535 536 537 538 539 540 541 542 543 544 545 546 547 548
#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];
549 550

    [[NSUserDefaults standardUserDefaults] setBool:_displayRemainingTime forKey:kVLCShowRemainingTime];
551 552 553 554 555 556 557 558 559 560 561 562 563
}

- (BOOL)canBecomeFirstResponder
{
    return YES;
}

- (void)remoteControlReceivedWithEvent:(UIEvent *)event
{
    switch (event.subtype) {
        case UIEventSubtypeRemoteControlPlay:
            [_mediaPlayer play];
            break;
564

565 566 567 568 569
        case UIEventSubtypeRemoteControlPause:
            [_mediaPlayer pause];
            break;

        case UIEventSubtypeRemoteControlTogglePlayPause:
570
            [self playPause];
571 572 573 574 575 576 577
            break;

        default:
            break;
    }
}

578 579
#pragma mark - controls visibility

580 581 582 583 584 585
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)recognizer
{
    if (recognizer.velocity < 0.)
        [self closePlayback:nil];
}

586 587
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
588
    if (touch.view != self.view)
589 590 591 592 593
        return NO;

    return YES;
}

Ahmad Harb's avatar
Ahmad Harb committed
594 595 596 597 598
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

599
- (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated
600
{
601
    _controlsHidden = hidden;
602
    CGFloat alpha = _controlsHidden? 0.0f: 1.0f;
603

604 605
    if (!_controlsHidden) {
        _controllerPanel.alpha = 0.0f;
606
        _controllerPanel.hidden = !_videoFiltersHidden;
607 608
        _toolbar.alpha = 0.0f;
        _toolbar.hidden = NO;
609 610
        _videoFilterView.alpha = 0.0f;
        _videoFilterView.hidden = _videoFiltersHidden;
611 612
        _playbackSpeedView.alpha = 0.0f;
        _playbackSpeedView.hidden = _playbackSpeedViewHidden;
613
    }
614

615 616
    void (^animationBlock)() = ^() {
        _controllerPanel.alpha = alpha;
617
        _toolbar.alpha = alpha;
618
        _videoFilterView.alpha = alpha;
619
        _playbackSpeedView.alpha = alpha;
620
    };
621

622
    void (^completionBlock)(BOOL finished) = ^(BOOL finished) {
623
        if (_videoFiltersHidden)
624
            _controllerPanel.hidden = _controlsHidden;
625
        else
626
            _controllerPanel.hidden = NO;
627
        _toolbar.hidden = _controlsHidden;
628
        _videoFilterView.hidden = _videoFiltersHidden;
629
        _playbackSpeedView.hidden = _playbackSpeedViewHidden;
630
    };
631

632 633
    UIStatusBarAnimation animationType = animated? UIStatusBarAnimationFade: UIStatusBarAnimationNone;
    NSTimeInterval animationDuration = animated? 0.3: 0.0;
634 635

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

    _volumeView.hidden = _controllerPanel.hidden;
639
}
640

641
- (void)toggleControlsVisible
642
{
643 644 645
    if (_controlsHidden && !_videoFiltersHidden)
        _videoFiltersHidden = YES;

646
    [self setControlsHidden:!_controlsHidden animated:YES];
647 648
}

649
- (void)_resetIdleTimer
650 651
{
    if (!_idleTimer)
652
        _idleTimer = [NSTimer scheduledTimerWithTimeInterval:4.
653 654 655 656 657
                                                      target:self
                                                    selector:@selector(idleTimerExceeded)
                                                    userInfo:nil
                                                     repeats:NO];
    else {
658 659
        if (fabs([_idleTimer.fireDate timeIntervalSinceNow]) < 4.)
            [_idleTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:4.]];
660 661 662 663 664 665 666
    }
}

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

669 670 671 672 673 674
    if (!_videoFiltersHidden)
        _videoFiltersHidden = YES;

    if (!_playbackSpeedViewHidden)
        _playbackSpeedViewHidden = YES;

Felix Paul Kühne's avatar
Felix Paul Kühne committed
675 676
    if (self.scrubIndicatorView.hidden == NO)
        self.scrubIndicatorView.hidden = YES;
677 678
}

679 680
- (UIResponder *)nextResponder
{
681
    [self _resetIdleTimer];
682 683 684
    return [super nextResponder];
}

685 686 687 688
#pragma mark - controls

- (IBAction)closePlayback:(id)sender
{
689
    [self setControlsHidden:NO animated:NO];
690 691 692 693 694
    [self.navigationController popViewControllerAnimated:YES];
}

- (IBAction)positionSliderAction:(UISlider *)sender
{
695 696 697
    /* 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. */
698
    [self performSelector:@selector(_setPositionForReal) withObject:nil afterDelay:0.3];
699
    VLCTime *newPosition = [VLCTime timeWithInt:(int)(_positionSlider.value * self.mediaItem.duration.intValue)];
700
    [self.timeDisplay setTitle:newPosition.stringValue forState:UIControlStateNormal];
701
    self.timeDisplay.accessibilityLabel = [NSString stringWithFormat:@"%@: %@", NSLocalizedString(@"PLAYBACK_POSITION", @""), newPosition.stringValue];
702
    _positionSet = NO;
703
    [self _resetIdleTimer];
704 705
}

706 707 708 709 710 711 712 713
- (void)_setPositionForReal
{
    if (!_positionSet) {
        _mediaPlayer.position = _positionSlider.value;
        _positionSet = YES;
    }
}

714 715 716 717
- (IBAction)positionSliderTouchDown:(id)sender
{
    [self _updateScrubLabel];
    self.scrubIndicatorView.hidden = NO;
718
    _isScrubbing = YES;
719 720 721 722 723
}

- (IBAction)positionSliderTouchUp:(id)sender
{
    self.scrubIndicatorView.hidden = YES;
724
    _isScrubbing = NO;
725 726 727 728 729 730 731 732 733 734 735 736 737
}

- (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", @"");
738 739

    [self _resetIdleTimer];
740 741 742 743 744 745 746
}

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

747 748 749 750 751
- (IBAction)volumeSliderAction:(id)sender
{
    [self _resetIdleTimer];
}

752 753
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
754 755 756 757
    if (!_isScrubbing) {
        self.positionSlider.value = [_mediaPlayer position];
    }

758
    if (_displayRemainingTime)
759
        [self.timeDisplay setTitle:[[_mediaPlayer remainingTime] stringValue] forState:UIControlStateNormal];
760
    else
761
        [self.timeDisplay setTitle:[[_mediaPlayer time] stringValue] forState:UIControlStateNormal];
762 763
}

764 765
- (void)mediaPlayerStateChanged:(NSNotification *)aNotification
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
766 767 768 769
    VLCMediaPlayerState currentState = _mediaPlayer.state;

    if (currentState == VLCMediaPlayerStateError) {
        [self.statusLabel showStatusMessage:NSLocalizedString(@"PLAYBACK_FAILED", @"")];
770
        [self performSelector:@selector(closePlayback:) withObject:nil afterDelay:2.];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
771 772
    }

773 774 775
    if (currentState == VLCMediaPlayerStateEnded || currentState == VLCMediaPlayerStateStopped)
        [self performSelector:@selector(closePlayback:) withObject:nil afterDelay:2.];

776
    UIImage *playPauseImage = [_mediaPlayer isPlaying]? [UIImage imageNamed:@"pauseIcon"] : [UIImage imageNamed:@"playIcon"];
Gleb Pinigin's avatar
Gleb Pinigin committed
777
    [_playPauseButton setImage:playPauseImage forState:UIControlStateNormal];
778 779 780 781 782 783 784 785 786 787

    if ([[_mediaPlayer audioTrackIndexes] count] > 2)
        self.audioSwitcherButton.hidden = NO;
    else
        self.audioSwitcherButton.hidden = YES;

    if ([[_mediaPlayer videoSubTitlesIndexes] count] > 1)
        self.subtitleSwitcherButton.hidden = NO;
    else
        self.subtitleSwitcherButton.hidden = YES;
788 789
}

790
- (IBAction)playPause
791
{
792
    if ([_mediaPlayer isPlaying])
793
        [_mediaPlayer pause];
794
    else
795 796 797 798 799 800 801 802 803 804 805 806 807 808 809
        [_mediaPlayer play];
}

- (IBAction)forward:(id)sender
{
    [_mediaPlayer mediumJumpForward];
}

- (IBAction)backward:(id)sender
{
    [_mediaPlayer mediumJumpBackward];
}

- (IBAction)switchAudioTrack:(id)sender
{
810
    _audiotrackActionSheet = [[UIActionSheet alloc] initWithTitle:NSLocalizedString(@"CHOOSE_AUDIO_TRACK", @"audio track selector") delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles: nil];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
811
    NSArray *audioTracks = [_mediaPlayer audioTrackNames];
812 813
    NSArray *audioTrackIndexes = [_mediaPlayer audioTrackIndexes];

814
    NSUInteger count = [audioTracks count];
815 816 817 818 819
    for (NSUInteger i = 0; i < count; i++) {
        NSString *indexIndicator = ([audioTrackIndexes[i] intValue] == [_mediaPlayer currentAudioTrackIndex])? @"\u2713": @"";
        NSString *buttonTitle = [NSString stringWithFormat:@"%@ %@", indexIndicator, audioTracks[i]];
        [_audiotrackActionSheet addButtonWithTitle:buttonTitle];
    }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
820

821
    [_audiotrackActionSheet addButtonWithTitle:NSLocalizedString(@"BUTTON_CANCEL", @"cancel button")];
822
    [_audiotrackActionSheet setCancelButtonIndex:[_audiotrackActionSheet numberOfButtons] - 1];
823
    [_audiotrackActionSheet showInView:self.audioSwitcherButton];
824 825 826 827
}

- (IBAction)switchSubtitleTrack:(id)sender
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
828
    NSArray *spuTracks = [_mediaPlayer videoSubTitlesNames];
829
    NSArray *spuTrackIndexes = [_mediaPlayer videoSubTitlesIndexes];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
830

831
    NSUInteger count = [spuTracks count];
832 833
    if (count <= 1)
        return;
834
    _subtitleActionSheet = [[UIActionSheet alloc] initWithTitle:NSLocalizedString(@"CHOOSE_SUBTITLE_TRACK", @"subtitle track selector") delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles: nil];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
835

836 837 838 839 840 841
    for (NSUInteger i = 0; i < count; i++) {
        NSString *indexIndicator = ([spuTrackIndexes[i] intValue] == [_mediaPlayer currentVideoSubTitleIndex])? @"\u2713": @"";
        NSString *buttonTitle = [NSString stringWithFormat:@"%@ %@", indexIndicator, spuTracks[i]];
        [_subtitleActionSheet addButtonWithTitle:buttonTitle];
    }

842
    [_subtitleActionSheet addButtonWithTitle:NSLocalizedString(@"BUTTON_CANCEL", @"cancel button")];
843
    [_subtitleActionSheet setCancelButtonIndex:[_subtitleActionSheet numberOfButtons] - 1];
844
    [_subtitleActionSheet showInView: self.subtitleSwitcherButton];
845 846 847
}

- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
848 849
    if (buttonIndex == [actionSheet cancelButtonIndex])
        return;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
850

Felix Paul Kühne's avatar
Felix Paul Kühne committed
851
    NSArray *indexArray;
852
    if (actionSheet == _subtitleActionSheet) {
853 854 855
        indexArray = _mediaPlayer.videoSubTitlesIndexes;
        if (buttonIndex <= indexArray.count) {
            _mediaPlayer.currentVideoSubTitleIndex = [indexArray[buttonIndex] intValue];
856
        }
857
    } else if (actionSheet == _audiotrackActionSheet) {
Felix Paul Kühne's avatar
Felix Paul Kühne committed
858
        indexArray = _mediaPlayer.audioTrackIndexes;
859 860
        if (buttonIndex <= indexArray.count) {
            _mediaPlayer.currentAudioTrackIndex = [indexArray[buttonIndex] intValue];
861
        }
862 863 864
    }
}

865
- (IBAction)toggleTimeDisplay:(id)sender
866 867
{
    _displayRemainingTime = !_displayRemainingTime;
868 869

    [self _resetIdleTimer];
870 871
}

Ahmad Harb's avatar
Ahmad Harb committed
872
#pragma mark - multi-touch gestures
873
- (CGSize)_screenSize
Ahmad Harb's avatar
Ahmad Harb committed
874 875 876 877 878 879
{
    UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
    CGRect screenRect = [[UIScreen mainScreen] bounds];

    CGFloat screenHeight, screenWidth = .0;

880
    if (orientation == UIDeviceOrientationPortrait) {
Ahmad Harb's avatar
Ahmad Harb committed
881 882
        screenHeight = screenRect.size.height;
        screenWidth = screenRect.size.width;
883
    } else {
Ahmad Harb's avatar
Ahmad Harb committed
884 885 886 887 888 889
        screenHeight = screenRect.size.width;
        screenWidth = screenRect.size.height;
    }

    return CGSizeMake(screenWidth, screenHeight);
}
890

891
- (void)tapRecognized
892
{
Ahmad Harb's avatar
Ahmad Harb committed
893 894
    if ([