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's avatar
Carola 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