VLCFullscreenMovieTVViewController.m 34.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
/*****************************************************************************
 * VLC for iOS
 *****************************************************************************
 * Copyright (c) 2015 VideoLAN. All rights reserved.
 * $Id$
 *
 * Authors: Felix Paul Kühne <fkuehne # videolan.org>
 *
 * Refer to the COPYING file of the official project for license.
 *****************************************************************************/

#import "VLCFullscreenMovieTVViewController.h"
13
#import "VLCPlaybackInfoTVViewController.h"
14
#import "VLCPlaybackInfoTVAnimators.h"
15
#import "VLCIRTVTapGestureRecognizer.h"
16
#import "VLCHTTPUploaderController.h"
17
#import "VLCSiriRemoteGestureRecognizer.h"
18
#import "VLCNetworkImageView.h"
19

20 21 22 23 24 25 26
typedef NS_ENUM(NSInteger, VLCPlayerScanState)
{
    VLCPlayerScanStateNone,
    VLCPlayerScanStateForward2,
    VLCPlayerScanStateForward4,
};

27
@interface VLCFullscreenMovieTVViewController (UIViewControllerTransitioningDelegate) <UIViewControllerTransitioningDelegate, UIGestureRecognizerDelegate>
28
@end
29

30
@interface VLCFullscreenMovieTVViewController ()
31

32 33
@property (nonatomic) CADisplayLink *displayLink;
@property (nonatomic) NSTimer *audioDescriptionScrollTimer;
34
@property (nonatomic) NSTimer *hidePlaybackControlsViewAfterDeleayTimer;
35
@property (nonatomic) VLCPlaybackInfoTVViewController *infoViewController;
36 37
@property (nonatomic) NSNumber *scanSavedPlaybackRate;
@property (nonatomic) VLCPlayerScanState scanState;
38 39
@property (nonatomic) NSString *lastArtist;

40 41
@property (nonatomic, readonly, getter=isSeekable) BOOL seekable;

42 43
@property (nonatomic) NSSet<UIGestureRecognizer *> *simultaneousGestureRecognizers;

44 45 46 47
@end

@implementation VLCFullscreenMovieTVViewController

48 49 50 51 52
+ (instancetype)fullscreenMovieTVViewController
{
    return [[self alloc] initWithNibName:nil bundle:nil];
}

53 54
- (void)viewDidLoad
{
55 56 57 58 59
    self.extendedLayoutIncludesOpaqueBars = YES;
    self.edgesForExtendedLayout = UIRectEdgeAll;

    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self
60
               selector:@selector(playbackDidStop)
61 62
                   name:VLCPlaybackControllerPlaybackDidStop
                 object:nil];
63 64 65 66
    [center addObserver:self
               selector:@selector(playbackDidStop)
                   name:VLCPlaybackControllerPlaybackDidFail
                 object:nil];
67 68

    _movieView.userInteractionEnabled = NO;
69

70 71 72 73 74 75 76
    self.titleLabel.text = @"";

    self.transportBar.bufferStartFraction = 0.0;
    self.transportBar.bufferEndFraction = 1.0;
    self.transportBar.playbackFraction = 0.0;
    self.transportBar.scrubbingFraction = 0.0;

77
    self.dimmingView.alpha = 0.0;
78
    self.bottomOverlayView.alpha = 0.0;
Tobias's avatar
Tobias committed
79

Felix Paul Kühne's avatar
Felix Paul Kühne committed
80
    self.bufferingLabel.text = NSLocalizedString(@"PLEASE_WAIT", nil);
81

82
    NSMutableSet<UIGestureRecognizer *> *simultaneousGestureRecognizers = [NSMutableSet set];
83

84
    // Panning and Swiping
Tobias's avatar
Tobias committed
85
    UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGesture:)];
86
    panGestureRecognizer.delegate = self;
Tobias's avatar
Tobias committed
87
    [self.view addGestureRecognizer:panGestureRecognizer];
88
    [simultaneousGestureRecognizers addObject:panGestureRecognizer];
Tobias's avatar
Tobias committed
89

90 91 92 93 94
    // Button presses
    UITapGestureRecognizer *playpauseGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(playPausePressed)];
    playpauseGesture.allowedPressTypes = @[@(UIPressTypePlayPause)];
    [self.view addGestureRecognizer:playpauseGesture];

95 96
    UITapGestureRecognizer *menuTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(menuButtonPressed:)];
    menuTapGestureRecognizer.allowedPressTypes = @[@(UIPressTypeMenu)];
97
    menuTapGestureRecognizer.delegate = self;
98
    [self.view addGestureRecognizer:menuTapGestureRecognizer];
99

100
    // IR only recognizer
101 102 103 104
    UITapGestureRecognizer *upArrowRecognizer = [[VLCIRTVTapGestureRecognizer alloc] initWithTarget:self action:@selector(handleIRPressUp)];
    upArrowRecognizer.allowedPressTypes = @[@(UIPressTypeUpArrow)];
    [self.view addGestureRecognizer:upArrowRecognizer];

105 106 107
    UITapGestureRecognizer *downArrowRecognizer = [[VLCIRTVTapGestureRecognizer alloc] initWithTarget:self action:@selector(showInfoVCIfNotScrubbing)];
    downArrowRecognizer.allowedPressTypes = @[@(UIPressTypeDownArrow)];
    [self.view addGestureRecognizer:downArrowRecognizer];
108

109 110 111 112 113 114 115
    UITapGestureRecognizer *leftArrowRecognizer = [[VLCIRTVTapGestureRecognizer alloc] initWithTarget:self action:@selector(handleIRPressLeft)];
    leftArrowRecognizer.allowedPressTypes = @[@(UIPressTypeLeftArrow)];
    [self.view addGestureRecognizer:leftArrowRecognizer];

    UITapGestureRecognizer *rightArrowRecognizer = [[VLCIRTVTapGestureRecognizer alloc] initWithTarget:self action:@selector(handleIRPressRight)];
    rightArrowRecognizer.allowedPressTypes = @[@(UIPressTypeRightArrow)];
    [self.view addGestureRecognizer:rightArrowRecognizer];
116 117 118 119 120

    // Siri remote arrow presses
    VLCSiriRemoteGestureRecognizer *siriArrowRecognizer = [[VLCSiriRemoteGestureRecognizer alloc] initWithTarget:self action:@selector(handleSiriRemote:)];
    siriArrowRecognizer.delegate = self;
    [self.view addGestureRecognizer:siriArrowRecognizer];
121 122 123
    [simultaneousGestureRecognizers addObject:siriArrowRecognizer];

    self.simultaneousGestureRecognizers = simultaneousGestureRecognizers;
124

125
    [super viewDidLoad];
126 127
}

128 129 130 131 132 133
- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    self.infoViewController = nil;
}

134 135
#pragma mark - view events

136 137 138 139
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

140
    self.audioView.hidden = YES;
141 142 143 144
    self.audioDescriptionTextView.hidden = YES;
    self.audioTitleLabel.hidden = YES;
    self.audioArtistLabel.hidden = YES;
    self.audioAlbumNameLabel.hidden = YES;
145 146 147 148 149
    self.audioArtworkImageView.image = [UIImage imageNamed:@"about-app-icon"];
    self.audioLargeBackgroundImageView.image = [UIImage imageNamed:@"about-app-icon"];
    self.audioArtworkImageView.animateImageSetting = YES;
    self.audioLargeBackgroundImageView.animateImageSetting = YES;

150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
    vpc.delegate = self;
    [vpc recoverPlaybackState];
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
    [vpc recoverDisplayedMetadata];
    vpc.videoOutputView = nil;
    vpc.videoOutputView = self.movieView;
}

- (void)viewWillDisappear:(BOOL)animated
{
    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
    if (vpc.videoOutputView == self.movieView) {
        vpc.videoOutputView = nil;
    }

172
    [vpc stopPlayback];
173

174 175
    [self stopAudioDescriptionAnimation];

176 177 178 179 180 181 182
    /* delete potentially downloaded subs */
    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString* tempSubsDirPath = [searchPaths[0] stringByAppendingPathComponent:@"tempsubs"];
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if ([fileManager fileExistsAtPath:tempSubsDirPath])
        [fileManager removeItemAtPath:tempSubsDirPath error:nil];

183
    [super viewWillDisappear:animated];
184
    [[NSNotificationCenter defaultCenter] removeObserver:self];
185 186
}

187 188 189 190 191
- (BOOL)canBecomeFirstResponder
{
    return YES;
}

Tobias's avatar
Tobias committed
192
#pragma mark - UIActions
Tobias's avatar
Tobias committed
193
- (void)playPausePressed
Tobias's avatar
Tobias committed
194
{
195 196
    [self showPlaybackControlsIfNeededForUserInteraction];

197 198
    [self setScanState:VLCPlayerScanStateNone];

Tobias's avatar
Tobias committed
199
    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
200
    if (self.transportBar.scrubbing) {
201
        [self selectButtonPressed];
202 203 204
    } else {
        [vpc playPause];
    }
Tobias's avatar
Tobias committed
205 206
}

Tobias's avatar
Tobias committed
207 208
- (void)panGesture:(UIPanGestureRecognizer *)panGestureRecognizer
{
209 210 211 212
    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
    NSInteger currentTitle = [vpc indexOfCurrentTitle];
    if (currentTitle < [vpc numberOfTitles]) {
        NSDictionary *title = [vpc titleDescriptionsDictAtIndex:currentTitle];
213 214 215 216 217
        if ([[title objectForKey:VLCTitleDescriptionIsMenu] boolValue]) {
            return;
        }
    }

218 219 220 221
    if (!self.canScrub) {
        return;
    }

222 223 224 225 226 227 228
    switch (panGestureRecognizer.state) {
        case UIGestureRecognizerStateCancelled:
        case UIGestureRecognizerStateFailed:
            return;
        default:
            break;
    }
229

Tobias's avatar
Tobias committed
230 231 232 233 234 235
    VLCTransportBar *bar = self.transportBar;

    UIView *view = self.view;
    CGPoint translation = [panGestureRecognizer translationInView:view];

    if (!bar.scrubbing) {
236
        if (ABS(translation.x) > 150.0) {
237 238 239 240 241
            if (self.isSeekable) {
                [self startScrubbing];
            } else {
                return;
            }
242
        } else if (translation.y > 200.0) {
243 244
            panGestureRecognizer.enabled = NO;
            panGestureRecognizer.enabled = YES;
245 246
            [self showInfoVCIfNotScrubbing];
            return;
Tobias's avatar
Tobias committed
247 248 249 250
        } else {
            return;
        }
    }
251

252
    [self showPlaybackControlsIfNeededForUserInteraction];
253
    [self setScanState:VLCPlayerScanStateNone];
254

Tobias's avatar
Tobias committed
255 256 257 258

    const CGFloat scaleFactor = 8.0;
    CGFloat fractionInView = translation.x/CGRectGetWidth(view.bounds)/scaleFactor;

259
    CGFloat scrubbingFraction = MAX(0.0, MIN(bar.scrubbingFraction + fractionInView,1.0));
260 261


262
    if (ABS(scrubbingFraction - bar.playbackFraction)<0.005) {
263 264 265 266 267 268 269 270 271 272 273 274 275
        scrubbingFraction = bar.playbackFraction;
    } else {
        translation.x = 0.0;
        [panGestureRecognizer setTranslation:translation inView:view];
    }

    [UIView animateWithDuration:0.3
                          delay:0.0
                        options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState
                     animations:^{
                         bar.scrubbingFraction = scrubbingFraction;
                     }
                     completion:nil];
276 277 278
    [self updateTimeLabelsForScrubbingFraction:scrubbingFraction];
}

279
- (void)selectButtonPressed
280
{
281 282 283 284
    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
    NSInteger currentTitle = [vpc indexOfCurrentTitle];
    if (currentTitle < [vpc numberOfTitles]) {
        NSDictionary *title = [vpc titleDescriptionsDictAtIndex:currentTitle];
285
        if ([[title objectForKey:VLCTitleDescriptionIsMenu] boolValue]) {
286
            [vpc performNavigationAction:VLCMediaPlaybackNavigationActionActivate];
287 288 289 290
            return;
        }
    }

291
    [self showPlaybackControlsIfNeededForUserInteraction];
292
    [self setScanState:VLCPlayerScanStateNone];
293

294 295
    VLCTransportBar *bar = self.transportBar;
    if (bar.scrubbing) {
296
        bar.playbackFraction = bar.scrubbingFraction;
297
        [self stopScrubbing];
298 299 300
        [vpc setPlaybackPosition:bar.scrubbingFraction];
    } else if(vpc.isPlaying) {
        [vpc playPause];
301 302 303 304 305 306 307 308 309 310 311 312
    }
}
- (void)menuButtonPressed:(UITapGestureRecognizer *)recognizer
{
    VLCTransportBar *bar = self.transportBar;
    if (bar.scrubbing) {
        [UIView animateWithDuration:0.3 animations:^{
            bar.scrubbingFraction = bar.playbackFraction;
            [bar layoutIfNeeded];
        }];
        [self updateTimeLabelsForScrubbingFraction:bar.playbackFraction];
        [self stopScrubbing];
313
        [self hidePlaybackControlsIfNeededAfterDelay];
314 315 316
    }
}

317
- (void)showInfoVCIfNotScrubbing
318
{
319 320 321 322
    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
    NSInteger currentTitle = [vpc indexOfCurrentTitle];
    if (currentTitle < [vpc numberOfTitles]) {
        NSDictionary *title = [vpc titleDescriptionsDictAtIndex:currentTitle];
323
        if ([[title objectForKey:VLCTitleDescriptionIsMenu] boolValue]) {
324
            [vpc performNavigationAction:VLCMediaPlaybackNavigationActionDown];
325 326 327 328
            return;
        }
    }

329 330 331 332
    if (self.transportBar.scrubbing) {
        return;
    }
    // TODO: configure with player info
333
    VLCPlaybackInfoTVViewController *infoViewController = self.infoViewController;
334 335 336 337 338

    // prevent repeated presentation when users repeatedly and quickly press the arrow button
    if (infoViewController.isBeingPresented) {
        return;
    }
339 340
    infoViewController.transitioningDelegate = self;
    [self presentViewController:infoViewController animated:YES completion:nil];
341
    [self animatePlaybackControlsToVisibility:NO];
342 343
}

344 345
- (void)handleIRPressUp
{
346 347 348 349
    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
    NSInteger currentTitle = [vpc indexOfCurrentTitle];
    if (currentTitle < [vpc numberOfTitles]) {
        NSDictionary *title = [vpc titleDescriptionsDictAtIndex:currentTitle];
350
        if ([[title objectForKey:VLCTitleDescriptionIsMenu] boolValue]) {
351
            [vpc performNavigationAction:VLCMediaPlaybackNavigationActionUp];
352 353 354 355
        }
    }
}

356 357
- (void)handleIRPressLeft
{
358 359 360 361
    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
    NSInteger currentTitle = [vpc indexOfCurrentTitle];
    if (currentTitle < [vpc numberOfTitles]) {
        NSDictionary *title = [vpc titleDescriptionsDictAtIndex:currentTitle];
362
        if ([[title objectForKey:VLCTitleDescriptionIsMenu] boolValue]) {
363
            [vpc performNavigationAction:VLCMediaPlaybackNavigationActionLeft];
364 365 366 367
            return;
        }
    }

368
    [self showPlaybackControlsIfNeededForUserInteraction];
369 370 371 372 373

    if (!self.isSeekable) {
        return;
    }

374 375 376 377 378 379 380 381 382 383 384
    BOOL paused = ![VLCPlaybackController sharedInstance].isPlaying;
    if (paused) {
        [self jumpBackward];
    } else
    {
        [self scanForwardPrevious];
    }
}

- (void)handleIRPressRight
{
385 386 387 388
    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
    NSInteger currentTitle = [vpc indexOfCurrentTitle];
    if (currentTitle < [vpc numberOfTitles]) {
        NSDictionary *title = [vpc titleDescriptionsDictAtIndex:currentTitle];
389
        if ([[title objectForKey:VLCTitleDescriptionIsMenu] boolValue]) {
390
            [vpc performNavigationAction:VLCMediaPlaybackNavigationActionRight];
391 392 393 394
            return;
        }
    }

395
    [self showPlaybackControlsIfNeededForUserInteraction];
396 397 398 399 400

    if (!self.isSeekable) {
        return;
    }

401 402 403 404 405 406 407 408
    BOOL paused = ![VLCPlaybackController sharedInstance].isPlaying;
    if (paused) {
        [self jumpForward];
    } else {
        [self scanForwardNext];
    }
}

409 410
- (void)handleSiriRemote:(VLCSiriRemoteGestureRecognizer *)recognizer
{
411 412 413 414
    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
    NSInteger currentTitle = [vpc indexOfCurrentTitle];
    if (currentTitle < [vpc numberOfTitles]) {
        NSDictionary *title = [vpc titleDescriptionsDictAtIndex:currentTitle];
415 416 417 418 419
        if ([[title objectForKey:VLCTitleDescriptionIsMenu] boolValue]) {
            switch (recognizer.state) {
                case UIGestureRecognizerStateBegan:
                case UIGestureRecognizerStateChanged:
                    if (recognizer.isLongPress) {
420
                        [vpc performNavigationAction:VLCMediaPlaybackNavigationActionActivate];
421
                        break;
422 423 424 425
                    }
                    break;
                case UIGestureRecognizerStateEnded:
                    if (recognizer.isClick && !recognizer.isLongPress) {
426
                        [vpc performNavigationAction:VLCMediaPlaybackNavigationActionActivate];
427 428 429
                    } else {
                        switch (recognizer.touchLocation) {
                            case VLCSiriRemoteTouchLocationLeft:
430
                                [vpc performNavigationAction:VLCMediaPlaybackNavigationActionLeft];
431 432
                                break;
                            case VLCSiriRemoteTouchLocationRight:
433
                                [vpc performNavigationAction:VLCMediaPlaybackNavigationActionRight];
434 435
                                break;
                            case VLCSiriRemoteTouchLocationUp:
436
                                [vpc performNavigationAction:VLCMediaPlaybackNavigationActionUp];
437 438
                                break;
                            case VLCSiriRemoteTouchLocationDown:
439
                                [vpc performNavigationAction:VLCMediaPlaybackNavigationActionDown];
440 441 442 443 444 445 446 447 448 449 450 451 452 453
                                break;
                            case VLCSiriRemoteTouchLocationUnknown:
                                break;
                        }
                    }
                    break;
                default:
                    break;
            }
            return;
        }
    }


454
    [self showPlaybackControlsIfNeededForUserInteraction];
455

456 457 458 459 460
    VLCTransportBarHint hint = self.transportBar.hint;
    switch (recognizer.state) {
        case UIGestureRecognizerStateBegan:
        case UIGestureRecognizerStateChanged:
            if (recognizer.isLongPress) {
461
                if (!self.isSeekable && recognizer.touchLocation == VLCSiriRemoteTouchLocationRight) {
462 463 464 465
                    [self setScanState:VLCPlayerScanStateForward2];
                    return;
                }
            } else {
466 467 468 469 470 471 472 473 474 475 476 477 478 479
                if (self.canJump) {
                    switch (recognizer.touchLocation) {
                        case VLCSiriRemoteTouchLocationLeft:
                            hint = VLCTransportBarHintJumpBackward10;
                            break;
                        case VLCSiriRemoteTouchLocationRight:
                            hint = VLCTransportBarHintJumpForward10;
                            break;
                        default:
                            hint = VLCTransportBarHintNone;
                            break;
                    }
                } else {
                    hint = VLCTransportBarHintNone;
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
                }
            }
            break;
        case UIGestureRecognizerStateEnded:
            if (recognizer.isClick && !recognizer.isLongPress) {
                [self handleSiriPressUpAtLocation:recognizer.touchLocation];
            }
            [self setScanState:VLCPlayerScanStateNone];
            break;
        case UIGestureRecognizerStateCancelled:
            hint = VLCTransportBarHintNone;
            [self setScanState:VLCPlayerScanStateNone];
            break;
        default:
            break;
    }
496
    self.transportBar.hint = self.isSeekable ? hint : VLCPlayerScanStateNone;
497 498 499 500
}

- (void)handleSiriPressUpAtLocation:(VLCSiriRemoteTouchLocation)location
{
501
    BOOL canJump = [self canJump];
502 503
    switch (location) {
        case VLCSiriRemoteTouchLocationLeft:
504
            if (canJump && self.isSeekable) {
505 506
                [self jumpBackward];
            }
507 508
            break;
        case VLCSiriRemoteTouchLocationRight:
509
            if (canJump && self.isSeekable) {
510 511
                [self jumpForward];
            }
512
            break;
513 514 515 516 517 518
        default:
            [self selectButtonPressed];
            break;
    }
}

519 520 521 522
#pragma mark -
static const NSInteger VLCJumpInterval = 10000; // 10 seconds
- (void)jumpForward
{
523 524
    NSAssert(self.isSeekable, @"Tried to seek while not media is not seekable.");

525 526
    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];

527
    if (vpc.isPlaying) {
528
        [self jumpInterval:VLCJumpInterval];
529 530 531
    } else {
        [self scrubbingJumpInterval:VLCJumpInterval];
    }
532 533 534
}
- (void)jumpBackward
{
535 536
    NSAssert(self.isSeekable, @"Tried to seek while not media is not seekable.");

537 538
    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];

539
    if (vpc.isPlaying) {
540
        [self jumpInterval:-VLCJumpInterval];
541 542 543
    } else {
        [self scrubbingJumpInterval:-VLCJumpInterval];
    }
544 545
}

546 547
- (void)jumpInterval:(NSInteger)interval
{
548 549
    NSAssert(self.isSeekable, @"Tried to seek while not media is not seekable.");

550 551 552 553 554 555 556
    NSInteger duration = [VLCPlaybackController sharedInstance].mediaDuration;
    if (duration==0) {
        return;
    }
    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];

    CGFloat intervalFraction = ((CGFloat)interval)/((CGFloat)duration);
557
    CGFloat currentFraction = vpc.playbackPosition;
558
    currentFraction += intervalFraction;
559
    vpc.playbackPosition = currentFraction;
560 561
}

562
- (void)scrubbingJumpInterval:(NSInteger)interval
563
{
564 565
    NSAssert(self.isSeekable, @"Tried to seek while not media is not seekable.");

566 567 568 569 570 571 572 573 574 575 576 577 578 579 580
    NSInteger duration = [VLCPlaybackController sharedInstance].mediaDuration;
    if (duration==0) {
        return;
    }
    CGFloat intervalFraction = ((CGFloat)interval)/((CGFloat)duration);
    VLCTransportBar *bar = self.transportBar;
    bar.scrubbing = YES;
    CGFloat currentFraction = bar.scrubbingFraction;
    currentFraction += intervalFraction;
    bar.scrubbingFraction = currentFraction;
    [self updateTimeLabelsForScrubbingFraction:currentFraction];
}

- (void)scanForwardNext
{
581 582
    NSAssert(self.isSeekable, @"Tried to seek while not media is not seekable.");

583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600
    VLCPlayerScanState nextState = self.scanState;
    switch (self.scanState) {
        case VLCPlayerScanStateNone:
            nextState = VLCPlayerScanStateForward2;
            break;
        case VLCPlayerScanStateForward2:
            nextState = VLCPlayerScanStateForward4;
            break;
        case VLCPlayerScanStateForward4:
            return;
        default:
            return;
    }
    [self setScanState:nextState];
}

- (void)scanForwardPrevious
{
601 602
    NSAssert(self.isSeekable, @"Tried to seek while not media is not seekable.");

603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621
    VLCPlayerScanState nextState = self.scanState;
    switch (self.scanState) {
        case VLCPlayerScanStateNone:
            return;
        case VLCPlayerScanStateForward2:
            nextState = VLCPlayerScanStateNone;
            break;
        case VLCPlayerScanStateForward4:
            nextState = VLCPlayerScanStateForward2;
            break;
        default:
            return;
    }
    [self setScanState:nextState];
}


- (void)setScanState:(VLCPlayerScanState)scanState
{
622 623 624 625
    if (_scanState == scanState) {
        return;
    }

626 627
    NSAssert(self.isSeekable || scanState == VLCPlayerScanStateNone, @"Tried to seek while media not seekable.");

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
    if (_scanState == VLCPlayerScanStateNone) {
        self.scanSavedPlaybackRate = @([VLCPlaybackController sharedInstance].playbackRate);
    }
    _scanState = scanState;
    float rate = 1.0;
    VLCTransportBarHint hint = VLCTransportBarHintNone;
    switch (scanState) {
        case VLCPlayerScanStateForward2:
            rate = 2.0;
            hint = VLCTransportBarHintScanForward;
            break;
        case VLCPlayerScanStateForward4:
            rate = 4.0;
            hint = VLCTransportBarHintScanForward;
            break;

        case VLCPlayerScanStateNone:
        default:
            rate = self.scanSavedPlaybackRate.floatValue ?: 1.0;
            hint = VLCTransportBarHintNone;
            self.scanSavedPlaybackRate = nil;
            break;
    }

    [VLCPlaybackController sharedInstance].playbackRate = rate;
    [self.transportBar setHint:hint];
}
655 656 657

- (BOOL)isSeekable
{
658
    return [[VLCPlaybackController sharedInstance] isSeekable];
659 660
}

661 662 663 664 665 666 667 668 669 670 671
- (BOOL)canJump
{
    // to match the AVPlayerViewController behavior only allow jumping when playing.
    return [VLCPlaybackController sharedInstance].isPlaying;
}
- (BOOL)canScrub
{
    // to match the AVPlayerViewController behavior only allow scrubbing when paused.
    return ![VLCPlaybackController sharedInstance].isPlaying;
}

672
#pragma mark -
673 674 675 676 677

- (void)updateTimeLabelsForScrubbingFraction:(CGFloat)scrubbingFraction
{
    VLCTransportBar *bar = self.transportBar;
    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
Tobias's avatar
Tobias committed
678
    // MAX 1, _ is ugly hack to prevent --:-- instead of 00:00
679
    int scrubbingTimeInt = MAX(1,vpc.mediaDuration*scrubbingFraction);
Tobias's avatar
Tobias committed
680 681
    VLCTime *scrubbingTime = [VLCTime timeWithInt:scrubbingTimeInt];
    bar.markerTimeLabel.text = [scrubbingTime stringValue];
682
    VLCTime *remainingTime = [VLCTime timeWithInt:-(int)(vpc.mediaDuration-scrubbingTime.intValue)];
Tobias's avatar
Tobias committed
683 684 685
    bar.remainingTimeLabel.text = [remainingTime stringValue];
}

686
- (void)startScrubbing
Tobias's avatar
Tobias committed
687
{
688 689
    NSAssert(self.isSeekable, @"Tried to seek while not media is not seekable.");

690
    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
691 692
    self.transportBar.scrubbing = YES;
    [self updateDimmingView];
693 694 695
    if (vpc.isPlaying) {
        [vpc playPause];
    }
696 697 698 699 700
}
- (void)stopScrubbing
{
    self.transportBar.scrubbing = NO;
    [self updateDimmingView];
701 702
    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
    [vpc playPause];
703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730
}

- (void)updateDimmingView
{
    BOOL shouldBeVisible = self.transportBar.scrubbing;
    BOOL isVisible = self.dimmingView.alpha == 1.0;
    if (shouldBeVisible != isVisible) {
        [UIView animateWithDuration:0.3 animations:^{
            self.dimmingView.alpha = shouldBeVisible ? 1.0 : 0.0;
        }];
    }
}

- (void)updateActivityIndicatorForState:(VLCMediaPlayerState)state {
    UIActivityIndicatorView *indicator = self.activityIndicator;
    switch (state) {
        case VLCMediaPlayerStateBuffering:
            if (!indicator.isAnimating) {
                self.activityIndicator.alpha = 1.0;
                [self.activityIndicator startAnimating];
            }
            break;
        default:
            if (indicator.isAnimating) {
                [self.activityIndicator stopAnimating];
                self.activityIndicator.alpha = 0.0;
            }
            break;
Tobias's avatar
Tobias committed
731 732 733
    }
}

734 735 736 737 738 739 740 741 742 743 744
#pragma mark - PlaybackControls

- (void)fireHidePlaybackControlsIfNotPlayingTimer:(NSTimer *)timer
{
    BOOL playing = [[VLCPlaybackController sharedInstance] isPlaying];
    if (playing) {
        [self animatePlaybackControlsToVisibility:NO];
    }
}
- (void)showPlaybackControlsIfNeededForUserInteraction
{
745 746 747 748
    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
    NSInteger currentTitle = [vpc indexOfCurrentTitle];
    if (currentTitle < [vpc numberOfTitles]) {
        NSDictionary *title = [vpc titleDescriptionsDictAtIndex:currentTitle];
749 750 751 752 753
        if ([[title objectForKey:VLCTitleDescriptionIsMenu] boolValue]) {
            return;
        }
    }

754 755
    if (self.bottomOverlayView.alpha == 0.0) {
        [self animatePlaybackControlsToVisibility:YES];
756 757 758 759 760

        // We need an additional update here because in some cases (e.g. when the playback was
        // paused or started buffering), the transport bar is only updated when it is visible
        // and if the playback is interrupted, no updates of the transport bar are triggered.
        [self updateTransportBarPosition];
761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790
    }
    [self hidePlaybackControlsIfNeededAfterDelay];
}
- (void)hidePlaybackControlsIfNeededAfterDelay
{
    self.hidePlaybackControlsViewAfterDeleayTimer = [NSTimer scheduledTimerWithTimeInterval:3.0
                                                                                     target:self
                                                                                   selector:@selector(fireHidePlaybackControlsIfNotPlayingTimer:)
                                                                                   userInfo:nil repeats:NO];
}


- (void)animatePlaybackControlsToVisibility:(BOOL)visible
{
    NSTimeInterval duration = visible ? 0.3 : 1.0;

    CGFloat alpha = visible ? 1.0 : 0.0;
    [UIView animateWithDuration:duration
                     animations:^{
                         self.bottomOverlayView.alpha = alpha;
                     }];
}


#pragma mark - Properties
- (void)setHidePlaybackControlsViewAfterDeleayTimer:(NSTimer *)hidePlaybackControlsViewAfterDeleayTimer {
    [_hidePlaybackControlsViewAfterDeleayTimer invalidate];
    _hidePlaybackControlsViewAfterDeleayTimer = hidePlaybackControlsViewAfterDeleayTimer;
}

791 792 793 794 795 796 797 798
- (VLCPlaybackInfoTVViewController *)infoViewController
{
    if (!_infoViewController) {
        _infoViewController = [[VLCPlaybackInfoTVViewController alloc] initWithNibName:nil bundle:nil];
    }
    return _infoViewController;
}

799 800


801 802
#pragma mark - playback controller delegation

803 804
- (void)prepareForMediaPlayback:(VLCPlaybackController *)controller
{
805
    self.audioView.hidden = YES;
806 807
}

808
- (void)playbackDidStop
809
{
810
    [self dismissViewControllerAnimated:YES completion:nil];
811 812 813 814 815 816 817 818
}

- (void)mediaPlayerStateChanged:(VLCMediaPlayerState)currentState
                      isPlaying:(BOOL)isPlaying
currentMediaHasTrackToChooseFrom:(BOOL)currentMediaHasTrackToChooseFrom
        currentMediaHasChapters:(BOOL)currentMediaHasChapters
          forPlaybackController:(VLCPlaybackController *)controller
{
819

820
    [self updateActivityIndicatorForState:currentState];
821 822

    if (controller.isPlaying) {
823 824 825 826 827
        // we sometimes don't set the vout correctly if playback stops and restarts without dismising and redisplaying the VC
        // hence, manually reset the vout container here if it doesn't have sufficient children
        if (self.movieView.subviews.count < 2) {
            controller.videoOutputView = self.movieView;
        }
828

829 830 831 832 833
        if (!self.bufferingLabel.hidden) {
            [UIView animateWithDuration:.3 animations:^{
                self.bufferingLabel.hidden = YES;
            }];
        }
834
    }
835 836 837 838 839 840 841 842 843
}

- (void)displayMetadataForPlaybackController:(VLCPlaybackController *)controller
                                       title:(NSString *)title
                                     artwork:(UIImage *)artwork
                                      artist:(NSString *)artist
                                       album:(NSString *)album
                                   audioOnly:(BOOL)audioOnly
{
844
    self.titleLabel.text = title;
845 846

    if (audioOnly) {
847
        self.audioArtworkImageView.image = nil;
848
        self.audioDescriptionTextView.hidden = YES;
849
        [self stopAudioDescriptionAnimation];
850 851

        if (artist != nil && album != nil) {
852 853
            [UIView animateWithDuration:.3 animations:^{
                self.audioArtistLabel.text = artist;
854
                self.audioArtistLabel.hidden = NO;
855
                self.audioAlbumNameLabel.text = album;
856
                self.audioAlbumNameLabel.hidden = NO;
857
            }];
858 859
            APLog(@"Audio-only track meta changed, tracing artist '%@' and album '%@'", artist, album);
        } else if (artist != nil) {
860 861
            [UIView animateWithDuration:.3 animations:^{
                self.audioArtistLabel.text = artist;
862 863
                self.audioArtistLabel.hidden = NO;
                self.audioAlbumNameLabel.hidden = YES;
864
            }];
865 866 867 868
            APLog(@"Audio-only track meta changed, tracing artist '%@'", artist);
        } else if (title != nil) {
            NSRange deviderRange = [title rangeOfString:@" - "];
            if (deviderRange.length != 0) { // for radio stations, all we have is "ARTIST - TITLE"
869 870
                artist = [title substringToIndex:deviderRange.location];
                title = [title substringFromIndex:deviderRange.location + deviderRange.length];
871
            }
872 873 874
            APLog(@"Audio-only track meta changed, tracing artist '%@'", artist);
            [UIView animateWithDuration:.3 animations:^{
                self.audioArtistLabel.text = artist;
875 876
                self.audioArtistLabel.hidden = NO;
                self.audioAlbumNameLabel.hidden = YES;
877
            }];
878
        }
879
        if (![self.lastArtist isEqualToString:artist]) {
880 881 882 883 884
            UIImage *dummyImage = [UIImage imageNamed:@"about-app-icon"];
            [UIView animateWithDuration:.3 animations:^{
                self.audioArtworkImageView.image = dummyImage;
                self.audioLargeBackgroundImageView.image = dummyImage;
            }];
885 886
        }
        self.lastArtist = artist;
887
        self.audioTitleLabel.text = title;
888
        self.audioTitleLabel.hidden = NO;
889

890 891 892
        [UIView animateWithDuration:0.3 animations:^{
            self.audioView.hidden = NO;
        }];
893 894 895 896
    } else if (!self.audioView.hidden) {
        self.audioView.hidden = YES;
        self.audioArtworkImageView.image = nil;
        [self.audioLargeBackgroundImageView stopAnimating];
897
    }
898 899
}

900 901
#pragma mark -

902
- (void)updateTransportBarPosition
903
{
904
    VLCPlaybackController *controller = [VLCPlaybackController sharedInstance];
905
    VLCTransportBar *transportBar = self.transportBar;
906 907 908
    transportBar.remainingTimeLabel.text = [[controller remainingTime] stringValue];
    transportBar.markerTimeLabel.text = [[controller playedTime] stringValue];
    transportBar.playbackFraction = controller.playbackPosition;
909 910 911 912
}

- (void)playbackPositionUpdated:(VLCPlaybackController *)controller
{
913 914
    // FIXME: hard coded state since the state in mediaPlayer is incorrectly still buffering
    [self updateActivityIndicatorForState:VLCMediaPlayerStatePlaying];
915

916
    if (self.bottomOverlayView.alpha != 0.0) {
917
        [self updateTransportBarPosition];
918
    }
919 920
}

921 922 923 924 925 926 927 928
#pragma mark - gesture recognizer delegate

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    if ([gestureRecognizer.allowedPressTypes containsObject:@(UIPressTypeMenu)]) {
        return self.transportBar.scrubbing;
    }
    return YES;
}
929 930
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
931
    return [self.simultaneousGestureRecognizers containsObject:gestureRecognizer];
932
}
933

934
- (void)scrollAudioDescriptionAnimationToTop
935
{
936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970
    [self stopAudioDescriptionAnimation];
    [self.audioDescriptionTextView setContentOffset:CGPointZero animated:YES];
    [self startAudioDescriptionAnimation];
}

- (void)startAudioDescriptionAnimation
{
    [self.audioDescriptionScrollTimer invalidate];
    self.audioDescriptionScrollTimer = [NSTimer scheduledTimerWithTimeInterval:2.0
                                                                        target:self
                                                                      selector:@selector(animateAudioDescription)
                                                                      userInfo:nil repeats:NO];
}

- (void)stopAudioDescriptionAnimation
{
    [self.audioDescriptionScrollTimer invalidate];
    self.audioDescriptionScrollTimer = nil;
    [self.displayLink invalidate];
    self.displayLink = nil;
}

- (void)animateAudioDescription
{
    [self.displayLink invalidate];
    self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkTriggered:)];
    [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}

- (void)displayLinkTriggered:(CADisplayLink*)link
{
    UIScrollView *scrollView = self.audioDescriptionTextView;
    CGFloat viewHeight = CGRectGetHeight(scrollView.frame);
    CGFloat maxOffsetY = scrollView.contentSize.height - viewHeight;

971
    CFTimeInterval secondsPerPage = 8.0;
972 973 974 975 976 977 978 979 980
    CGFloat offset = link.duration/secondsPerPage * viewHeight;

    CGFloat newYOffset = scrollView.contentOffset.y + offset;

    if (newYOffset > maxOffsetY+viewHeight) {
        scrollView.contentOffset = CGPointMake(0, -viewHeight);
    } else {
        scrollView.contentOffset = CGPointMake(0, newYOffset);
    }
981 982
}

983
@end
984 985 986 987 988 989 990 991 992 993 994 995 996


@implementation VLCFullscreenMovieTVViewController (UIViewControllerTransitioningDelegate)

- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
    return [[VLCPlaybackInfoTVTransitioningAnimator alloc] init];
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
    return [[VLCPlaybackInfoTVTransitioningAnimator alloc] init];
}
@end