VLCPlaybackController.m 43.2 KB
Newer Older
1 2 3 4
/*****************************************************************************
 * VLCPlaybackController.m
 * VLC for iOS
 *****************************************************************************
5
 * Copyright (c) 2013-2018 VideoLAN. All rights reserved.
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 * $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 "UIDevice+VLC.h"
#import <AVFoundation/AVFoundation.h>
22
#import "VLCPlayerDisplayController.h"
23
#import "VLCConstants.h"
24
#import "VLCRemoteControlService.h"
25
#import "VLCMetadata.h"
26
#if TARGET_OS_IOS
27
#import "VLC-Swift.h"
28
#endif
29

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

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

46
@interface VLCPlaybackController () <VLCMediaPlayerDelegate, VLCMediaDelegate, VLCRemoteControlServiceDelegate>
47
{
48
    VLCRemoteControlService *_remoteControlService;
49
    VLCMediaPlayer *_mediaPlayer;
50
    VLCMediaListPlayer *_listPlayer;
51 52
    BOOL _playerIsSetup;
    BOOL _shouldResumePlaying;
53 54 55 56
    BOOL _sessionWillRestart;

    NSString *_pathToExternalSubtitlesFile;
    int _itemInMediaListToBePlayedFirst;
57
    NSTimer *_sleepTimer;
58

59
    NSUInteger _currentAspectRatio;
60
    BOOL _isInFillToScreen;
61 62 63

    UIView *_videoOutputViewWrapper;
    UIView *_actualVideoOutputView;
64
    UIView *_preBackgroundWrapperView;
65

66
    BOOL _needsMetadataUpdate;
67
    BOOL _mediaWasJustStarted;
68
    BOOL _recheckForExistingThumbnail;
69
    BOOL _externalAudioPlaybackDeviceConnected;
70 71

    NSLock *_playbackSessionManagementLock;
72

Soomin Lee's avatar
Soomin Lee committed
73
    NSMutableArray *_shuffleStack;
74
    void (^_playbackCompletion)(BOOL success);
75 76 77 78 79 80 81 82 83 84 85 86 87 88
}

@end

@implementation VLCPlaybackController

#pragma mark instance management

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

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

    return sharedInstance;
}

- (void)dealloc
{
97
    _dialogProvider = nil;
98 99
}

Tobias's avatar
Tobias committed
100 101 102 103
- (instancetype)init
{
    self = [super init];
    if (self) {
104
        // listen to audiosessions and appkit callback
105
        _externalAudioPlaybackDeviceConnected = [self isExternalAudioPlaybackDeviceConnected];
Tobias's avatar
Tobias committed
106 107 108
        NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
        [defaultCenter addObserver:self selector:@selector(audioSessionRouteChange:)
                              name:AVAudioSessionRouteChangeNotification object:nil];
109

110 111 112
        [defaultCenter addObserver:self selector:@selector(handleInterruption:)
                              name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];

113
        // appkit because we neeed to know when we go to background in order to stop the video, so that we don't crash
Tobias's avatar
Tobias committed
114 115 116 117 118 119
        [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];
120

121
        _metadata = [VLCMetaData new];
122 123
        _dialogProvider = [[VLCDialogProvider alloc] initWithLibrary:[VLCLibrary sharedLibrary] customUI:YES];
        _dialogProvider.customRenderer = self;
124

125
        _playbackSessionManagementLock = [[NSLock alloc] init];
Soomin Lee's avatar
Soomin Lee committed
126 127
        _shuffleMode = NO;
        _shuffleStack = [[NSMutableArray alloc] init];
Tobias's avatar
Tobias committed
128 129 130 131
    }
    return self;
}

132 133 134 135 136 137 138 139
- (VLCRemoteControlService *)remoteControlService
{
    if (!_remoteControlService) {
        _remoteControlService = [[VLCRemoteControlService alloc] init];
        _remoteControlService.remoteControlServiceDelegate = self;
    }
    return _remoteControlService;
}
140 141
#pragma mark - playback management

142 143
- (void)openVideoSubTitlesFromFile:(NSString *)pathToFile
{
144
    [_mediaPlayer addPlaybackSlave:[NSURL fileURLWithPath:pathToFile] type:VLCMediaPlaybackSlaveTypeSubtitle enforce:YES];
145 146
}

147
- (void)playMediaList:(VLCMediaList *)mediaList firstIndex:(NSInteger)index subtitlesFilePath:(NSString * _Nullable)subsFilePath
148
{
149 150 151 152 153 154
    [self playMediaList: mediaList firstIndex: index subtitlesFilePath: subsFilePath completion: nil];
}

- (void)playMediaList:(VLCMediaList *)mediaList firstIndex:(NSInteger)index subtitlesFilePath:(NSString * _Nullable)subsFilePath completion:(void (^ __nullable)(BOOL success))completion
{
    _playbackCompletion = completion;
155
    self.mediaList = mediaList;
156 157
    _itemInMediaListToBePlayedFirst = (int)index;
    _pathToExternalSubtitlesFile = subsFilePath;
158

159 160
    _sessionWillRestart = _playerIsSetup;
    _playerIsSetup ? [self stopPlayback] : [self startPlayback];
161 162
}

163 164 165 166 167
- (VLCTime *)playedTime
{
    return [_mediaPlayer time];
}

168 169
- (void)startPlayback
{
170 171
    if (_playerIsSetup) {
        APLog(@"%s: player is already setup, bailing out", __PRETTY_FUNCTION__);
172
        return;
173
    }
174 175 176 177 178 179 180

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

181 182
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

183
    if (!self.mediaList) {
184
        APLog(@"%s: no URL and no media list set, stopping playback", __PRETTY_FUNCTION__);
185
        [_playbackSessionManagementLock unlock];
186 187 188
        [self stopPlayback];
        return;
    }
189

190 191 192 193 194 195 196
    /* 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;

197
    _listPlayer = [[VLCMediaListPlayer alloc] initWithDrawable:_actualVideoOutputView];
198 199 200 201 202 203

    /* 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;

204 205 206 207
    _mediaPlayer = _listPlayer.mediaPlayer;
    [_mediaPlayer setDelegate:self];
    if ([[defaults objectForKey:kVLCSettingPlaybackSpeedDefaultValue] floatValue] != 0)
        [_mediaPlayer setRate: [[defaults objectForKey:kVLCSettingPlaybackSpeedDefaultValue] floatValue]];
208 209
    int deinterlace = [[defaults objectForKey:kVLCSettingDeinterlace] intValue];
    [_mediaPlayer setDeinterlace:deinterlace withFilter:@"blend"];
210

211 212 213 214 215 216
    VLCMedia *media = [_mediaList mediaAtIndex:_itemInMediaListToBePlayedFirst];
    [media parseWithOptions:VLCMediaParseLocal];
    media.delegate = self;
    [media addOptions:self.mediaOptionsDictionary];

    [_listPlayer setMediaList:self.mediaList];
217

218 219
    [_listPlayer setRepeatMode:VLCDoNotRepeat];

220 221
    [_playbackSessionManagementLock unlock];

222
    [self _playNewMedia];
223 224 225 226
}

- (void)_playNewMedia
{
227 228
    BOOL ret = [_playbackSessionManagementLock tryLock];
    if (!ret) {
Felix Paul Kühne's avatar
Felix Paul Kühne committed
229
        APLog(@"%s: locking failed", __PRETTY_FUNCTION__);
230 231 232
        return;
    }

233 234 235 236 237
    // Set last selected equalizer profile
    unsigned int profile = (unsigned int)[[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingEqualizerProfile] integerValue];
    [_mediaPlayer resetEqualizerFromProfile:profile];
    [_mediaPlayer setPreAmplification:[_mediaPlayer preAmplification]];

238 239
    _mediaWasJustStarted = YES;

240 241 242
    [_mediaPlayer addObserver:self forKeyPath:@"time" options:0 context:nil];
    [_mediaPlayer addObserver:self forKeyPath:@"remainingTime" options:0 context:nil];

243 244
    [_mediaPlayer setRendererItem:_renderer];

245
    [_listPlayer playItemAtNumber:@(_itemInMediaListToBePlayedFirst)];
246 247 248 249

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

250
    _currentAspectRatio = VLCAspectRatioDefault;
251
    _mediaPlayer.videoAspectRatio = NULL;
252
    _mediaPlayer.videoCropGeometry = NULL;
253

254
    [[self remoteControlService] subscribeToRemoteCommands];
255

256 257 258 259 260 261 262 263 264 265 266
    if (_pathToExternalSubtitlesFile) {
        /* this could be a path or an absolute string - let's see */
        NSURL *subtitleURL = [NSURL URLWithString:_pathToExternalSubtitlesFile];
        if (!subtitleURL) {
            subtitleURL = [NSURL fileURLWithPath:_pathToExternalSubtitlesFile];
        }
        if (subtitleURL) {
            [_mediaPlayer addPlaybackSlave:subtitleURL type:VLCMediaPlaybackSlaveTypeSubtitle enforce:YES];
        }
    }

267
    _playerIsSetup = YES;
268

269
    [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackDidStart object:self];
270
    [_playbackSessionManagementLock unlock];
271 272 273 274
}

- (void)stopPlayback
{
275 276 277 278 279 280
    BOOL ret = [_playbackSessionManagementLock tryLock];
    if (!ret) {
        APLog(@"%s: locking failed", __PRETTY_FUNCTION__);
        return;
    }

281 282 283 284 285 286 287 288 289 290 291
    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
292
#if TARGET_OS_IOS
293
            [self _savePlaybackState];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
294
#endif
295 296
            [_mediaPlayer stop];
        }
297 298
        _mediaPlayer = nil;
        _listPlayer = nil;
299
    }
300
    if (!_sessionWillRestart) {
301
        _mediaList = nil;
302 303
    }
    _playerIsSetup = NO;
Soomin Lee's avatar
Soomin Lee committed
304
    [_shuffleStack removeAllObjects];
305

306 307 308
    if (_playbackCompletion) {
        BOOL finishedPlaybackWithError = _mediaPlayer.state == VLCMediaPlayerStateError &&  !_sessionWillRestart;
        _playbackCompletion(!finishedPlaybackWithError);
309
    }
310

311
    [[self remoteControlService] unsubscribeFromRemoteCommands];
312

313
    [_playbackSessionManagementLock unlock];
314 315 316
    if (!_sessionWillRestart) {
        [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackDidStop object:self];
    } else {
317
        _sessionWillRestart = NO;
318
        [self startPlayback];
319
    }
320 321
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
322
#if TARGET_OS_IOS
323 324 325 326 327 328 329 330 331 332 333

- (void)restoreAudioAndSubtitleTrack
{
    MLFile *item = [MLFile fileForURL:_mediaPlayer.media.url].firstObject;

    if (item) {
        _mediaPlayer.currentAudioTrackIndex = item.lastAudioTrack.intValue;
        _mediaPlayer.currentVideoSubTitleIndex = item.lastSubtitleTrack.intValue;
    }
}

334 335
- (void)_savePlaybackState
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
336 337 338 339 340 341 342
    @try {
        [[MLMediaLibrary sharedMediaLibrary] save];
    }
    @catch (NSException *exception) {
        APLog(@"saving playback state failed");
    }

343
    NSArray *files = [MLFile fileForURL:_mediaPlayer.media.url];
344
    MLFile *fileItem = files.firstObject;
345

346 347
    if (!fileItem) {
        APLog(@"couldn't find file, not saving playback progress");
348
        return;
349
    }
350 351 352 353 354 355 356 357 358 359

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

        if (position > .95)
            return;

360 361 362 363 364 365 366 367 368 369 370 371 372
        if (_mediaPlayer.hasVideoOut) {
            NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
            NSString *newThumbnailPath = [searchPaths.firstObject stringByAppendingPathComponent:@"VideoSnapshots"];
            NSError *error;

            [[NSFileManager defaultManager] createDirectoryAtPath:newThumbnailPath withIntermediateDirectories:YES attributes:nil error:&error];
            if (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];
            }
        }
373 374 375 376 377
    }
    @catch (NSException *exception) {
        APLog(@"failed to save current media state - file removed?");
    }
}
Felix Paul Kühne's avatar
Felix Paul Kühne committed
378
#endif
379

Felix Paul Kühne's avatar
Felix Paul Kühne committed
380
#if TARGET_OS_IOS
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 408 409 410 411 412
- (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];
413
}
Felix Paul Kühne's avatar
Felix Paul Kühne committed
414
#endif
415 416 417

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
418 419
    if (_mediaWasJustStarted) {
        _mediaWasJustStarted = NO;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
420
#if TARGET_OS_IOS
421 422
        if (self.mediaList) {
            NSArray *matches = [MLFile fileForURL:_mediaPlayer.media.url];
423
            MLFile *item = matches.firstObject;
424 425
            [self _recoverLastPlaybackStateOfItem:item];
        }
426 427 428
#else
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
        BOOL bValue = [defaults boolForKey:kVLCSettingUseSPDIF];
429

430
        if (bValue) {
431
           _mediaPlayer.audio.passthrough = bValue;
432
        }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
433
#endif
434 435
    }

436 437
    if ([self.delegate respondsToSelector:@selector(playbackPositionUpdated:)])
        [self.delegate playbackPositionUpdated:self];
438 439 440

    [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackPositionUpdated
                                                        object:self];
441 442 443 444
}

- (NSInteger)mediaDuration
{
445
    return _mediaPlayer.media.length.intValue;;
446 447 448 449 450 451 452
}

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

453 454 455 456 457
- (BOOL)willPlay
{
    return _mediaPlayer.willPlay;
}

458 459 460 461 462 463 464 465 466 467 468 469
- (VLCRepeatMode)repeatMode
{
    return _listPlayer.repeatMode;
}

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

- (BOOL)currentMediaHasChapters
{
470
    return [_mediaPlayer numberOfTitles] > 1 || [_mediaPlayer numberOfChaptersForTitle:_mediaPlayer.currentTitleIndex] > 1;
471 472 473 474 475 476 477
}

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

478 479 480 481 482
- (BOOL) isSeekable
{
    return _mediaPlayer.isSeekable;
}

483 484 485 486 487
- (NSNumber *)playbackTime
{
    return _mediaPlayer.time.value;
}

488 489
- (float)playbackRate
{
490
    return _mediaPlayer.rate;
491 492 493 494
}

- (void)setPlaybackRate:(float)playbackRate
{
495 496
    [_mediaPlayer setRate:playbackRate];
    _metadata.playbackRate = @(_mediaPlayer.rate);
497 498
}

499 500
- (void)setAudioDelay:(float)audioDelay
{
501
    _mediaPlayer.currentAudioPlaybackDelay = 1000.*audioDelay;
502
}
503

504 505
- (float)audioDelay
{
506
    return _mediaPlayer.currentAudioPlaybackDelay/1000.;
507
}
508

509 510 511 512 513 514 515
- (float)playbackPosition
{
    return [_mediaPlayer position];
}

- (void)setPlaybackPosition:(float)position
{
516
    _mediaPlayer.position = position;
517 518 519
}

- (void)setSubtitleDelay:(float)subtitleDeleay
520
{
521
    _mediaPlayer.currentVideoSubTitleDelay = 1000.*subtitleDeleay;
522
}
523

524 525
- (float)subtitleDelay
{
526
    return _mediaPlayer.currentVideoSubTitleDelay/1000.;
527 528
}

529 530 531 532 533 534 535 536 537 538 539 540 541 542 543
- (float)hue
{
    return _mediaPlayer.hue;
}

- (void)setHue:(float)hue
{
    _mediaPlayer.hue = hue;
}

- (float)contrast
{
    return _mediaPlayer.contrast;
}

544
- (void)setContrast:(float)contrast
545
{
546
    _mediaPlayer.contrast = contrast;
547 548 549 550 551 552
}

- (float)brightness
{
    return _mediaPlayer.brightness;
}
553

554 555 556 557
- (void)setBrightness:(float)brightness
{
    _mediaPlayer.brightness = brightness;
}
558

559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587
- (float)saturation
{
    return _mediaPlayer.saturation;
}

- (void)setSaturation:(float)saturation
{
    _mediaPlayer.saturation = saturation;
}

- (void)setGamma:(float)gamma
{
    _mediaPlayer.gamma = gamma;
}

- (float)gamma
{
    return _mediaPlayer.gamma;
}

- (void)resetFilters
{
    _mediaPlayer.hue = 0.;
    _mediaPlayer.contrast = 1.;
    _mediaPlayer.brightness = 1.;
    _mediaPlayer.saturation = 1.;
    _mediaPlayer.gamma = 1.;
}

588 589
- (void)toggleRepeatMode
{
590 591 592 593
    if (_listPlayer.repeatMode == VLCRepeatAllItems) {
        _listPlayer.repeatMode = VLCDoNotRepeat;
    } else {
        _listPlayer.repeatMode += 1;
594 595 596
    }
}

597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696
- (NSInteger)indexOfCurrentAudioTrack
{
    return [_mediaPlayer.audioTrackIndexes indexOfObject:@(_mediaPlayer.currentAudioTrackIndex)];
}

- (NSInteger)indexOfCurrentSubtitleTrack
{
    return [_mediaPlayer.videoSubTitlesIndexes indexOfObject:@(_mediaPlayer.currentVideoSubTitleIndex)];
}

- (NSInteger)indexOfCurrentChapter
{
    return _mediaPlayer.currentChapterIndex;
}

- (NSInteger)indexOfCurrentTitle
{
    return _mediaPlayer.currentTitleIndex;
}

- (NSInteger)numberOfAudioTracks
{
    return _mediaPlayer.audioTrackIndexes.count;
}

- (NSInteger)numberOfVideoSubtitlesIndexes
{
    return _mediaPlayer.videoSubTitlesIndexes.count;
}

- (NSInteger)numberOfTitles
{
    return  [_mediaPlayer numberOfTitles];
}

- (NSInteger)numberOfChaptersForCurrentTitle
{
    return [_mediaPlayer numberOfChaptersForTitle:_mediaPlayer.currentTitleIndex];
}

- (NSString *)videoSubtitleNameAtIndex:(NSInteger)index
{
    if (index >= 0 && index < _mediaPlayer.videoSubTitlesNames.count)
        return _mediaPlayer.videoSubTitlesNames[index];
    return nil;
}

- (NSString *)audioTrackNameAtIndex:(NSInteger)index
{
    if (index >= 0 && index < _mediaPlayer.audioTrackNames.count)
        return _mediaPlayer.audioTrackNames[index];
    return nil;
}

- (NSDictionary *)titleDescriptionsDictAtIndex:(NSInteger)index
{
    if (index >= 0 && index < _mediaPlayer.titleDescriptions.count)
        return _mediaPlayer.titleDescriptions[index];
    return nil;
}

- (NSDictionary *)chapterDescriptionsDictAtIndex:(NSInteger)index
{
    NSArray *chapterDescriptions = [_mediaPlayer chapterDescriptionsOfTitle:_mediaPlayer.currentTitleIndex];
    if (index >= 0 && index < chapterDescriptions.count)
        return chapterDescriptions[index];
    return nil;
}

- (void)selectAudioTrackAtIndex:(NSInteger)index
{
    if (index >= 0 && index < _mediaPlayer.audioTrackIndexes.count) {
        //we can cast this cause we won't have more than 2 million audiotracks
        _mediaPlayer.currentAudioTrackIndex = [_mediaPlayer.audioTrackIndexes[index] intValue];
    }
}

- (void)selectVideoSubtitleAtIndex:(NSInteger)index
{
    if (index >= 0 && index < _mediaPlayer.videoSubTitlesIndexes.count) {
        _mediaPlayer.currentVideoSubTitleIndex = [_mediaPlayer.videoSubTitlesIndexes[index] intValue];
    }
}

- (void)selectTitleAtIndex:(NSInteger)index
{
    if (index >= 0 && index < [_mediaPlayer numberOfTitles]) {
        //we can cast this cause we won't have more than 2 million titles
        _mediaPlayer.currentTitleIndex = (int)index;
    }
}

- (void)selectChapterAtIndex:(NSInteger)index
{
    if (index >= 0 && index < [self numberOfChaptersForCurrentTitle]) {
        //we can cast this cause we won't have more than 2 million chapters
        _mediaPlayer.currentChapterIndex = (int)index;
    }
}

697 698 699 700 701 702 703 704 705 706 707 708 709 710 711
- (void)shortJumpForward
{
    [_mediaPlayer shortJumpForward];
}

- (void)shortJumpBackward
{
    [_mediaPlayer shortJumpBackward];
}

- (VLCTime *)remainingTime
{
    return [_mediaPlayer remainingTime];
}

712 713 714 715 716
- (void)setAudioPassthrough:(BOOL)shouldPass
{
    _mediaPlayer.audio.passthrough = shouldPass;
}

717 718
- (void)mediaPlayerStateChanged:(NSNotification *)aNotification
{
719
    VLCMediaPlayerState currentState = _mediaPlayer.state;
720

721 722 723 724 725 726 727
    switch (currentState) {
        case VLCMediaPlayerStateBuffering: {
            /* attach delegate */
            _mediaPlayer.media.delegate = self;

            /* on-the-fly values through hidden API */
            NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
728 729
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
730 731 732 733
            [_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]];
734
#pragma clang diagnostic pop
735 736 737 738 739 740 741
        } break;

        case VLCMediaPlayerStateError: {
            APLog(@"Playback failed");
            dispatch_async(dispatch_get_main_queue(),^{
                [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackDidFail object:self];
            });
742
            _sessionWillRestart = NO;
743
            [self stopPlayback];
744 745 746 747 748
        } break;
        case VLCMediaPlayerStateEnded:
        case VLCMediaPlayerStateStopped: {
            [_listPlayer.mediaList lock];
            NSUInteger listCount = _listPlayer.mediaList.count;
749
            [_listPlayer.mediaList unlock];
750 751 752 753
            if ([_listPlayer.mediaList indexOfMedia:_mediaPlayer.media] == listCount - 1 && self.repeatMode == VLCDoNotRepeat) {
                _sessionWillRestart = NO;
                [self stopPlayback];
                return;
754
            }
755 756
        } break;
        case VLCMediaPlayerStateESAdded: {
757 758 759
#if TARGET_OS_IOS
            [self restoreAudioAndSubtitleTrack];
#endif
760 761 762
        } break;
        default:
            break;
763
    }
764

765
    if ([self.delegate respondsToSelector:@selector(mediaPlayerStateChanged:isPlaying:currentMediaHasTrackToChooseFrom:currentMediaHasChapters:forPlaybackController:)])
766
        [self.delegate mediaPlayerStateChanged:currentState
767 768 769 770
                                     isPlaying:_mediaPlayer.isPlaying
              currentMediaHasTrackToChooseFrom:self.currentMediaHasTrackToChooseFrom
                       currentMediaHasChapters:self.currentMediaHasChapters
                         forPlaybackController:self];
771 772

    [self setNeedsMetadataUpdate];
773 774 775 776 777
}

#pragma mark - playback controls
- (void)playPause
{
778 779 780 781 782 783 784 785 786 787 788 789
    [_mediaPlayer isPlaying] ? [self pause] : [self play];
}

- (void)play
{
    [_listPlayer play];
    [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackDidResume object:self];
}

- (void)pause
{
    [_listPlayer pause];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
790
#if TARGET_OS_IOS
791
    [self _savePlaybackState];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
792
#endif
793
    [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackDidPause object:self];
794 795
}

796
- (void)next
797
{
Soomin Lee's avatar
Soomin Lee committed
798 799
    NSInteger mediaListCount = _mediaList.count;

Soomin Lee's avatar
Soomin Lee committed
800
#if TARGET_OS_IOS
801
    if (self.repeatMode != VLCRepeatCurrentItem && mediaListCount > 2 && _shuffleMode) {
Soomin Lee's avatar
Soomin Lee committed
802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822

        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
823
#endif
Soomin Lee's avatar
Soomin Lee committed
824 825

    if (mediaListCount > 1) {
826
        [_listPlayer next];
827
        [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackMetadataDidChange object:self];
828 829 830 831 832 833
    } else {
        NSNumber *skipLength = [[NSUserDefaults standardUserDefaults] valueForKey:kVLCSettingPlaybackForwardSkipLength];
        [_mediaPlayer jumpForward:skipLength.intValue];
    }
}

834
- (void)previous
835
{
836
    if (_mediaList.count > 1) {
837
        [_listPlayer previous];
838
        [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackMetadataDidChange object:self];
839 840 841 842 843 844 845
    }
    else {
        NSNumber *skipLength = [[NSUserDefaults standardUserDefaults] valueForKey:kVLCSettingPlaybackBackwardSkipLength];
        [_mediaPlayer jumpBackward:skipLength.intValue];
    }
}

846

847 848 849 850 851 852 853 854 855 856
- (void)jumpForward:(int)interval
{
    [_mediaPlayer jumpForward:interval];
}

- (void)jumpBackward:(int)interval
{
    [_mediaPlayer jumpBackward:interval];
}

857
- (UIScreen *)currentScreen
858
{
859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882
    return [[UIDevice currentDevice] VLCHasExternalDisplay] ? [UIScreen screens][1] : [UIScreen mainScreen];
}

- (void)switchToFillToScreen
{
    UIScreen *screen = [self currentScreen];
    CGSize screenSize = screen.bounds.size;

    CGSize videoSize = _mediaPlayer.videoSize;

    CGFloat ar = videoSize.width / (float)videoSize.height;
    CGFloat dar = screenSize.width / (float)screenSize.height;

    CGFloat scale;

    if (dar >= ar) {
        scale = screenSize.width / (float)videoSize.width;
    } else {
        scale = screenSize.height / (float)videoSize.height;
    }

    // Multiplied by screen.scale in consideration of pt to px
    _mediaPlayer.scaleFactor = scale * screen.scale;
    _isInFillToScreen = YES;
883 884 885 886
}

- (void)switchIPhoneXFullScreen
{
887
    if (_isInFillToScreen) {
888 889
        const char *previousAspectRatio = _currentAspectRatio == VLCAspectRatioDefault ? NULL : [[self stringForAspectRatio:_currentAspectRatio] UTF8String];
        _mediaPlayer.videoAspectRatio = (char *)previousAspectRatio;
890 891 892 893
        _mediaPlayer.scaleFactor = 0;
        _isInFillToScreen = NO;
    } else {
        [self switchToFillToScreen];
894 895 896
    }
}

897 898
- (void)switchAspectRatio
{
899 900 901
    _currentAspectRatio = _currentAspectRatio == VLCAspectRatioSixteenToTen ? VLCAspectRatioDefault : _currentAspectRatio + 1;
    switch (_currentAspectRatio) {
        case VLCAspectRatioDefault:
902
            _mediaPlayer.scaleFactor = 0;
903
            _mediaPlayer.videoAspectRatio = NULL;
904
            _mediaPlayer.videoCropGeometry = NULL;
905 906
            break;
        case VLCAspectRatioFillToScreen:
907 908 909 910
            // Reset aspect ratio only with aspectRatio button since we want to keep
            // the user ratio with double tap.
            _mediaPlayer.videoAspectRatio = NULL;
            [self switchToFillToScreen];
911 912 913 914
            break;
        case VLCAspectRatioFourToThree:
        case VLCAspectRatioSixteenToTen:
        case VLCAspectRatioSixteenToNine:
915
            _mediaPlayer.scaleFactor = 0;
916 917
            _mediaPlayer.videoCropGeometry = NULL;
            _mediaPlayer.videoAspectRatio = (char *)[[self stringForAspectRatio:_currentAspectRatio] UTF8String];
918
    }
919

920 921
    if ([self.delegate respondsToSelector:@selector(showStatusMessage:)]) {
        [self.delegate showStatusMessage:[NSString stringWithFormat:NSLocalizedString(@"AR_CHANGED", nil), [self stringForAspectRatio:_currentAspectRatio]]];
922 923
    }
}
924

925 926 927 928 929 930 931 932
- (NSString *)stringForAspectRatio:(VLCAspectRatio)ratio
{
    switch (ratio) {
            case VLCAspectRatioFillToScreen:
            return NSLocalizedString(@"FILL_TO_SCREEN", nil);
            case VLCAspectRatioDefault:
            return NSLocalizedString(@"DEFAULT", nil);
            case VLCAspectRatioFourToThree:
933
            return @"4:3";
934
            case VLCAspectRatioSixteenToTen:
935
            return @"16:10";
936
            case VLCAspectRatioSixteenToNine:
937
            return @"16:9";
938 939
        default:
            NSAssert(NO, @"this shouldn't happen");
940 941 942
    }
}

943 944 945 946 947 948 949 950 951 952 953 954 955 956
- (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;
            }
        }
    }
}

957 958 959
- (void)setVideoOutputView:(UIView *)videoOutputView
{
    if (videoOutputView) {
960 961 962
        if ([_actualVideoOutputView superview] != nil)
            [_actualVideoOutputView removeFromSuperview];

963 964
        _actualVideoOutputView.frame = (CGRect){CGPointZero, videoOutputView.frame.size};

965
        [self setVideoTrackEnabled:true];
966

967
        [videoOutputView addSubview:_actualVideoOutputView];
968 969 970
        [_actualVideoOutputView layoutSubviews];
        [_actualVideoOutputView updateConstraints];
        [_actualVideoOutputView setNeedsLayout];
971
    } else
972 973 974 975 976 977 978 979 980 981
        [_actualVideoOutputView removeFromSuperview];

    _videoOutputViewWrapper = videoOutputView;
}

- (UIView *)videoOutputView
{
    return _videoOutputViewWrapper;
}

982
#pragma mark - 360 Support
Soomin Lee's avatar
Soomin Lee committed
983
#if !TARGET_OS_TV
984 985
- (BOOL)updateViewpoint:(CGFloat)yaw pitch:(CGFloat)pitch roll:(CGFloat)roll fov:(CGFloat)fov absolute:(BOOL)absolute
{
986 987 988 989 990 991 992 993 994 995
    //adjusting the values
    if (fabs(yaw) > 180) {
        yaw = yaw > 0 ? yaw - 360 : yaw + 360;
    }
    if (fabs(roll) > 180) {
        roll = roll > 0 ? roll - 360 : roll + 360;
    }
    if (fabs(pitch) > 90) {
        pitch = pitch > 0 ? pitch - 180 : pitch + 180;
    }
996 997 998
    return [_mediaPlayer updateViewpoint:yaw pitch:pitch roll:roll fov:fov absolute:absolute];
}

999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018
- (CGFloat)yaw
{
    return _mediaPlayer.yaw;
}

- (CGFloat)pitch
{
    return _mediaPlayer.pitch;
}

- (CGFloat)roll
{
    return _mediaPlayer.roll;
}

- (CGFloat)fov
{
    return _mediaPlayer.fov;
}

1019 1020 1021 1022 1023
- (BOOL)currentMediaIs360Video
{
    return [self currentMediaProjection] == VLCMediaProjectionEquiRectangular;
}

1024 1025 1026 1027 1028 1029 1030 1031
- (NSInteger)currentMediaProjection
{
    VLCMedia *media = [_mediaPlayer media];
    NSInteger currentVideoTrackIndex = [_mediaPlayer currentVideoTrackIndex];

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

1032 1033 1034 1035
        for (NSDictionary *track in tracksInfo) {
            if ([track[VLCMediaTracksInformationType] isEqualToString:VLCMediaTracksInformationTypeVideo]) {
                return [track[VLCMediaTracksInformationVideoProjection] integerValue];
            }
1036 1037 1038 1039
        }
    }
    return -1;
}
Soomin Lee's avatar
Soomin Lee committed
1040
#endif
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 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083
#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];
}

1084 1085 1086
#pragma mark - AVAudioSession Notification Observers

- (void)handleInterruption:(NSNotification *)notification
1087
{
1088 1089 1090 1091
    NSDictionary *userInfo = notification.userInfo;

    if (!userInfo || !userInfo[AVAudioSessionInterruptionTypeKey]) {
        return;
1092 1093
    }

1094 1095 1096 1097 1098 1099
    NSUInteger interruptionType = [userInfo[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];

    if (interruptionType == AVAudioSessionInterruptionTypeBegan) {
        [_mediaPlayer pause];
    } else if (interruptionType == AVAudioSessionInterruptionTypeEnded
               && [userInfo[AVAudioSessionInterruptionOptionKey] unsignedIntegerValue] == AVAudioSessionInterruptionOptionShouldResume) {
1100 1101 1102 1103
        [_mediaPlayer play];
    }
}

1104
- (BOOL)isExternalAudioPlaybackDeviceConnected
1105
{
1106
    /* check what output device is currently connected
1107
     * this code assumes that everything which is not a builtin speaker, must be external */
1108
    NSArray *outputs = [[AVAudioSession sharedInstance] currentRoute].outputs;
1109 1110
    AVAudioSessionPortDescription *outputDescription = outputs.firstObject;
    return ![outputDescription.portType isEqualToString:AVAudioSessionPortBuiltInSpeaker];
1111 1112 1113 1114
}

- (void)audioSessionRouteChange:(NSNotification *)notification
{
1115 1116 1117 1118 1119 1120
    NSDictionary *userInfo = notification.userInfo;
    NSInteger routeChangeReason = [[userInfo valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];

    if (routeChangeReason == AVAudioSessionRouteChangeReasonRouteConfigurationChange)
        return;

1121
    BOOL externalAudioPlaybackDeviceConnected = [self isExternalAudioPlaybackDeviceConnected];
1122

1123 1124
    if (_externalAudioPlaybackDeviceConnected && !externalAudioPlaybackDeviceConnected && [_mediaPlayer isPlaying]) {
        APLog(@"Pausing playback as previously connected external audio playback device was removed");
1125
        [_mediaPlayer pause];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
1126
#if TARGET_OS_IOS
1127
        [self _savePlaybackState];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
1128
#endif
1129 1130
        [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackDidPause object:self];
    }
1131
    _externalAudioPlaybackDeviceConnected = externalAudioPlaybackDeviceConnected;
1132 1133 1134 1135
}

#pragma mark - Managing the media item

1136 1137 1138
- (VLCMedia *)currentlyPlayingMedia
{
    return _mediaPlayer.media;
1139 1140 1141
}

#pragma mark - metadata handling
1142 1143 1144 1145
- (void)performNavigationAction:(VLCMediaPlaybackNavigationAction)action
{
    [_mediaPlayer performNavigationAction:action];
}
1146 1147
- (void)mediaDidFinishParsing:(VLCMedia *)aMedia
{
1148
    [self setNeedsMetadataUpdate];
1149 1150 1151 1152
}

- (void)mediaMetaDataDidChange:(VLCMedia*)aMedia
{
1153 1154 1155 1156 1157 1158 1159
    [self setNeedsMetadataUpdate];
}

- (void)setNeedsMetadataUpdate
{
    if (_needsMetadataUpdate == NO) {
        _needsMetadataUpdate = YES;
1160
        dispatch_async(dispatch_get_main_queue(), ^{
1161 1162
            [self->_metadata updateMetadataFromMediaPlayer:self->_mediaPlayer];
            self->_needsMetadataUpdate = NO;
1163
            if ([self.delegate respondsToSelector:@selector(displayMetadataForPlaybackController:metadata:)])
1164
                [self.delegate displayMetadataForPlaybackController:self metadata:self->_metadata];
1165 1166
        });
    }
1167 1168
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
1169
#if TARGET_OS_IOS
1170 1171 1172 1173 1174 1175
- (void)_recoverLastPlaybackStateOfItem:(MLFile *)item
{
    if (item) {
        CGFloat lastPosition = .0;
        NSInteger duration = 0;

1176
        if (item.lastPosition) {
1177
            lastPosition = item.lastPosition.floatValue;
1178 1179
        }
        
1180 1181
        duration = item.duration.intValue;

1182
        if (lastPosition < .95 && _mediaPlayer.position < lastPosition) {
1183
            NSInteger continuePlayback;
1184
            if ([item isAlbumTrack] || [item isSupportedAudioFile])
1185 1186 1187 1188
                continuePlayback = [[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingContinueAudioPlayback] integerValue];
            else
                continuePlayback = [[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingContinuePlayback] integerValue];

1189
            if (continuePlayback == 1) {
1190
                [self setPlaybackPosition:lastPosition];
1191
            } else if (continuePlayback == 0) {
1192
                NSArray<VLCAlertButton *> *buttonsAction = @[[[VLCAlertButton alloc] initWithTitle: NSLocalizedString(@"BUTTON_CANCEL", nil)
1193 1194
                                                                                             style: UIAlertActionStyleCancel
                                                                                            action: nil],
1195
                                                             [[VLCAlertButton alloc] initWithTitle: NSLocalizedString(@"BUTTON_CONTINUE", nil)
1196
                                                                                            action: ^(UIAlertAction *action) {
1197 1198
                                                                                                [self setPlaybackPosition:lastPosition];
                                                                                            }]
1199
                                                             ];
1200 1201
                UIViewController *presentingVC = [UIApplication sharedApplication].delegate.window.rootViewController;
                presentingVC = presentingVC.presentedViewController ?: presentingVC;
1202 1203
                [VLCAlertViewController alertViewManagerWithTitle:NSLocalizedString(@"CONTINUE_PLAYBACK", nil)
                                                     errorMessage:[NSString stringWithFormat:NSLocalizedString(@"CONTINUE_PLAYBACK_LONG", nil), item.title]
1204
                                                   viewController:presentingVC
1205
                                                    buttonsAction:buttonsAction];
1206

1207 1208
            }
        }
1209 1210
    }
}
Felix Paul Kühne's avatar
Felix Paul Kühne committed
1211
#endif
1212

1213 1214
- (void)recoverDisplayedMetadata
{
1215 1216
    if ([self.delegate respondsToSelector:@selector(displayMetadataForPlaybackController:metadata:)])
        [self.delegate displayMetadataForPlaybackController:self metadata:_metadata];
1217 1218
}

1219 1220 1221 1222 1223 1224 1225 1226
- (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];
1227 1228
    if ([self.delegate respondsToSelector:@selector(prepareForMediaPlayback:)])
        [self.delegate prepareForMediaPlayback:self];
1229 1230
}

1231 1232 1233 1234 1235 1236 1237 1238 1239
- (void)scheduleSleepTimerWithInterval:(NSTimeInterval)timeInterval
{
    if (_sleepTimer) {
        [_sleepTimer invalidate];
        _sleepTimer = nil;
    }
    _sleepTimer = [NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(stopPlayback) userInfo:nil repeats:NO];
}

1240 1241 1242 1243 1244
- (BOOL)isPlayingOnExternalScreen
{
    return (_renderer || [[UIDevice currentDevice] VLCHasExternalDisplay]);
}

1245 1246 1247 1248
#pragma mark - background interaction

- (void)applicationWillResignActive:(NSNotification *)aNotification
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
1249
#if TARGET_OS_IOS
1250
    [self _savePlaybackState];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
1251
#endif
1252 1253
    if (![self isPlayingOnExternalScreen]
        && ![[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingContinueAudioInBackgroundKey] boolValue]) {
1254 1255 1256 1257 1258 1259 1260 1261 1262
        if ([_mediaPlayer isPlaying]) {
            [_mediaPlayer pause];
            _shouldResumePlaying = YES;
        }
    }
}

- (void)applicationDidEnterBackground:(NSNotification *)notification
{
1263 1264
    _preBackgroundWrapperView = _videoOutputViewWrapper;

1265
    if (!_renderer && _mediaPlayer.audioTrackIndexes.count > 0)
1266
        [self setVideoTrackEnabled:false];
1267 1268 1269 1270
}

- (void)applicationDidBecomeActive:(NSNotification *)notification
{
1271 1272 1273 1274
    if (_preBackgroundWrapperView) {
        [self setVideoOutputView:_preBackgroundWrapperView];
        _preBackgroundWrapperView = nil;
    }
1275

1276
    [self setVideoTrackEnabled:true];
1277 1278 1279 1280 1281 1282

    if (_shouldResumePlaying) {
        _shouldResumePlaying = NO;
        [_listPlayer play];
    }
}
1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298
#pragma mark - remoteControlDelegate

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

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

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

1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336
- (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;
}
1337 1338 1339 1340 1341 1342 1343

- (void)remoteControlService:(VLCRemoteControlService *)rcs setCurrentPlaybackTime:(NSTimeInterval)playbackTime
{
    float positionDiff = playbackTime - [self.metadata.elapsedPlaybackTime floatValue];
    [_mediaPlayer jumpForward:positionDiff];
}

1344 1345
#pragma mark - helpers

1346 1347 1348 1349
- (NSDictionary *)mediaOptionsDictionary
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    return @{ kVLCSettingNetworkCaching : [defaults objectForKey:kVLCSettingNetworkCaching],
1350 1351 1352
              kVLCSettingStretchAudio : [[defaults objectForKey:kVLCSettingStretchAudio] boolValue] ? kVLCSettingStretchAudioOnValue : kVLCSettingStretchAudioOffValue,
              kVLCSettingTextEncoding : [defaults objectForKey:kVLCSettingTextEncoding],
              kVLCSettingSkipLoopFilter : [defaults objectForKey:kVLCSettingSkipLoopFilter],
1353
              kVLCSettingHardwareDecoding : [defaults objectForKey:kVLCSettingHardwareDecoding]};
1354 1355
}

1356
#pragma mark - Renderer
1357
- (void)setRenderer:(VLCRendererItem * __nullable)renderer
1358
{
1359 1360
    _renderer = renderer;
    [_mediaPlayer setRendererItem:_renderer];
1361
}
1362
@end