VLCPlaybackController.m 36.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*****************************************************************************
 * VLCPlaybackController.m
 * VLC for iOS
 *****************************************************************************
 * Copyright (c) 2013-2015 VideoLAN. All rights reserved.
 * $Id$
 *
 * Authors: Felix Paul Kühne <fkuehne # videolan.org>
 *          Carola Nitz <caro # videolan.org>
 *          Gleb Pinigin <gpinigin # gmail.com>
 *          Pierre Sagaspe <pierre.sagaspe # me.com>
 *          Tobias Conradi <videolan # tobias-conradi.de>
 *          Sylver Bruneau <sylver.bruneau # gmail dot com>
 *          Winston Weinert <winston # ml1 dot net>
 *
 * Refer to the COPYING file of the official project for license.
 *****************************************************************************/

#import "VLCPlaybackController.h"
#import <CommonCrypto/CommonDigest.h>
#import "UIDevice+VLC.h"
#import <AVFoundation/AVFoundation.h>
23
#import "VLCPlayerDisplayController.h"
24
#import "VLCConstants.h"
25
#import "VLCRemoteControlService.h"
26
#import "VLCMetadata.h"
27

28 29 30 31 32 33
NSString *const VLCPlaybackControllerPlaybackDidStart = @"VLCPlaybackControllerPlaybackDidStart";
NSString *const VLCPlaybackControllerPlaybackDidPause = @"VLCPlaybackControllerPlaybackDidPause";
NSString *const VLCPlaybackControllerPlaybackDidResume = @"VLCPlaybackControllerPlaybackDidResume";
NSString *const VLCPlaybackControllerPlaybackDidStop = @"VLCPlaybackControllerPlaybackDidStop";
NSString *const VLCPlaybackControllerPlaybackMetadataDidChange = @"VLCPlaybackControllerPlaybackMetadataDidChange";
NSString *const VLCPlaybackControllerPlaybackDidFail = @"VLCPlaybackControllerPlaybackDidFail";
34
NSString *const VLCPlaybackControllerPlaybackPositionUpdated = @"VLCPlaybackControllerPlaybackPositionUpdated";
35

36 37 38 39 40 41 42 43
typedef NS_ENUM(NSUInteger, VLCAspectRatio) {
    VLCAspectRatioDefault = 0,
    VLCAspectRatioFillToScreen,
    VLCAspectRatioFourToThree,
    VLCAspectRatioSixteenToNine,
    VLCAspectRatioSixteenToTen,
};

44 45 46 47
@interface VLCPlaybackController () <VLCMediaPlayerDelegate,
#if TARGET_OS_IOS
AVAudioSessionDelegate,
#endif
48
VLCMediaDelegate, VLCRemoteControlServiceDelegate>
49
{
50
    VLCRemoteControlService *_remoteControlService;
51 52 53
    BOOL _playerIsSetup;
    BOOL _playbackFailed;
    BOOL _shouldResumePlaying;
54
    BOOL _shouldResumePlayingAfterInteruption;
55
    NSTimer *_sleepTimer;
56

57
    NSUInteger _currentAspectRatio;
58 59 60

    UIView *_videoOutputViewWrapper;
    UIView *_actualVideoOutputView;
61
    UIView *_preBackgroundWrapperView;
62

63
    BOOL _needsMetadataUpdate;
64
    BOOL _mediaWasJustStarted;
65
    BOOL _recheckForExistingThumbnail;
66
    BOOL _activeSession;
67
    BOOL _headphonesWasPlugged;
68 69

    NSLock *_playbackSessionManagementLock;
70 71

    VLCDialogProvider *_dialogProvider;
Soomin Lee's avatar
Soomin Lee committed
72 73

    NSMutableArray *_shuffleStack;
74 75 76 77 78 79 80 81 82 83 84 85 86 87
}

@end

@implementation VLCPlaybackController

#pragma mark instance management

+ (VLCPlaybackController *)sharedInstance
{
    static VLCPlaybackController *sharedInstance = nil;
    static dispatch_once_t pred;

    dispatch_once(&pred, ^{
88
        sharedInstance = [VLCPlaybackController new];
89 90 91 92 93 94 95
    });

    return sharedInstance;
}

- (void)dealloc
{
96
    _dialogProvider = nil;
97 98 99
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

Tobias's avatar
Tobias committed
100 101 102 103
- (instancetype)init
{
    self = [super init];
    if (self) {
104
        _headphonesWasPlugged = [self areHeadphonesPlugged];
Tobias's avatar
Tobias committed
105 106 107 108 109 110 111 112 113
        NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
        [defaultCenter addObserver:self selector:@selector(audioSessionRouteChange:)
                              name:AVAudioSessionRouteChangeNotification object:nil];
        [defaultCenter addObserver:self selector:@selector(applicationWillResignActive:)
                              name:UIApplicationWillResignActiveNotification object:nil];
        [defaultCenter addObserver:self selector:@selector(applicationDidBecomeActive:)
                              name:UIApplicationDidBecomeActiveNotification object:nil];
        [defaultCenter addObserver:self selector:@selector(applicationDidEnterBackground:)
                              name:UIApplicationDidEnterBackgroundNotification object:nil];
114

115
        _metadata = [VLCMetaData new];
116 117
        _dialogProvider = [[VLCDialogProvider alloc] initWithLibrary:[VLCLibrary sharedLibrary] customUI:NO];

118
        _playbackSessionManagementLock = [[NSLock alloc] init];
Soomin Lee's avatar
Soomin Lee committed
119 120
        _shuffleMode = NO;
        _shuffleStack = [[NSMutableArray alloc] init];
Tobias's avatar
Tobias committed
121 122 123 124
    }
    return self;
}

125 126 127 128 129 130 131 132
- (VLCRemoteControlService *)remoteControlService
{
    if (!_remoteControlService) {
        _remoteControlService = [[VLCRemoteControlService alloc] init];
        _remoteControlService.remoteControlServiceDelegate = self;
    }
    return _remoteControlService;
}
133 134
#pragma mark - playback management

135
- (void)playMediaList:(VLCMediaList *)mediaList firstIndex:(NSInteger)index
136 137
{
    self.mediaList = mediaList;
138
    self.itemInMediaListToBePlayedFirst = (int)index;
139 140 141 142
    self.pathToExternalSubtitlesFile = nil;

    if (self.activePlaybackSession) {
        self.sessionWillRestart = YES;
143
        [self stopPlayback];
144 145
    } else {
        self.sessionWillRestart = NO;
146
        [self startPlayback];
147 148 149
    }
}

150 151 152 153 154 155 156 157
- (void)playURL:(NSURL *)url successCallback:(NSURL*)successCallback errorCallback:(NSURL *)errorCallback
{
    self.url = url;
    self.successCallback = successCallback;
    self.errorCallback = errorCallback;

    if (self.activePlaybackSession) {
        self.sessionWillRestart = YES;
158
        [self stopPlayback];
159 160
    } else {
        self.sessionWillRestart = NO;
161
        [self startPlayback];
162 163 164 165 166 167 168 169 170 171
    }
}

- (void)playURL:(NSURL *)url subtitlesFilePath:(NSString *)subsFilePath
{
    self.url = url;
    self.pathToExternalSubtitlesFile = subsFilePath;

    if (self.activePlaybackSession) {
        self.sessionWillRestart = YES;
172
        [self stopPlayback];
173 174
    } else {
        self.sessionWillRestart = NO;
Carola Nitz's avatar
Carola Nitz committed
175 176 177
        dispatch_async(dispatch_get_main_queue(), ^{
            [self startPlayback];
        });
178 179 180
    }
}

181 182
- (void)startPlayback
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
183 184
    if (_playerIsSetup) {
        APLog(@"%s: player is already setup, bailing out", __PRETTY_FUNCTION__);
185
        return;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
186
    }
187 188 189 190 191 192 193

    BOOL ret = [_playbackSessionManagementLock tryLock];
    if (!ret) {
        APLog(@"%s: locking failed", __PRETTY_FUNCTION__);
        return;
    }

194
    _activeSession = YES;
195

Felix Paul Kühne's avatar
Felix Paul Kühne committed
196
#if TARGET_OS_IOS
197
    [[AVAudioSession sharedInstance] setDelegate:self];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
198
#endif
199 200 201

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

202
    if (!self.url && !self.mediaList) {
Felix Paul Kühne's avatar
Felix Paul Kühne committed
203
        APLog(@"%s: no URL and no media list set, stopping playback", __PRETTY_FUNCTION__);
204
        [_playbackSessionManagementLock unlock];
205 206 207
        [self stopPlayback];
        return;
    }
208

209 210 211 212 213 214 215
    /* video decoding permanently fails if we don't provide a UIView to draw into on init
     * hence we provide one which is not attached to any view controller for off-screen drawing
     * and disable video decoding once playback started */
    _actualVideoOutputView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    _actualVideoOutputView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    _actualVideoOutputView.autoresizesSubviews = YES;

216 217 218 219 220 221 222 223 224 225
    if (self.pathToExternalSubtitlesFile)
        _listPlayer = [[VLCMediaListPlayer alloc] initWithOptions:@[[NSString stringWithFormat:@"--%@=%@", kVLCSettingSubtitlesFilePath, self.pathToExternalSubtitlesFile]] andDrawable:_actualVideoOutputView];
    else
        _listPlayer = [[VLCMediaListPlayer alloc] initWithDrawable:_actualVideoOutputView];

    /* to enable debug logging for the playback library instance, switch the boolean below
     * note that the library instance used for playback may not necessarily match the instance
     * used for media discovery or thumbnailing */
    _listPlayer.mediaPlayer.libraryInstance.debugLogging = NO;

226 227 228 229 230 231 232 233 234
    _mediaPlayer = _listPlayer.mediaPlayer;
    [_mediaPlayer setDelegate:self];
    if ([[defaults objectForKey:kVLCSettingPlaybackSpeedDefaultValue] floatValue] != 0)
        [_mediaPlayer setRate: [[defaults objectForKey:kVLCSettingPlaybackSpeedDefaultValue] floatValue]];
    if ([[defaults objectForKey:kVLCSettingDeinterlace] intValue] != 0)
        [_mediaPlayer setDeinterlaceFilter:@"blend"];
    else
        [_mediaPlayer setDeinterlaceFilter:nil];
    if (self.pathToExternalSubtitlesFile)
235
        [_mediaPlayer addPlaybackSlave:[NSURL fileURLWithPath:self.pathToExternalSubtitlesFile] type:VLCMediaPlaybackSlaveTypeSubtitle enforce:YES];
236 237

    VLCMedia *media;
238 239
    if (_mediaList) {
        media = [_mediaList mediaAtIndex:_itemInMediaListToBePlayedFirst];
240
        [media parseWithOptions:VLCMediaParseLocal];
241 242 243 244
        media.delegate = self;
    } else {
        media = [VLCMedia mediaWithURL:self.url];
        media.delegate = self;
245
        [media parseWithOptions:VLCMediaParseLocal];
246
        [media addOptions:self.mediaOptionsDictionary];
247
    }
248

249 250 251 252 253 254 255
    if (self.mediaList) {
        [_listPlayer setMediaList:self.mediaList];
    } else {
        [_listPlayer setRootMedia:media];
    }
    [_listPlayer setRepeatMode:VLCDoNotRepeat];

256 257
    [_playbackSessionManagementLock unlock];

258
    [self _playNewMedia];
259 260 261 262
}

- (void)_playNewMedia
{
263 264
    BOOL ret = [_playbackSessionManagementLock tryLock];
    if (!ret) {
Felix Paul Kühne's avatar
Felix Paul Kühne committed
265
        APLog(@"%s: locking failed", __PRETTY_FUNCTION__);
266 267 268
        return;
    }

269 270 271 272 273
    // Set last selected equalizer profile
    unsigned int profile = (unsigned int)[[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingEqualizerProfile] integerValue];
    [_mediaPlayer resetEqualizerFromProfile:profile];
    [_mediaPlayer setPreAmplification:[_mediaPlayer preAmplification]];

274 275
    _mediaWasJustStarted = YES;

276 277 278 279
    [_mediaPlayer addObserver:self forKeyPath:@"time" options:0 context:nil];
    [_mediaPlayer addObserver:self forKeyPath:@"remainingTime" options:0 context:nil];

    if (self.mediaList)
280
        [_listPlayer playItemAtNumber:@(self.itemInMediaListToBePlayedFirst)];
281 282 283 284 285 286
    else
        [_listPlayer playMedia:_listPlayer.rootMedia];

    if ([self.delegate respondsToSelector:@selector(prepareForMediaPlayback:)])
        [self.delegate prepareForMediaPlayback:self];

287
    _currentAspectRatio = VLCAspectRatioDefault;
288
    _mediaPlayer.videoAspectRatio = NULL;
289
    _mediaPlayer.scaleFactor = 0;
290

291
    [[self remoteControlService] subscribeToRemoteCommands];
292

293
    _playerIsSetup = YES;
294 295

    [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackDidStart object:self];
296
    [_playbackSessionManagementLock unlock];
297 298 299 300
}

- (void)stopPlayback
{
301 302 303 304 305 306
    BOOL ret = [_playbackSessionManagementLock tryLock];
    if (!ret) {
        APLog(@"%s: locking failed", __PRETTY_FUNCTION__);
        return;
    }

307 308 309 310 311 312 313 314 315 316 317
    if (_mediaPlayer) {
        @try {
            [_mediaPlayer removeObserver:self forKeyPath:@"time"];
            [_mediaPlayer removeObserver:self forKeyPath:@"remainingTime"];
        }
        @catch (NSException *exception) {
            APLog(@"we weren't an observer yet");
        }

        if (_mediaPlayer.media) {
            [_mediaPlayer pause];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
318
#if TARGET_OS_IOS
319
            [self _savePlaybackState];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
320
#endif
321 322 323 324 325 326 327
            [_mediaPlayer stop];
        }
        if (_mediaPlayer)
            _mediaPlayer = nil;
        if (_listPlayer)
            _listPlayer = nil;
    }
328 329 330 331 332 333 334 335 336 337 338
    if (!_sessionWillRestart) {
        if (_mediaList)
            _mediaList = nil;
        if (_url)
            _url = nil;
        if (_pathToExternalSubtitlesFile) {
            NSFileManager *fileManager = [NSFileManager defaultManager];
            if ([fileManager fileExistsAtPath:_pathToExternalSubtitlesFile])
                [fileManager removeItemAtPath:_pathToExternalSubtitlesFile error:nil];
            _pathToExternalSubtitlesFile = nil;
        }
339 340
    }
    _playerIsSetup = NO;
Soomin Lee's avatar
Soomin Lee committed
341
    [_shuffleStack removeAllObjects];
342

343
    if (self.errorCallback && _playbackFailed && !_sessionWillRestart)
344
        [[UIApplication sharedApplication] openURL:self.errorCallback];
345
    else if (self.successCallback && !_sessionWillRestart)
346 347
        [[UIApplication sharedApplication] openURL:self.successCallback];

348
    [[self remoteControlService] unsubscribeFromRemoteCommands];
349
    _activeSession = NO;
350

351
    [_playbackSessionManagementLock unlock];
352 353
    if (_playbackFailed) {
        [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackDidFail object:self];
354
    } else if (!_sessionWillRestart) {
355
        [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackDidStop object:self];
356 357
    } else {
        self.sessionWillRestart = NO;
358
        [self startPlayback];
359
    }
360 361
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
362
#if TARGET_OS_IOS
363 364
- (void)_savePlaybackState
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
365 366 367 368 369 370 371
    @try {
        [[MLMediaLibrary sharedMediaLibrary] save];
    }
    @catch (NSException *exception) {
        APLog(@"saving playback state failed");
    }

372 373 374 375
    MLFile *fileItem;
    NSArray *files = [MLFile fileForURL:_mediaPlayer.media.url];
    if (files.count > 0)
        fileItem = files.firstObject;
376

377 378
    if (!fileItem) {
        APLog(@"couldn't find file, not saving playback progress");
379
        return;
380
    }
381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407

    @try {
        float position = _mediaPlayer.position;
        fileItem.lastPosition = @(position);
        fileItem.lastAudioTrack = @(_mediaPlayer.currentAudioTrackIndex);
        fileItem.lastSubtitleTrack = @(_mediaPlayer.currentVideoSubTitleIndex);

        if (position > .95)
            return;

        NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
        NSString* newThumbnailPath = [searchPaths[0] stringByAppendingPathComponent:@"VideoSnapshots"];
        NSFileManager *fileManager = [NSFileManager defaultManager];

        if (![fileManager fileExistsAtPath:newThumbnailPath])
            [fileManager createDirectoryAtPath:newThumbnailPath withIntermediateDirectories:YES attributes:nil error:nil];

        newThumbnailPath = [newThumbnailPath stringByAppendingPathComponent:fileItem.objectID.URIRepresentation.lastPathComponent];
        [_mediaPlayer saveVideoSnapshotAt:newThumbnailPath withWidth:0 andHeight:0];

        _recheckForExistingThumbnail = YES;
        [self performSelector:@selector(_updateStoredThumbnailForFile:) withObject:fileItem afterDelay:.25];
    }
    @catch (NSException *exception) {
        APLog(@"failed to save current media state - file removed?");
    }
}
Felix Paul Kühne's avatar
Felix Paul Kühne committed
408
#endif
409

Felix Paul Kühne's avatar
Felix Paul Kühne committed
410
#if TARGET_OS_IOS
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
- (void)_updateStoredThumbnailForFile:(MLFile *)fileItem
{
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString* newThumbnailPath = [searchPaths[0] stringByAppendingPathComponent:@"VideoSnapshots"];
    newThumbnailPath = [newThumbnailPath stringByAppendingPathComponent:fileItem.objectID.URIRepresentation.lastPathComponent];

    if (![fileManager fileExistsAtPath:newThumbnailPath]) {
        if (_recheckForExistingThumbnail) {
            [self performSelector:@selector(_updateStoredThumbnailForFile:) withObject:fileItem afterDelay:1.];
            _recheckForExistingThumbnail = NO;
        } else
            return;
    }

    UIImage *newThumbnail = [UIImage imageWithContentsOfFile:newThumbnailPath];
    if (!newThumbnail) {
        if (_recheckForExistingThumbnail) {
            [self performSelector:@selector(_updateStoredThumbnailForFile:) withObject:fileItem afterDelay:1.];
            _recheckForExistingThumbnail = NO;
        } else
            return;
    }

    @try {
        [fileItem setComputedThumbnailScaledForDevice:newThumbnail];
    }
    @catch (NSException *exception) {
        APLog(@"updating thumbnail failed");
    }

    [fileManager removeItemAtPath:newThumbnailPath error:nil];
443
}
Felix Paul Kühne's avatar
Felix Paul Kühne committed
444
#endif
445 446 447

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
448 449
    if (_mediaWasJustStarted) {
        _mediaWasJustStarted = NO;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
450
#if TARGET_OS_IOS
451 452 453 454 455 456
        if (self.mediaList) {
            MLFile *item;
            NSArray *matches = [MLFile fileForURL:_mediaPlayer.media.url];
            item = matches.firstObject;
            [self _recoverLastPlaybackStateOfItem:item];
        }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
457 458 459
#else
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
        BOOL bValue = [defaults boolForKey:kVLCSettingUseSPDIF];
460

Felix Paul Kühne's avatar
Felix Paul Kühne committed
461
        if (bValue) {
462
           _mediaPlayer.audio.passthrough = bValue;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
463
        }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
464
#endif
465 466
    }

467 468
    if ([self.delegate respondsToSelector:@selector(playbackPositionUpdated:)])
        [self.delegate playbackPositionUpdated:self];
469 470 471

    [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackPositionUpdated
                                                        object:self];
472 473 474 475
}

- (NSInteger)mediaDuration
{
476
    return _mediaPlayer.media.length.intValue;;
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
}

- (BOOL)isPlaying
{
    return _mediaPlayer.isPlaying;
}

- (VLCRepeatMode)repeatMode
{
    return _listPlayer.repeatMode;
}

- (void)setRepeatMode:(VLCRepeatMode)repeatMode
{
    _listPlayer.repeatMode = repeatMode;
}

- (BOOL)currentMediaHasChapters
{
496
    return [_mediaPlayer numberOfTitles] > 1 || [_mediaPlayer numberOfChaptersForTitle:_mediaPlayer.currentTitleIndex] > 1;
497 498 499 500 501 502 503
}

- (BOOL)currentMediaHasTrackToChooseFrom
{
    return [[_mediaPlayer audioTrackIndexes] count] > 2 || [[_mediaPlayer videoSubTitlesIndexes] count] > 1;
}

504 505
- (BOOL)activePlaybackSession
{
506
    return _activeSession;
507 508
}

509 510
- (float)playbackRate
{
511
    return _mediaPlayer.rate;
512 513 514 515
}

- (void)setPlaybackRate:(float)playbackRate
{
516 517
    [_mediaPlayer setRate:playbackRate];
    _metadata.playbackRate = @(_mediaPlayer.rate);
518 519
}

520 521 522 523
- (void)setAudioDelay:(float)audioDelay
{
    _mediaPlayer.currentAudioPlaybackDelay = 1000000.*audioDelay;
}
524

525 526 527 528
- (float)audioDelay
{
    return _mediaPlayer.currentAudioPlaybackDelay/1000000.;
}
529

530 531 532 533 534 535 536 537 538
-(void)setSubtitleDelay:(float)subtitleDeleay
{
    _mediaPlayer.currentVideoSubTitleDelay = 1000000.*subtitleDeleay;
}
- (float)subtitleDelay
{
    return _mediaPlayer.currentVideoSubTitleDelay/1000000.;
}

539 540 541
- (void)mediaPlayerStateChanged:(NSNotification *)aNotification
{
    VLCMediaPlayerState currentState = _mediaPlayer.state;
542

543 544 545 546 547
    if (currentState == VLCMediaPlayerStateBuffering) {
        /* attach delegate */
        _mediaPlayer.media.delegate = self;

        /* on-the-fly values through hidden API */
548 549 550 551 552
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
        [_mediaPlayer performSelector:@selector(setTextRendererFont:) withObject:[defaults objectForKey:kVLCSettingSubtitlesFont]];
        [_mediaPlayer performSelector:@selector(setTextRendererFontSize:) withObject:[defaults objectForKey:kVLCSettingSubtitlesFontSize]];
        [_mediaPlayer performSelector:@selector(setTextRendererFontColor:) withObject:[defaults objectForKey:kVLCSettingSubtitlesFontColor]];
        [_mediaPlayer performSelector:@selector(setTextRendererFontForceBold:) withObject:[defaults objectForKey:kVLCSettingSubtitlesBoldFont]];
553
    } else if (currentState == VLCMediaPlayerStateError) {
554
        APLog(@"Playback failed");
555
        _playbackFailed = YES;
556
        self.sessionWillRestart = NO;
557
        [self stopPlayback];
558
    } else if (currentState == VLCMediaPlayerStateEnded || currentState == VLCMediaPlayerStateStopped) {
559 560
        [_listPlayer.mediaList lock];
        NSUInteger listCount = _listPlayer.mediaList.count;
561
        if ([_listPlayer.mediaList indexOfMedia:_mediaPlayer.media] == listCount - 1 && self.repeatMode == VLCDoNotRepeat) {
562
            [_listPlayer.mediaList unlock];
563
            self.sessionWillRestart = NO;
564 565
            [self stopPlayback];
            return;
566 567 568
        } else if (listCount > 1) {
            [_listPlayer.mediaList unlock];
            [_listPlayer next];
569 570
        } else
            [_listPlayer.mediaList unlock];
571 572
    }

573 574 575 576 577 578
    if ([self.delegate respondsToSelector:@selector(mediaPlayerStateChanged:isPlaying:currentMediaHasTrackToChooseFrom:currentMediaHasChapters:forPlaybackController:)])
        [self.delegate mediaPlayerStateChanged:currentState
                                     isPlaying:_mediaPlayer.isPlaying
              currentMediaHasTrackToChooseFrom:self.currentMediaHasTrackToChooseFrom
                       currentMediaHasChapters:self.currentMediaHasChapters
                         forPlaybackController:self];
579 580

    [self setNeedsMetadataUpdate];
581 582 583 584 585
}

#pragma mark - playback controls
- (void)playPause
{
586
    if ([_mediaPlayer isPlaying]) {
587
        [_listPlayer pause];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
588
#if TARGET_OS_IOS
589
        [self _savePlaybackState];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
590
#endif
591 592
        [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackDidPause object:self];
    } else {
593
        [_listPlayer play];
594 595
        [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackDidResume object:self];
    }
596 597 598 599
}

- (void)forward
{
Soomin Lee's avatar
Soomin Lee committed
600 601
    NSInteger mediaListCount = _mediaList.count;

Soomin Lee's avatar
Soomin Lee committed
602
#if TARGET_OS_IOS
Soomin Lee's avatar
Soomin Lee committed
603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624
    if (mediaListCount > 2 && _shuffleMode) {

        NSNumber *nextIndex;
        NSUInteger currentIndex = [_mediaList indexOfMedia:_listPlayer.mediaPlayer.media];

        //Reached end of playlist
        if (_shuffleStack.count + 1 == mediaListCount) {
            if ([self repeatMode] == VLCDoNotRepeat)
                return;
            [_shuffleStack removeAllObjects];
        }

        [_shuffleStack addObject:[NSNumber numberWithUnsignedInteger:currentIndex]];
        do {
            nextIndex = [NSNumber numberWithUnsignedInt:arc4random_uniform((uint32_t)mediaListCount)];
        } while (currentIndex == nextIndex.unsignedIntegerValue || [_shuffleStack containsObject:nextIndex]);

        [_listPlayer playItemAtNumber:[NSNumber numberWithUnsignedInteger:nextIndex.unsignedIntegerValue]];
        [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackMetadataDidChange object:self];

        return;
    }
Soomin Lee's avatar
Soomin Lee committed
625
#endif
Soomin Lee's avatar
Soomin Lee committed
626 627

    if (mediaListCount > 1) {
628
        [_listPlayer next];
629
        [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackMetadataDidChange object:self];
630 631 632 633 634 635 636 637
    } else {
        NSNumber *skipLength = [[NSUserDefaults standardUserDefaults] valueForKey:kVLCSettingPlaybackForwardSkipLength];
        [_mediaPlayer jumpForward:skipLength.intValue];
    }
}

- (void)backward
{
638
    if (_mediaList.count > 1) {
639
        [_listPlayer previous];
640
        [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackMetadataDidChange object:self];
641 642 643 644 645 646 647 648 649
    }
    else {
        NSNumber *skipLength = [[NSUserDefaults standardUserDefaults] valueForKey:kVLCSettingPlaybackBackwardSkipLength];
        [_mediaPlayer jumpBackward:skipLength.intValue];
    }
}

- (void)switchAspectRatio
{
650
    if (_currentAspectRatio == VLCAspectRatioSixteenToTen) {
651
        _mediaPlayer.videoAspectRatio = NULL;
652 653
        _mediaPlayer.scaleFactor = 0;
        _currentAspectRatio = VLCAspectRatioDefault;
654
    } else {
655
        _currentAspectRatio++;
656

657
        if (_currentAspectRatio == VLCAspectRatioFillToScreen) {
658
            UIScreen *screen;
659
            if (![[UIDevice currentDevice] VLCHasExternalDisplay])
660 661 662 663 664 665
                screen = [UIScreen mainScreen];
            else
                screen = [UIScreen screens][1];

            float f_ar = screen.bounds.size.width / screen.bounds.size.height;

Felix Paul Kühne's avatar
Felix Paul Kühne committed
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681
            if (f_ar == (float)(4.0/3.0) ||
                f_ar == (float)(1366./1024.)) {
                // all iPads
                _mediaPlayer.videoCropGeometry = "4:3";
            } else if (f_ar == (float)(2./3.) || f_ar == (float)(480./320.)) {
                // all other iPhones
                _mediaPlayer.videoCropGeometry = "16:10"; // libvlc doesn't support 2:3 crop
            } else if (f_ar == .5625) {
                // AirPlay
                _mediaPlayer.videoCropGeometry = "16:9";
            } else if (f_ar == (float)(640./1136.) ||
                       f_ar == (float)(568./320.) ||
                       f_ar == (float)(667./375.) ||
                       f_ar == (float)(736./414.)) {
                // iPhone 5 and 6 and 6+
                _mediaPlayer.videoCropGeometry = "16:9";
682
            } else
683
                APLog(@"unknown screen format %f, can't crop", f_ar);
684 685
        } else {
            _mediaPlayer.videoAspectRatio = (char *)[[self stringForAspectRatio:_currentAspectRatio] UTF8String];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
686
            _mediaPlayer.videoCropGeometry = NULL;
687
        }
688
    }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
689

690 691 692 693
    if ([self.delegate respondsToSelector:@selector(showStatusMessage:forPlaybackController:)]) {
        [self.delegate showStatusMessage:[NSString stringWithFormat:NSLocalizedString(@"AR_CHANGED", nil), [self stringForAspectRatio:_currentAspectRatio]] forPlaybackController:self];
    }
}
694

695 696 697 698 699 700 701 702
- (NSString *)stringForAspectRatio:(VLCAspectRatio)ratio
{
    switch (ratio) {
            case VLCAspectRatioFillToScreen:
            return NSLocalizedString(@"FILL_TO_SCREEN", nil);
            case VLCAspectRatioDefault:
            return NSLocalizedString(@"DEFAULT", nil);
            case VLCAspectRatioFourToThree:
703
            return @"4:3";
704
            case VLCAspectRatioSixteenToTen:
705
            return @"16:10";
706
            case VLCAspectRatioSixteenToNine:
707
            return @"16:9";
708 709
        default:
            NSAssert(NO, @"this shouldn't happen");
710 711 712
    }
}

713 714 715 716 717 718 719 720 721 722 723 724 725 726
- (void)setVideoTrackEnabled:(BOOL)enabled
{
    if (!enabled)
        _mediaPlayer.currentVideoTrackIndex = -1;
    else if (_mediaPlayer.currentVideoTrackIndex == -1) {
        for (NSNumber *trackId in _mediaPlayer.videoTrackIndexes) {
            if ([trackId intValue] != -1) {
                _mediaPlayer.currentVideoTrackIndex = [trackId intValue];
                break;
            }
        }
    }
}

727 728 729
- (void)setVideoOutputView:(UIView *)videoOutputView
{
    if (videoOutputView) {
730 731 732
        if ([_actualVideoOutputView superview] != nil)
            [_actualVideoOutputView removeFromSuperview];

733 734
        _actualVideoOutputView.frame = (CGRect){CGPointZero, videoOutputView.frame.size};

735
        [self setVideoTrackEnabled:true];
736

737
        [videoOutputView addSubview:_actualVideoOutputView];
738 739 740
        [_actualVideoOutputView layoutSubviews];
        [_actualVideoOutputView updateConstraints];
        [_actualVideoOutputView setNeedsLayout];
741
    } else
742 743 744 745 746 747 748 749 750 751
        [_actualVideoOutputView removeFromSuperview];

    _videoOutputViewWrapper = videoOutputView;
}

- (UIView *)videoOutputView
{
    return _videoOutputViewWrapper;
}

752
#pragma mark - 360 Support
Soomin Lee's avatar
Soomin Lee committed
753
#if !TARGET_OS_TV
754 755 756 757 758 759 760 761 762 763 764 765 766
- (BOOL)updateViewpoint:(CGFloat)yaw pitch:(CGFloat)pitch roll:(CGFloat)roll fov:(CGFloat)fov absolute:(BOOL)absolute
{
    return [_mediaPlayer updateViewpoint:yaw pitch:pitch roll:roll fov:fov absolute:absolute];
}

- (NSInteger)currentMediaProjection
{
    VLCMedia *media = [_mediaPlayer media];
    NSInteger currentVideoTrackIndex = [_mediaPlayer currentVideoTrackIndex];

    if (media && currentVideoTrackIndex >= 0) {
        NSArray *tracksInfo = media.tracksInformation;

767 768 769 770
        for (NSDictionary *track in tracksInfo) {
            if ([track[VLCMediaTracksInformationType] isEqualToString:VLCMediaTracksInformationTypeVideo]) {
                return [track[VLCMediaTracksInformationVideoProjection] integerValue];
            }
771 772 773 774
        }
    }
    return -1;
}
Soomin Lee's avatar
Soomin Lee committed
775
#endif
776

777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823
#pragma mark - equalizer

- (void)setAmplification:(CGFloat)amplification forBand:(unsigned int)index
{
    if (!_mediaPlayer.equalizerEnabled)
        [_mediaPlayer setEqualizerEnabled:YES];

    [_mediaPlayer setAmplification:amplification forBand:index];

    // For some reason we have to apply again preamp to apply change
    [_mediaPlayer setPreAmplification:[_mediaPlayer preAmplification]];
}

- (CGFloat)amplificationOfBand:(unsigned int)index
{
    return [_mediaPlayer amplificationOfBand:index];
}

- (NSArray *)equalizerProfiles
{
    return _mediaPlayer.equalizerProfiles;
}

- (void)resetEqualizerFromProfile:(unsigned int)profile
{
    [[NSUserDefaults standardUserDefaults] setObject:@(profile) forKey:kVLCSettingEqualizerProfile];
    [_mediaPlayer resetEqualizerFromProfile:profile];
}

- (void)setPreAmplification:(CGFloat)preAmplification
{
    if (!_mediaPlayer.equalizerEnabled)
        [_mediaPlayer setEqualizerEnabled:YES];

    [_mediaPlayer setPreAmplification:preAmplification];
}

- (CGFloat)preAmplification
{
    return [_mediaPlayer preAmplification];
}

#pragma mark - AVSession delegate
- (void)beginInterruption
{
    if ([_mediaPlayer isPlaying]) {
        [_mediaPlayer pause];
824
        _shouldResumePlayingAfterInteruption = YES;
825 826 827 828 829
    }
}

- (void)endInterruption
{
830
    if (_shouldResumePlayingAfterInteruption) {
831
        [_mediaPlayer play];
832
        _shouldResumePlayingAfterInteruption = NO;
833 834 835
    }
}

836
- (BOOL)areHeadphonesPlugged
837 838
{
    NSArray *outputs = [[AVAudioSession sharedInstance] currentRoute].outputs;
839
    NSString *portName = [[outputs firstObject] portName];
840 841 842 843 844
    return [portName isEqualToString:@"Headphones"];
}

- (void)audioSessionRouteChange:(NSNotification *)notification
{
845 846 847 848 849 850
    NSDictionary *userInfo = notification.userInfo;
    NSInteger routeChangeReason = [[userInfo valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];

    if (routeChangeReason == AVAudioSessionRouteChangeReasonRouteConfigurationChange)
        return;

851
    BOOL headphonesPlugged = [self areHeadphonesPlugged];
852

853
    if (_headphonesWasPlugged && !headphonesPlugged && [_mediaPlayer isPlaying]) {
854
        [_mediaPlayer pause];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
855
#if TARGET_OS_IOS
856
        [self _savePlaybackState];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
857
#endif
858 859
        [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackDidPause object:self];
    }
860
    _headphonesWasPlugged = headphonesPlugged;
861 862 863 864
}

#pragma mark - Managing the media item

Felix Paul Kühne's avatar
Felix Paul Kühne committed
865
#if TARGET_OS_IOS
866
- (MLFile *)currentlyPlayingMediaFile {
867
    if (self.mediaList) {
868
        NSArray *results = [MLFile fileForURL:_mediaPlayer.media.url];
869
        return results.firstObject;
870 871 872 873
    }

    return nil;
}
Felix Paul Kühne's avatar
Felix Paul Kühne committed
874
#endif
875 876 877 878

#pragma mark - metadata handling
- (void)mediaDidFinishParsing:(VLCMedia *)aMedia
{
879
    [self setNeedsMetadataUpdate];
880 881 882 883
}

- (void)mediaMetaDataDidChange:(VLCMedia*)aMedia
{
884 885 886 887 888 889 890
    [self setNeedsMetadataUpdate];
}

- (void)setNeedsMetadataUpdate
{
    if (_needsMetadataUpdate == NO) {
        _needsMetadataUpdate = YES;
891
        dispatch_async(dispatch_get_main_queue(), ^{
892 893 894 895
            [_metadata updateMetadataFromMediaPlayer:_mediaPlayer];
            _needsMetadataUpdate = NO;
            if ([self.delegate respondsToSelector:@selector(displayMetadataForPlaybackController:metadata:)])
                [self.delegate displayMetadataForPlaybackController:self metadata:_metadata];
896 897
        });
    }
898 899
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
900
#if TARGET_OS_IOS
901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919
- (void)_recoverLastPlaybackStateOfItem:(MLFile *)item
{
    if (item) {
        if (_mediaPlayer.numberOfAudioTracks > 2) {
            if (item.lastAudioTrack.intValue > 0)
                _mediaPlayer.currentAudioTrackIndex = item.lastAudioTrack.intValue;
        }
        if (_mediaPlayer.numberOfSubtitlesTracks > 2) {
            if (item.lastSubtitleTrack.intValue > 0)
                _mediaPlayer.currentVideoSubTitleIndex = item.lastSubtitleTrack.intValue;
        }

        CGFloat lastPosition = .0;
        NSInteger duration = 0;

        if (item.lastPosition)
            lastPosition = item.lastPosition.floatValue;
        duration = item.duration.intValue;

920 921
        if (lastPosition < .95 && _mediaPlayer.position < lastPosition && (duration * lastPosition - duration) < -50000) {
            NSInteger continuePlayback;
922
            if ([item isAlbumTrack] || [item isSupportedAudioFile])
923 924 925 926
                continuePlayback = [[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingContinueAudioPlayback] integerValue];
            else
                continuePlayback = [[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingContinuePlayback] integerValue];

927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942
            if (continuePlayback == 1) {
                _mediaPlayer.position = lastPosition;
            } else if (continuePlayback == 0) {
                VLCAlertView *alert = [[VLCAlertView alloc] initWithTitle:NSLocalizedString(@"CONTINUE_PLAYBACK", nil)
                                                                  message:[NSString stringWithFormat:NSLocalizedString(@"CONTINUE_PLAYBACK_LONG", nil), item.title]
                                                                 delegate:self
                                                        cancelButtonTitle:NSLocalizedString(@"BUTTON_CANCEL", nil)
                                                        otherButtonTitles:NSLocalizedString(@"BUTTON_CONTINUE", nil), nil];
                alert.completion = ^(BOOL cancelled, NSInteger buttonIndex) {
                    if (!cancelled) {
                        _mediaPlayer.position = lastPosition;
                    }
                };
                [alert show];
            }
        }
943 944
    }
}
Felix Paul Kühne's avatar
Felix Paul Kühne committed
945
#endif
946

947 948
- (void)recoverDisplayedMetadata
{
949 950
    if ([self.delegate respondsToSelector:@selector(displayMetadataForPlaybackController:metadata:)])
        [self.delegate displayMetadataForPlaybackController:self metadata:_metadata];
951 952
}

953 954 955 956 957 958 959 960
- (void)recoverPlaybackState
{
    if ([self.delegate respondsToSelector:@selector(mediaPlayerStateChanged:isPlaying:currentMediaHasTrackToChooseFrom:currentMediaHasChapters:forPlaybackController:)])
        [self.delegate mediaPlayerStateChanged:_mediaPlayer.state
                                     isPlaying:self.isPlaying
              currentMediaHasTrackToChooseFrom:self.currentMediaHasTrackToChooseFrom
                       currentMediaHasChapters:self.currentMediaHasChapters
                         forPlaybackController:self];
961 962
    if ([self.delegate respondsToSelector:@selector(prepareForMediaPlayback:)])
        [self.delegate prepareForMediaPlayback:self];
963 964
}

965 966 967 968 969 970 971 972 973
- (void)scheduleSleepTimerWithInterval:(NSTimeInterval)timeInterval
{
    if (_sleepTimer) {
        [_sleepTimer invalidate];
        _sleepTimer = nil;
    }
    _sleepTimer = [NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(stopPlayback) userInfo:nil repeats:NO];
}

974 975 976 977
#pragma mark - background interaction

- (void)applicationWillResignActive:(NSNotification *)aNotification
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
978
#if TARGET_OS_IOS
979
    [self _savePlaybackState];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
980
#endif
981 982 983 984 985 986 987 988 989 990 991

    if (![[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingContinueAudioInBackgroundKey] boolValue]) {
        if ([_mediaPlayer isPlaying]) {
            [_mediaPlayer pause];
            _shouldResumePlaying = YES;
        }
    }
}

- (void)applicationDidEnterBackground:(NSNotification *)notification
{
992 993 994
    _preBackgroundWrapperView = _videoOutputViewWrapper;

    if (_mediaPlayer.audioTrackIndexes.count > 0)
995
        [self setVideoTrackEnabled:false];
996 997 998 999
}

- (void)applicationDidBecomeActive:(NSNotification *)notification
{
1000 1001 1002 1003
    if (_preBackgroundWrapperView) {
        [self setVideoOutputView:_preBackgroundWrapperView];
        _preBackgroundWrapperView = nil;
    }
1004

1005
    [self setVideoTrackEnabled:true];
1006 1007 1008 1009 1010 1011

    if (_shouldResumePlaying) {
        _shouldResumePlaying = NO;
        [_listPlayer play];
    }
}
1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027
#pragma mark - remoteControlDelegate

- (void)remoteControlServiceHitPause:(VLCRemoteControlService *)rcs
{
    [_listPlayer pause];
}

- (void)remoteControlServiceHitPlay:(VLCRemoteControlService *)rcs
{
    [_listPlayer play];
}

- (void)remoteControlServiceTogglePlayPause:(VLCRemoteControlService *)rcs
{
    [self playPause];
}
1028

1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065
- (void)remoteControlServiceHitStop:(VLCRemoteControlService *)rcs
{
    //TODO handle stop playback entirely
    [_listPlayer stop];
}

- (BOOL)remoteControlServiceHitPlayNextIfPossible:(VLCRemoteControlService *)rcs
{
    //TODO This doesn't handle shuffle or repeat yet
    return [_listPlayer next];
}

- (BOOL)remoteControlServiceHitPlayPreviousIfPossible:(VLCRemoteControlService *)rcs
{
    //TODO This doesn't handle shuffle or repeat yet
    return [_listPlayer previous];
}

- (void)remoteControlService:(VLCRemoteControlService *)rcs jumpForwardInSeconds:(NSTimeInterval)seconds
{
    [_mediaPlayer jumpForward:seconds];
}

- (void)remoteControlService:(VLCRemoteControlService *)rcs jumpBackwardInSeconds:(NSTimeInterval)seconds
{
    [_mediaPlayer jumpBackward:seconds];
}

- (NSInteger)remoteControlServiceNumberOfMediaItemsinList:(VLCRemoteControlService *)rcs
{
    return _mediaList.count;
}

- (void)remoteControlService:(VLCRemoteControlService *)rcs setPlaybackRate:(CGFloat)playbackRate
{
    self.playbackRate = playbackRate;
}
1066 1067
#pragma mark - helpers

1068 1069 1070 1071
- (NSDictionary *)mediaOptionsDictionary
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    return @{ kVLCSettingNetworkCaching : [defaults objectForKey:kVLCSettingNetworkCaching],
1072 1073 1074
              kVLCSettingStretchAudio : [[defaults objectForKey:kVLCSettingStretchAudio] boolValue] ? kVLCSettingStretchAudioOnValue : kVLCSettingStretchAudioOffValue,
              kVLCSettingTextEncoding : [defaults objectForKey:kVLCSettingTextEncoding],
              kVLCSettingSkipLoopFilter : [defaults objectForKey:kVLCSettingSkipLoopFilter],
1075
              kVLCSettingHardwareDecoding : [defaults objectForKey:kVLCSettingHardwareDecoding]};
1076 1077
}

1078
@end