1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123 |
- /*****************************************************************************
- * 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"
- #import "VLCPlaybackInfoTVViewController.h"
- #import "VLCPlaybackInfoTVAnimators.h"
- #import "VLCIRTVTapGestureRecognizer.h"
- #import "VLCHTTPUploaderController.h"
- #import "VLCSiriRemoteGestureRecognizer.h"
- #import "MetaDataFetcherKit.h"
- #import "VLCNetworkImageView.h"
- typedef NS_ENUM(NSInteger, VLCPlayerScanState)
- {
- VLCPlayerScanStateNone,
- VLCPlayerScanStateForward2,
- VLCPlayerScanStateForward4,
- };
- @interface VLCFullscreenMovieTVViewController (UIViewControllerTransitioningDelegate) <UIViewControllerTransitioningDelegate, UIGestureRecognizerDelegate>
- @end
- @interface VLCFullscreenMovieTVViewController () <MDFHatchetFetcherDataRecipient>
- @property (nonatomic) CADisplayLink *displayLink;
- @property (nonatomic) NSTimer *audioDescriptionScrollTimer;
- @property (nonatomic) NSTimer *hidePlaybackControlsViewAfterDeleayTimer;
- @property (nonatomic) VLCPlaybackInfoTVViewController *infoViewController;
- @property (nonatomic) NSNumber *scanSavedPlaybackRate;
- @property (nonatomic) VLCPlayerScanState scanState;
- @property (nonatomic) MDFHatchetFetcher *audioMetaDataFetcher;
- @property (nonatomic) NSString *lastArtist;
- @property (nonatomic, readonly, getter=isSeekable) BOOL seekable;
- @property (nonatomic) NSSet<UIGestureRecognizer *> *simultaneousGestureRecognizers;
- @end
- @implementation VLCFullscreenMovieTVViewController
- + (instancetype)fullscreenMovieTVViewController
- {
- return [[self alloc] initWithNibName:nil bundle:nil];
- }
- - (void)viewDidLoad
- {
- self.extendedLayoutIncludesOpaqueBars = YES;
- self.edgesForExtendedLayout = UIRectEdgeAll;
- NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
- [center addObserver:self
- selector:@selector(playbackDidStop)
- name:VLCPlaybackControllerPlaybackDidStop
- object:nil];
- [center addObserver:self
- selector:@selector(playbackDidStop)
- name:VLCPlaybackControllerPlaybackDidFail
- object:nil];
- _movieView.userInteractionEnabled = NO;
- self.titleLabel.text = @"";
- self.transportBar.bufferStartFraction = 0.0;
- self.transportBar.bufferEndFraction = 1.0;
- self.transportBar.playbackFraction = 0.0;
- self.transportBar.scrubbingFraction = 0.0;
- self.dimmingView.alpha = 0.0;
- self.bottomOverlayView.alpha = 0.0;
- self.bufferingLabel.text = NSLocalizedString(@"PLEASE_WAIT", nil);
- NSMutableSet<UIGestureRecognizer *> *simultaneousGestureRecognizers = [NSMutableSet set];
- // Panning and Swiping
- UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGesture:)];
- panGestureRecognizer.delegate = self;
- [self.view addGestureRecognizer:panGestureRecognizer];
- [simultaneousGestureRecognizers addObject:panGestureRecognizer];
- // Button presses
- UITapGestureRecognizer *playpauseGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(playPausePressed)];
- playpauseGesture.allowedPressTypes = @[@(UIPressTypePlayPause)];
- [self.view addGestureRecognizer:playpauseGesture];
- UITapGestureRecognizer *menuTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(menuButtonPressed:)];
- menuTapGestureRecognizer.allowedPressTypes = @[@(UIPressTypeMenu)];
- menuTapGestureRecognizer.delegate = self;
- [self.view addGestureRecognizer:menuTapGestureRecognizer];
- // IR only recognizer
- UITapGestureRecognizer *upArrowRecognizer = [[VLCIRTVTapGestureRecognizer alloc] initWithTarget:self action:@selector(handleIRPressUp)];
- upArrowRecognizer.allowedPressTypes = @[@(UIPressTypeUpArrow)];
- [self.view addGestureRecognizer:upArrowRecognizer];
- UITapGestureRecognizer *downArrowRecognizer = [[VLCIRTVTapGestureRecognizer alloc] initWithTarget:self action:@selector(showInfoVCIfNotScrubbing)];
- downArrowRecognizer.allowedPressTypes = @[@(UIPressTypeDownArrow)];
- [self.view addGestureRecognizer:downArrowRecognizer];
- 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];
- // Siri remote arrow presses
- VLCSiriRemoteGestureRecognizer *siriArrowRecognizer = [[VLCSiriRemoteGestureRecognizer alloc] initWithTarget:self action:@selector(handleSiriRemote:)];
- siriArrowRecognizer.delegate = self;
- [self.view addGestureRecognizer:siriArrowRecognizer];
- [simultaneousGestureRecognizers addObject:siriArrowRecognizer];
- self.simultaneousGestureRecognizers = simultaneousGestureRecognizers;
- [super viewDidLoad];
- }
- - (void)didReceiveMemoryWarning
- {
- [super didReceiveMemoryWarning];
- self.infoViewController = nil;
- }
- #pragma mark - view events
- - (void)viewWillAppear:(BOOL)animated
- {
- [super viewWillAppear:animated];
- self.audioView.hidden = YES;
- self.audioDescriptionTextView.hidden = YES;
- self.audioTitleLabel.hidden = YES;
- self.audioArtistLabel.hidden = YES;
- self.audioAlbumNameLabel.hidden = YES;
- self.audioArtworkImageView.image = [UIImage imageNamed:@"about-app-icon"];
- self.audioLargeBackgroundImageView.image = [UIImage imageNamed:@"about-app-icon"];
- self.audioArtworkImageView.animateImageSetting = YES;
- self.audioLargeBackgroundImageView.animateImageSetting = YES;
- 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;
- }
- [vpc stopPlayback];
- [self stopAudioDescriptionAnimation];
- /* 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];
- [super viewWillDisappear:animated];
- [[NSNotificationCenter defaultCenter] removeObserver:self];
- }
- - (BOOL)canBecomeFirstResponder
- {
- return YES;
- }
- #pragma mark - UIActions
- - (void)playPausePressed
- {
- [self showPlaybackControlsIfNeededForUserInteraction];
- [self setScanState:VLCPlayerScanStateNone];
- VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
- if (self.transportBar.scrubbing) {
- [self selectButtonPressed];
- } else {
- [vpc playPause];
- }
- }
- - (void)panGesture:(UIPanGestureRecognizer *)panGestureRecognizer
- {
- VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
- NSInteger currentTitle = [vpc indexOfCurrentTitle];
- if (currentTitle < [vpc numberOfTitles]) {
- NSDictionary *title = [vpc titleDescriptionsDictAtIndex:currentTitle];
- if ([[title objectForKey:VLCTitleDescriptionIsMenu] boolValue]) {
- return;
- }
- }
- if (!self.canScrub) {
- return;
- }
- switch (panGestureRecognizer.state) {
- case UIGestureRecognizerStateCancelled:
- case UIGestureRecognizerStateFailed:
- return;
- default:
- break;
- }
- VLCTransportBar *bar = self.transportBar;
- UIView *view = self.view;
- CGPoint translation = [panGestureRecognizer translationInView:view];
- if (!bar.scrubbing) {
- if (ABS(translation.x) > 150.0) {
- if (self.isSeekable) {
- [self startScrubbing];
- } else {
- return;
- }
- } else if (translation.y > 200.0) {
- panGestureRecognizer.enabled = NO;
- panGestureRecognizer.enabled = YES;
- [self showInfoVCIfNotScrubbing];
- return;
- } else {
- return;
- }
- }
- [self showPlaybackControlsIfNeededForUserInteraction];
- [self setScanState:VLCPlayerScanStateNone];
- const CGFloat scaleFactor = 8.0;
- CGFloat fractionInView = translation.x/CGRectGetWidth(view.bounds)/scaleFactor;
- CGFloat scrubbingFraction = MAX(0.0, MIN(bar.scrubbingFraction + fractionInView,1.0));
- if (ABS(scrubbingFraction - bar.playbackFraction)<0.005) {
- 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];
- [self updateTimeLabelsForScrubbingFraction:scrubbingFraction];
- }
- - (void)selectButtonPressed
- {
- VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
- NSInteger currentTitle = [vpc indexOfCurrentTitle];
- if (currentTitle < [vpc numberOfTitles]) {
- NSDictionary *title = [vpc titleDescriptionsDictAtIndex:currentTitle];
- if ([[title objectForKey:VLCTitleDescriptionIsMenu] boolValue]) {
- [vpc performNavigationAction:VLCMediaPlaybackNavigationActionActivate];
- return;
- }
- }
- [self showPlaybackControlsIfNeededForUserInteraction];
- [self setScanState:VLCPlayerScanStateNone];
- VLCTransportBar *bar = self.transportBar;
- if (bar.scrubbing) {
- bar.playbackFraction = bar.scrubbingFraction;
- [self stopScrubbing];
- [vpc setPlaybackPosition:bar.scrubbingFraction];
- } else if(vpc.isPlaying) {
- [vpc playPause];
- }
- }
- - (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];
- [self hidePlaybackControlsIfNeededAfterDelay];
- }
- }
- - (void)showInfoVCIfNotScrubbing
- {
- VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
- NSInteger currentTitle = [vpc indexOfCurrentTitle];
- if (currentTitle < [vpc numberOfTitles]) {
- NSDictionary *title = [vpc titleDescriptionsDictAtIndex:currentTitle];
- if ([[title objectForKey:VLCTitleDescriptionIsMenu] boolValue]) {
- [vpc performNavigationAction:VLCMediaPlaybackNavigationActionDown];
- return;
- }
- }
- if (self.transportBar.scrubbing) {
- return;
- }
- // TODO: configure with player info
- VLCPlaybackInfoTVViewController *infoViewController = self.infoViewController;
- // prevent repeated presentation when users repeatedly and quickly press the arrow button
- if (infoViewController.isBeingPresented) {
- return;
- }
- infoViewController.transitioningDelegate = self;
- [self presentViewController:infoViewController animated:YES completion:nil];
- [self animatePlaybackControlsToVisibility:NO];
- }
- - (void)handleIRPressUp
- {
- VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
- NSInteger currentTitle = [vpc indexOfCurrentTitle];
- if (currentTitle < [vpc numberOfTitles]) {
- NSDictionary *title = [vpc titleDescriptionsDictAtIndex:currentTitle];
- if ([[title objectForKey:VLCTitleDescriptionIsMenu] boolValue]) {
- [vpc performNavigationAction:VLCMediaPlaybackNavigationActionUp];
- }
- }
- }
- - (void)handleIRPressLeft
- {
- VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
- NSInteger currentTitle = [vpc indexOfCurrentTitle];
- if (currentTitle < [vpc numberOfTitles]) {
- NSDictionary *title = [vpc titleDescriptionsDictAtIndex:currentTitle];
- if ([[title objectForKey:VLCTitleDescriptionIsMenu] boolValue]) {
- [vpc performNavigationAction:VLCMediaPlaybackNavigationActionLeft];
- return;
- }
- }
- [self showPlaybackControlsIfNeededForUserInteraction];
- if (!self.isSeekable) {
- return;
- }
- BOOL paused = ![VLCPlaybackController sharedInstance].isPlaying;
- if (paused) {
- [self jumpBackward];
- } else
- {
- [self scanForwardPrevious];
- }
- }
- - (void)handleIRPressRight
- {
- VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
- NSInteger currentTitle = [vpc indexOfCurrentTitle];
- if (currentTitle < [vpc numberOfTitles]) {
- NSDictionary *title = [vpc titleDescriptionsDictAtIndex:currentTitle];
- if ([[title objectForKey:VLCTitleDescriptionIsMenu] boolValue]) {
- [vpc performNavigationAction:VLCMediaPlaybackNavigationActionRight];
- return;
- }
- }
- [self showPlaybackControlsIfNeededForUserInteraction];
- if (!self.isSeekable) {
- return;
- }
- BOOL paused = ![VLCPlaybackController sharedInstance].isPlaying;
- if (paused) {
- [self jumpForward];
- } else {
- [self scanForwardNext];
- }
- }
- - (void)handleSiriRemote:(VLCSiriRemoteGestureRecognizer *)recognizer
- {
- VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
- NSInteger currentTitle = [vpc indexOfCurrentTitle];
- if (currentTitle < [vpc numberOfTitles]) {
- NSDictionary *title = [vpc titleDescriptionsDictAtIndex:currentTitle];
- if ([[title objectForKey:VLCTitleDescriptionIsMenu] boolValue]) {
- switch (recognizer.state) {
- case UIGestureRecognizerStateBegan:
- case UIGestureRecognizerStateChanged:
- if (recognizer.isLongPress) {
- [vpc performNavigationAction:VLCMediaPlaybackNavigationActionActivate];
- break;
- }
- break;
- case UIGestureRecognizerStateEnded:
- if (recognizer.isClick && !recognizer.isLongPress) {
- [vpc performNavigationAction:VLCMediaPlaybackNavigationActionActivate];
- } else {
- switch (recognizer.touchLocation) {
- case VLCSiriRemoteTouchLocationLeft:
- [vpc performNavigationAction:VLCMediaPlaybackNavigationActionLeft];
- break;
- case VLCSiriRemoteTouchLocationRight:
- [vpc performNavigationAction:VLCMediaPlaybackNavigationActionRight];
- break;
- case VLCSiriRemoteTouchLocationUp:
- [vpc performNavigationAction:VLCMediaPlaybackNavigationActionUp];
- break;
- case VLCSiriRemoteTouchLocationDown:
- [vpc performNavigationAction:VLCMediaPlaybackNavigationActionDown];
- break;
- case VLCSiriRemoteTouchLocationUnknown:
- break;
- }
- }
- break;
- default:
- break;
- }
- return;
- }
- }
- [self showPlaybackControlsIfNeededForUserInteraction];
- VLCTransportBarHint hint = self.transportBar.hint;
- switch (recognizer.state) {
- case UIGestureRecognizerStateBegan:
- case UIGestureRecognizerStateChanged:
- if (recognizer.isLongPress) {
- if (!self.isSeekable && recognizer.touchLocation == VLCSiriRemoteTouchLocationRight) {
- [self setScanState:VLCPlayerScanStateForward2];
- return;
- }
- } else {
- if (self.canJump) {
- switch (recognizer.touchLocation) {
- case VLCSiriRemoteTouchLocationLeft:
- hint = VLCTransportBarHintJumpBackward10;
- break;
- case VLCSiriRemoteTouchLocationRight:
- hint = VLCTransportBarHintJumpForward10;
- break;
- default:
- hint = VLCTransportBarHintNone;
- break;
- }
- } else {
- hint = VLCTransportBarHintNone;
- }
- }
- 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;
- }
- self.transportBar.hint = self.isSeekable ? hint : VLCPlayerScanStateNone;
- }
- - (void)handleSiriPressUpAtLocation:(VLCSiriRemoteTouchLocation)location
- {
- BOOL canJump = [self canJump];
- switch (location) {
- case VLCSiriRemoteTouchLocationLeft:
- if (canJump && self.isSeekable) {
- [self jumpBackward];
- }
- break;
- case VLCSiriRemoteTouchLocationRight:
- if (canJump && self.isSeekable) {
- [self jumpForward];
- }
- break;
- default:
- [self selectButtonPressed];
- break;
- }
- }
- #pragma mark -
- static const NSInteger VLCJumpInterval = 10000; // 10 seconds
- - (void)jumpForward
- {
- NSAssert(self.isSeekable, @"Tried to seek while not media is not seekable.");
- VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
- if (vpc.isPlaying) {
- [self jumpInterval:VLCJumpInterval];
- } else {
- [self scrubbingJumpInterval:VLCJumpInterval];
- }
- }
- - (void)jumpBackward
- {
- NSAssert(self.isSeekable, @"Tried to seek while not media is not seekable.");
- VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
- if (vpc.isPlaying) {
- [self jumpInterval:-VLCJumpInterval];
- } else {
- [self scrubbingJumpInterval:-VLCJumpInterval];
- }
- }
- - (void)jumpInterval:(NSInteger)interval
- {
- NSAssert(self.isSeekable, @"Tried to seek while not media is not seekable.");
- NSInteger duration = [VLCPlaybackController sharedInstance].mediaDuration;
- if (duration==0) {
- return;
- }
- VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
- CGFloat intervalFraction = ((CGFloat)interval)/((CGFloat)duration);
- CGFloat currentFraction = vpc.playbackPosition;
- currentFraction += intervalFraction;
- vpc.playbackPosition = currentFraction;
- }
- - (void)scrubbingJumpInterval:(NSInteger)interval
- {
- NSAssert(self.isSeekable, @"Tried to seek while not media is not seekable.");
- 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
- {
- NSAssert(self.isSeekable, @"Tried to seek while not media is not seekable.");
- 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
- {
- NSAssert(self.isSeekable, @"Tried to seek while not media is not seekable.");
- 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
- {
- if (_scanState == scanState) {
- return;
- }
- NSAssert(self.isSeekable || scanState == VLCPlayerScanStateNone, @"Tried to seek while media not seekable.");
- 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];
- }
- - (BOOL)isSeekable
- {
- return [[VLCPlaybackController sharedInstance] isSeekable];
- }
- - (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;
- }
- #pragma mark -
- - (void)updateTimeLabelsForScrubbingFraction:(CGFloat)scrubbingFraction
- {
- VLCTransportBar *bar = self.transportBar;
- VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
- // MAX 1, _ is ugly hack to prevent --:-- instead of 00:00
- int scrubbingTimeInt = MAX(1,vpc.mediaDuration*scrubbingFraction);
- VLCTime *scrubbingTime = [VLCTime timeWithInt:scrubbingTimeInt];
- bar.markerTimeLabel.text = [scrubbingTime stringValue];
- VLCTime *remainingTime = [VLCTime timeWithInt:-(int)(vpc.mediaDuration-scrubbingTime.intValue)];
- bar.remainingTimeLabel.text = [remainingTime stringValue];
- }
- - (void)startScrubbing
- {
- NSAssert(self.isSeekable, @"Tried to seek while not media is not seekable.");
- VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
- self.transportBar.scrubbing = YES;
- [self updateDimmingView];
- if (vpc.isPlaying) {
- [vpc playPause];
- }
- }
- - (void)stopScrubbing
- {
- self.transportBar.scrubbing = NO;
- [self updateDimmingView];
- VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
- [vpc playPause];
- }
- - (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;
- }
- }
- #pragma mark - PlaybackControls
- - (void)fireHidePlaybackControlsIfNotPlayingTimer:(NSTimer *)timer
- {
- BOOL playing = [[VLCPlaybackController sharedInstance] isPlaying];
- if (playing) {
- [self animatePlaybackControlsToVisibility:NO];
- }
- }
- - (void)showPlaybackControlsIfNeededForUserInteraction
- {
- VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
- NSInteger currentTitle = [vpc indexOfCurrentTitle];
- if (currentTitle < [vpc numberOfTitles]) {
- NSDictionary *title = [vpc titleDescriptionsDictAtIndex:currentTitle];
- if ([[title objectForKey:VLCTitleDescriptionIsMenu] boolValue]) {
- return;
- }
- }
- if (self.bottomOverlayView.alpha == 0.0) {
- [self animatePlaybackControlsToVisibility:YES];
- // 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];
- }
- [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;
- }
- - (VLCPlaybackInfoTVViewController *)infoViewController
- {
- if (!_infoViewController) {
- _infoViewController = [[VLCPlaybackInfoTVViewController alloc] initWithNibName:nil bundle:nil];
- }
- return _infoViewController;
- }
- #pragma mark - playback controller delegation
- - (void)prepareForMediaPlayback:(VLCPlaybackController *)controller
- {
- self.audioView.hidden = YES;
- }
- - (void)playbackDidStop
- {
- [self dismissViewControllerAnimated:YES completion:nil];
- }
- - (void)mediaPlayerStateChanged:(VLCMediaPlayerState)currentState
- isPlaying:(BOOL)isPlaying
- currentMediaHasTrackToChooseFrom:(BOOL)currentMediaHasTrackToChooseFrom
- currentMediaHasChapters:(BOOL)currentMediaHasChapters
- forPlaybackController:(VLCPlaybackController *)controller
- {
- [self updateActivityIndicatorForState:currentState];
- if (controller.isPlaying) {
- // 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;
- }
- [self hidePlaybackControlsIfNeededAfterDelay];
- } else {
- [self showPlaybackControlsIfNeededForUserInteraction];
- }
- if (controller.isPlaying && !self.bufferingLabel.hidden) {
- [UIView animateWithDuration:.3 animations:^{
- self.bufferingLabel.hidden = YES;
- }];
- }
- }
- - (void)displayMetadataForPlaybackController:(VLCPlaybackController *)controller
- title:(NSString *)title
- artwork:(UIImage *)artwork
- artist:(NSString *)artist
- album:(NSString *)album
- audioOnly:(BOOL)audioOnly
- {
- self.titleLabel.text = title;
- if (audioOnly) {
- self.audioArtworkImageView.image = nil;
- self.audioDescriptionTextView.hidden = YES;
- [self stopAudioDescriptionAnimation];
- if (!self.audioMetaDataFetcher) {
- self.audioMetaDataFetcher = [[MDFHatchetFetcher alloc] init];
- self.audioMetaDataFetcher.dataRecipient = self;
- }
- [self.audioMetaDataFetcher cancelAllRequests];
- if (artist != nil && album != nil) {
- [UIView animateWithDuration:.3 animations:^{
- self.audioArtistLabel.text = artist;
- self.audioArtistLabel.hidden = NO;
- self.audioAlbumNameLabel.text = album;
- self.audioAlbumNameLabel.hidden = NO;
- }];
- APLog(@"Audio-only track meta changed, tracing artist '%@' and album '%@'", artist, album);
- } else if (artist != nil) {
- [UIView animateWithDuration:.3 animations:^{
- self.audioArtistLabel.text = artist;
- self.audioArtistLabel.hidden = NO;
- self.audioAlbumNameLabel.hidden = YES;
- }];
- 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"
- artist = [title substringToIndex:deviderRange.location];
- title = [title substringFromIndex:deviderRange.location + deviderRange.length];
- }
- APLog(@"Audio-only track meta changed, tracing artist '%@'", artist);
- [UIView animateWithDuration:.3 animations:^{
- self.audioArtistLabel.text = artist;
- self.audioArtistLabel.hidden = NO;
- self.audioAlbumNameLabel.hidden = YES;
- }];
- }
- if (![self.lastArtist isEqualToString:artist]) {
- UIImage *dummyImage = [UIImage imageNamed:@"about-app-icon"];
- [UIView animateWithDuration:.3 animations:^{
- self.audioArtworkImageView.image = dummyImage;
- self.audioLargeBackgroundImageView.image = dummyImage;
- }];
- }
- self.lastArtist = artist;
- self.audioTitleLabel.text = title;
- self.audioTitleLabel.hidden = NO;
- if (artist != nil) {
- if (album != nil) {
- [self.audioMetaDataFetcher searchForAlbum:album ofArtist:artist];
- } else
- [self.audioMetaDataFetcher searchForArtist:artist];
- }
- [UIView animateWithDuration:0.3 animations:^{
- self.audioView.hidden = NO;
- }];
- } else if (!self.audioView.hidden) {
- [self.audioMetaDataFetcher cancelAllRequests];
- self.audioView.hidden = YES;
- self.audioArtworkImageView.image = nil;
- [self.audioLargeBackgroundImageView stopAnimating];
- }
- }
- #pragma mark -
- - (void)updateTransportBarPosition
- {
- VLCPlaybackController *controller = [VLCPlaybackController sharedInstance];
- VLCTransportBar *transportBar = self.transportBar;
- transportBar.remainingTimeLabel.text = [[controller remainingTime] stringValue];
- transportBar.markerTimeLabel.text = [[controller playedTime] stringValue];
- transportBar.playbackFraction = controller.playbackPosition;
- }
- - (void)playbackPositionUpdated:(VLCPlaybackController *)controller
- {
- // FIXME: hard coded state since the state in mediaPlayer is incorrectly still buffering
- [self updateActivityIndicatorForState:VLCMediaPlayerStatePlaying];
- if (self.bottomOverlayView.alpha != 0.0) {
- [self updateTransportBarPosition];
- }
- }
- #pragma mark - gesture recognizer delegate
- - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
- if ([gestureRecognizer.allowedPressTypes containsObject:@(UIPressTypeMenu)]) {
- return self.transportBar.scrubbing;
- }
- return YES;
- }
- - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
- {
- return [self.simultaneousGestureRecognizers containsObject:gestureRecognizer];
- }
- #pragma mark - meta data recipient
- - (void)MDFHatchetFetcher:(MDFHatchetFetcher * _Nonnull)aFetcher
- didFindAlbum:(MDFMusicAlbum * _Nonnull)album
- byArtist:(MDFArtist * _Nullable)artist
- forSearchRequest:(NSString *)searchRequest
- {
- /* we have no match */
- if (!artist) {
- [self _simplifyMetaDataSearchString:searchRequest];
- return;
- }
- if (artist.biography) {
- [self scrollAudioDescriptionAnimationToTop];
- [UIView animateWithDuration:.3 animations:^{
- self.audioDescriptionTextView.hidden = NO;
- self.audioDescriptionTextView.text = artist.biography;
- }];
- [self startAudioDescriptionAnimation];
- } else
- [self stopAudioDescriptionAnimation];
- NSString *imageURLString = album.artworkImage;
- if (!imageURLString) {
- NSArray *imageURLStrings = album.largeSizedArtistImages;
- if (imageURLStrings.count > 0) {
- imageURLString = imageURLStrings.firstObject;
- } else {
- imageURLStrings = artist.mediumSizedImages;
- if (imageURLStrings.count > 0) {
- imageURLString = imageURLStrings.firstObject;
- }
- }
- }
- if (imageURLString) {
- [self.audioArtworkImageView setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@?height=500&width=500", imageURLString]]];
- [self.audioLargeBackgroundImageView setImageWithURL:[NSURL URLWithString:imageURLString]];
- } else {
- UIImage *dummyImage = [UIImage imageNamed:@"about-app-icon"];
- self.audioArtworkImageView.image = dummyImage;
- self.audioLargeBackgroundImageView.image = dummyImage;
- [self _simplifyMetaDataSearchString:searchRequest];
- }
- }
- - (void)MDFHatchetFetcher:(MDFHatchetFetcher *)aFetcher didFailToFindAlbum:(NSString *)albumName forArtistName:(NSString *)artistName
- {
- APLog(@"%s: %@ %@", __PRETTY_FUNCTION__, albumName, artistName);
- UIImage *dummyImage = [UIImage imageNamed:@"about-app-icon"];
- self.audioArtworkImageView.image = dummyImage;
- self.audioLargeBackgroundImageView.image = dummyImage;
- }
- - (void)MDFHatchetFetcher:(MDFHatchetFetcher *)aFetcher didFindArtist:(MDFArtist *)artist forSearchRequest:(NSString *)searchRequest
- {
- /* we have no match */
- if (!artist) {
- [self _simplifyMetaDataSearchString:searchRequest];
- return;
- }
- if (artist.biography) {
- [self scrollAudioDescriptionAnimationToTop];
- [UIView animateWithDuration:.3 animations:^{
- self.audioDescriptionTextView.text = artist.biography;
- self.audioDescriptionTextView.hidden = NO;
- }];
- [self startAudioDescriptionAnimation];
- } else
- [self stopAudioDescriptionAnimation];
- NSArray *imageURLStrings = artist.largeSizedImages;
- NSString *imageURLString;
- if (imageURLStrings.count > 0) {
- imageURLString = imageURLStrings.firstObject;
- } else {
- imageURLStrings = artist.mediumSizedImages;
- if (imageURLStrings.count > 0) {
- imageURLString = imageURLStrings.firstObject;
- }
- }
- if (imageURLString) {
- [self.audioArtworkImageView setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@?height=500&width=500",imageURLString]]];
- [self.audioLargeBackgroundImageView setImageWithURL:[NSURL URLWithString:imageURLString]];
- } else {
- [self _simplifyMetaDataSearchString:searchRequest];
- }
- }
- - (void)_simplifyMetaDataSearchString:(NSString *)searchString
- {
- NSRange lastRange = [searchString rangeOfString:@" " options:NSBackwardsSearch];
- if (lastRange.location != NSNotFound)
- [self.audioMetaDataFetcher searchForArtist:[searchString substringToIndex:lastRange.location]];
- }
- - (void)MDFHatchetFetcher:(MDFHatchetFetcher *)aFetcher didFailToFindArtistForSearchRequest:(NSString *)searchRequest
- {
- APLog(@"%s: %@", __PRETTY_FUNCTION__, searchRequest);
- UIImage *dummyImage = [UIImage imageNamed:@"about-app-icon"];
- self.audioArtworkImageView.image = dummyImage;
- self.audioLargeBackgroundImageView.image = dummyImage;
- }
- - (void)scrollAudioDescriptionAnimationToTop
- {
- [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;
- CFTimeInterval secondsPerPage = 8.0;
- 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);
- }
- }
- @end
- @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
|