2 * Copyright (C) 2014, 2015 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 #if PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED > 80200
31 #import "WebVideoFullscreenInterfaceAVKit.h"
35 #import "GeometryUtilities.h"
36 #import "WebCoreSystemInterface.h"
37 #import "WebVideoFullscreenModel.h"
38 #import <AVFoundation/AVTime.h>
39 #import <UIKit/UIKit.h>
40 #import <WebCore/RuntimeApplicationChecksIOS.h>
41 #import <WebCore/TimeRanges.h>
42 #import <WebCore/WebCoreThreadRun.h>
43 #import <wtf/RetainPtr.h>
44 #import <wtf/text/CString.h>
45 #import <wtf/text/WTFString.h>
47 using namespace WebCore;
49 // Soft-linking headers must be included last since they #define functions, constants, etc.
50 #import "CoreMediaSoftLink.h"
52 SOFT_LINK_FRAMEWORK(AVFoundation)
53 SOFT_LINK_CLASS(AVFoundation, AVPlayerLayer)
55 SOFT_LINK_FRAMEWORK(AVKit)
56 SOFT_LINK_CLASS(AVKit, AVPlayerController)
57 SOFT_LINK_CLASS(AVKit, AVPlayerViewController)
58 SOFT_LINK_CLASS(AVKit, AVValueTiming)
60 SOFT_LINK_FRAMEWORK(UIKit)
61 SOFT_LINK_CLASS(UIKit, UIApplication)
62 SOFT_LINK_CLASS(UIKit, UIScreen)
63 SOFT_LINK_CLASS(UIKit, UIWindow)
64 SOFT_LINK_CLASS(UIKit, UIView)
65 SOFT_LINK_CLASS(UIKit, UIViewController)
66 SOFT_LINK_CLASS(UIKit, UIColor)
68 @class WebAVMediaSelectionOption;
70 @interface WebAVPlayerController : NSObject <AVPlayerViewControllerDelegate>
72 WebAVMediaSelectionOption *_currentAudioMediaSelectionOption;
73 WebAVMediaSelectionOption *_currentLegibleMediaSelectionOption;
78 @property (retain) AVPlayerController* playerControllerProxy;
79 @property (assign) WebVideoFullscreenModel* delegate;
80 @property (assign) WebVideoFullscreenInterfaceAVKit* fullscreenInterface;
82 @property (readonly) BOOL canScanForward;
83 @property BOOL canScanBackward;
84 @property (readonly) BOOL canSeekToBeginning;
85 @property (readonly) BOOL canSeekToEnd;
87 @property BOOL canPlay;
88 @property (getter=isPlaying) BOOL playing;
89 @property BOOL canPause;
90 @property BOOL canTogglePlayback;
91 @property double rate;
92 @property BOOL canSeek;
93 @property NSTimeInterval contentDuration;
94 @property NSSize contentDimensions;
95 @property BOOL hasEnabledAudio;
96 @property BOOL hasEnabledVideo;
97 @property NSTimeInterval minTime;
98 @property NSTimeInterval maxTime;
99 @property NSTimeInterval contentDurationWithinEndTimes;
100 @property (retain) NSArray *loadedTimeRanges;
101 @property AVPlayerControllerStatus status;
102 @property (retain) AVValueTiming *timing;
103 @property (retain) NSArray *seekableTimeRanges;
105 @property (readonly) BOOL hasMediaSelectionOptions;
106 @property (readonly) BOOL hasAudioMediaSelectionOptions;
107 @property (retain) NSArray *audioMediaSelectionOptions;
108 @property (retain) WebAVMediaSelectionOption *currentAudioMediaSelectionOption;
109 @property (readonly) BOOL hasLegibleMediaSelectionOptions;
110 @property (retain) NSArray *legibleMediaSelectionOptions;
111 @property (retain) WebAVMediaSelectionOption *currentLegibleMediaSelectionOption;
113 @property (readonly, getter=isPlayingOnExternalScreen) BOOL playingOnExternalScreen;
114 @property (getter=isExternalPlaybackActive) BOOL externalPlaybackActive;
115 @property AVPlayerControllerExternalPlaybackType externalPlaybackType;
116 @property (retain) NSString *externalPlaybackAirPlayDeviceLocalizedName;
118 - (BOOL)playerViewController:(AVPlayerViewController *)playerViewController shouldExitFullScreenWithReason:(AVPlayerViewControllerExitFullScreenReason)reason;
121 @implementation WebAVPlayerController
125 if (!(self = [super init]))
128 initAVPlayerController();
129 self.playerControllerProxy = [[allocAVPlayerControllerInstance() init] autorelease];
135 [_playerControllerProxy release];
136 [_loadedTimeRanges release];
137 [_seekableTimeRanges release];
139 [_audioMediaSelectionOptions release];
140 [_legibleMediaSelectionOptions release];
141 [_currentAudioMediaSelectionOption release];
142 [_currentLegibleMediaSelectionOption release];
147 self.contentDuration = 0;
149 self.contentDurationWithinEndTimes = 0;
150 self.loadedTimeRanges = @[];
154 self.canTogglePlayback = NO;
155 self.hasEnabledAudio = NO;
158 self.status = AVPlayerControllerStatusUnknown;
163 self.hasEnabledVideo = NO;
164 self.contentDimensions = CGSizeMake(0, 0);
166 self.seekableTimeRanges = [NSMutableArray array];
168 self.canScanBackward = NO;
170 self.audioMediaSelectionOptions = nil;
171 self.currentAudioMediaSelectionOption = nil;
173 self.legibleMediaSelectionOptions = nil;
174 self.currentLegibleMediaSelectionOption = nil;
177 - (AVPlayer*) player {
181 - (id)forwardingTargetForSelector:(SEL)selector
183 UNUSED_PARAM(selector);
184 return self.playerControllerProxy;
187 - (BOOL)playerViewController:(AVPlayerViewController *)playerViewController shouldExitFullScreenWithReason:(AVPlayerViewControllerExitFullScreenReason)reason
189 UNUSED_PARAM(playerViewController);
190 UNUSED_PARAM(reason);
194 if (reason == AVPlayerViewControllerExitFullScreenReasonDoneButtonTapped || reason == AVPlayerViewControllerExitFullScreenReasonRemoteControlStopEventReceived)
195 self.delegate->pause();
197 self.delegate->requestExitFullscreen();
201 - (void)playerViewController:(AVPlayerViewController *)playerViewController restoreUserInterfaceForOptimizedFullscreenStopWithCompletionHandler:(void (^)(BOOL restored))completionHandler
203 UNUSED_PARAM(playerViewController);
204 completionHandler(self.fullscreenInterface->fullscreenMayReturnToInline());
207 - (void)playerViewControllerWillCancelOptimizedFullscree:(AVPlayerViewController *)playerViewController
209 UNUSED_PARAM(playerViewController);
210 ASSERT(self.delegate);
211 self.delegate->requestExitFullscreen();
214 - (void)play:(id)sender
216 UNUSED_PARAM(sender);
219 self.delegate->play();
222 - (void)pause:(id)sender
224 UNUSED_PARAM(sender);
227 self.delegate->pause();
230 - (void)togglePlayback:(id)sender
232 UNUSED_PARAM(sender);
235 self.delegate->togglePlayState();
238 - (void)togglePlaybackEvenWhenInBackground:(id)sender
240 [self togglePlayback:sender];
245 return [self rate] != 0;
248 - (void)setPlaying:(BOOL)playing
253 self.delegate->play();
255 self.delegate->pause();
258 + (NSSet *)keyPathsForValuesAffectingPlaying
260 return [NSSet setWithObject:@"rate"];
263 - (void)beginScrubbing:(id)sender
265 UNUSED_PARAM(sender);
268 self.delegate->beginScrubbing();
271 - (void)endScrubbing:(id)sender
273 UNUSED_PARAM(sender);
276 self.delegate->endScrubbing();
279 - (void)seekToTime:(NSTimeInterval)time
283 self.delegate->fastSeek(time);
286 - (BOOL)hasLiveStreamingContent
288 if ([self status] == AVPlayerControllerStatusReadyToPlay)
289 return [self contentDuration] == std::numeric_limits<float>::infinity();
293 + (NSSet *)keyPathsForValuesAffectingHasLiveStreamingContent
295 return [NSSet setWithObjects:@"contentDuration", @"status", nil];
298 - (void)skipBackwardThirtySeconds:(id)sender
300 UNUSED_PARAM(sender);
301 BOOL isTimeWithinSeekableTimeRanges = NO;
302 CMTime currentTime = CMTimeMakeWithSeconds([[self timing] currentValue], 1000);
303 CMTime thirtySecondsBeforeCurrentTime = CMTimeSubtract(currentTime, CMTimeMake(30, 1));
305 for (NSValue *seekableTimeRangeValue in [self seekableTimeRanges]) {
306 if (CMTimeRangeContainsTime([seekableTimeRangeValue CMTimeRangeValue], thirtySecondsBeforeCurrentTime)) {
307 isTimeWithinSeekableTimeRanges = YES;
312 if (isTimeWithinSeekableTimeRanges)
313 [self seekToTime:CMTimeGetSeconds(thirtySecondsBeforeCurrentTime)];
316 - (void)gotoEndOfSeekableRanges:(id)sender
318 UNUSED_PARAM(sender);
319 NSTimeInterval timeAtEndOfSeekableTimeRanges = NAN;
321 for (NSValue *seekableTimeRangeValue in [self seekableTimeRanges]) {
322 CMTimeRange seekableTimeRange = [seekableTimeRangeValue CMTimeRangeValue];
323 NSTimeInterval endOfSeekableTimeRange = CMTimeGetSeconds(CMTimeRangeGetEnd(seekableTimeRange));
324 if (isnan(timeAtEndOfSeekableTimeRanges) || endOfSeekableTimeRange > timeAtEndOfSeekableTimeRanges)
325 timeAtEndOfSeekableTimeRanges = endOfSeekableTimeRange;
328 if (!isnan(timeAtEndOfSeekableTimeRanges))
329 [self seekToTime:timeAtEndOfSeekableTimeRanges];
332 - (BOOL)canScanForward
334 return [self canPlay];
337 + (NSSet *)keyPathsForValuesAffectingCanScanForward
339 return [NSSet setWithObject:@"canPlay"];
342 - (void)beginScanningForward:(id)sender
344 UNUSED_PARAM(sender);
347 self.delegate->beginScanningForward();
350 - (void)endScanningForward:(id)sender
352 UNUSED_PARAM(sender);
355 self.delegate->endScanning();
358 - (void)beginScanningBackward:(id)sender
360 UNUSED_PARAM(sender);
363 self.delegate->beginScanningBackward();
366 - (void)endScanningBackward:(id)sender
368 UNUSED_PARAM(sender);
371 self.delegate->endScanning();
374 - (BOOL)canSeekToBeginning
376 CMTime minimumTime = kCMTimeIndefinite;
378 for (NSValue *value in [self seekableTimeRanges])
379 minimumTime = CMTimeMinimum([value CMTimeRangeValue].start, minimumTime);
381 return CMTIME_IS_NUMERIC(minimumTime);
384 + (NSSet *)keyPathsForValuesAffectingCanSeekToBeginning
386 return [NSSet setWithObject:@"seekableTimeRanges"];
389 - (void)seekToBeginning:(id)sender
391 UNUSED_PARAM(sender);
394 self.delegate->seekToTime(-INFINITY);
397 - (void)seekChapterBackward:(id)sender
399 [self seekToBeginning:sender];
404 CMTime maximumTime = kCMTimeIndefinite;
406 for (NSValue *value in [self seekableTimeRanges])
407 maximumTime = CMTimeMaximum(CMTimeRangeGetEnd([value CMTimeRangeValue]), maximumTime);
409 return CMTIME_IS_NUMERIC(maximumTime);
412 + (NSSet *)keyPathsForValuesAffectingCanSeekToEnd
414 return [NSSet setWithObject:@"seekableTimeRanges"];
417 - (void)seekToEnd:(id)sender
419 UNUSED_PARAM(sender);
422 self.delegate->seekToTime(INFINITY);
425 - (void)seekChapterForward:(id)sender
427 [self seekToEnd:sender];
430 - (BOOL)hasMediaSelectionOptions
432 return [self hasAudioMediaSelectionOptions] || [self hasLegibleMediaSelectionOptions];
435 + (NSSet *)keyPathsForValuesAffectingHasMediaSelectionOptions
437 return [NSSet setWithObjects:@"hasAudioMediaSelectionOptions", @"hasLegibleMediaSelectionOptions", nil];
440 - (BOOL)hasAudioMediaSelectionOptions
442 return [[self audioMediaSelectionOptions] count] > 1;
445 + (NSSet *)keyPathsForValuesAffectingHasAudioMediaSelectionOptions
447 return [NSSet setWithObject:@"audioMediaSelectionOptions"];
450 - (BOOL)hasLegibleMediaSelectionOptions
452 const NSUInteger numDefaultLegibleOptions = 2;
453 return [[self legibleMediaSelectionOptions] count] > numDefaultLegibleOptions;
456 + (NSSet *)keyPathsForValuesAffectingHasLegibleMediaSelectionOptions
458 return [NSSet setWithObject:@"legibleMediaSelectionOptions"];
461 - (WebAVMediaSelectionOption *)currentAudioMediaSelectionOption
463 return _currentAudioMediaSelectionOption;
466 - (void)setCurrentAudioMediaSelectionOption:(WebAVMediaSelectionOption *)option
468 if (option == _currentAudioMediaSelectionOption)
471 [_currentAudioMediaSelectionOption release];
472 _currentAudioMediaSelectionOption = [option retain];
477 NSInteger index = NSNotFound;
479 if (option && self.audioMediaSelectionOptions)
480 index = [self.audioMediaSelectionOptions indexOfObject:option];
482 self.delegate->selectAudioMediaOption(index != NSNotFound ? index : UINT64_MAX);
485 - (WebAVMediaSelectionOption *)currentLegibleMediaSelectionOption
487 return _currentLegibleMediaSelectionOption;
490 - (void)setCurrentLegibleMediaSelectionOption:(WebAVMediaSelectionOption *)option
492 if (option == _currentLegibleMediaSelectionOption)
495 [_currentLegibleMediaSelectionOption release];
496 _currentLegibleMediaSelectionOption = [option retain];
501 NSInteger index = NSNotFound;
503 if (option && self.legibleMediaSelectionOptions)
504 index = [self.legibleMediaSelectionOptions indexOfObject:option];
506 self.delegate->selectLegibleMediaOption(index != NSNotFound ? index : UINT64_MAX);
509 - (BOOL)isPlayingOnExternalScreen
511 return [self isExternalPlaybackActive];
514 + (NSSet *)keyPathsForValuesAffectingPlayingOnExternalScreen
516 return [NSSet setWithObjects:@"externalPlaybackActive", nil];
519 - (void)layoutSublayersOfLayer:(CALayer *)layer
521 CGRect layerBounds = [layer bounds];
523 self.delegate->setVideoLayerFrame(CGRectMake(0, 0, CGRectGetWidth(layerBounds), CGRectGetHeight(layerBounds)));
525 [CATransaction begin];
526 for (CALayer *sublayer in [layer sublayers]) {
527 [sublayer setAnchorPoint:CGPointMake(0.5, 0.5)];
528 [sublayer setPosition:CGPointMake(CGRectGetMidX(layerBounds), CGRectGetMidY(layerBounds))];
530 [CATransaction commit];
534 @interface WebAVMediaSelectionOption : NSObject
535 @property (retain) NSString *localizedDisplayName;
538 @implementation WebAVMediaSelectionOption
541 @interface WebAVVideoLayer : CALayer <AVVideoLayer>
542 +(WebAVVideoLayer *)videoLayer;
543 @property (nonatomic) AVVideoLayerGravity videoLayerGravity;
544 @property (nonatomic, getter = isReadyForDisplay) BOOL readyForDisplay;
545 @property (nonatomic) CGRect videoRect;
546 - (void)setPlayerViewController:(AVPlayerViewController *)playerViewController;
547 - (void)setPlayerController:(AVPlayerController *)playerController;
548 @property (nonatomic, retain) CALayer* videoSublayer;
551 @implementation WebAVVideoLayer
553 RetainPtr<WebAVPlayerController> _avPlayerController;
554 RetainPtr<AVPlayerViewController> _avPlayerViewController;
555 RetainPtr<CALayer> _videoSublayer;
556 AVVideoLayerGravity _videoLayerGravity;
559 +(WebAVVideoLayer *)videoLayer
561 return [[[WebAVVideoLayer alloc] init] autorelease];
568 [self setMasksToBounds:YES];
569 [self setVideoLayerGravity:AVVideoLayerGravityResizeAspect];
574 - (void)setPlayerController:(AVPlayerController *)playerController
576 ASSERT(!playerController || [playerController isKindOfClass:[WebAVPlayerController class]]);
577 _avPlayerController = (WebAVPlayerController *)playerController;
580 - (void)setPlayerViewController:(AVPlayerViewController *)playerViewController
582 NSString* propertyName = NSStringFromSelector(@selector(optimizedFullscreenActive));
584 [_avPlayerViewController removeObserver:self forKeyPath:propertyName];
585 _avPlayerViewController = playerViewController;
586 [_avPlayerViewController addObserver:self forKeyPath:propertyName options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];
589 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
591 UNUSED_PARAM(context);
592 UNUSED_PARAM(object);
594 NSString* propertyName = NSStringFromSelector(@selector(optimizedFullscreenActive));
596 if ([keyPath isEqualToString:propertyName] && change[NSKeyValueChangeNewKey]) {
597 BOOL oldActive = [change[NSKeyValueChangeOldKey] boolValue];
598 BOOL active = [change[NSKeyValueChangeNewKey] boolValue];
599 if (oldActive != active && [_avPlayerController fullscreenInterface])
600 [_avPlayerController fullscreenInterface]->setIsOptimized(active);
605 - (void)setVideoSublayer:(CALayer *)videoSublayer
607 _videoSublayer = videoSublayer;
608 [self addSublayer:videoSublayer];
611 - (CALayer*)videoSublayer
613 return _videoSublayer.get();
616 - (void)setBounds:(CGRect)bounds
618 [super setBounds:bounds];
620 if (![_avPlayerController delegate] || !_avPlayerViewController)
623 UIView* rootView = [[_avPlayerViewController view] window];
627 [CATransaction begin];
628 NSTimeInterval animationDuration = [self animationForKey:@"bounds"].duration;
629 if (!animationDuration) // a duration of 0 for CA means 0.25. This is a way to approximate 0.
630 animationDuration = 0.001;
631 [CATransaction setAnimationDuration:animationDuration];
632 [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
634 FloatRect rootBounds = [rootView bounds];
635 [_avPlayerController delegate]->setVideoLayerFrame(rootBounds);
637 FloatRect sourceBounds = largestRectWithAspectRatioInsideRect(CGRectGetWidth(bounds) / CGRectGetHeight(bounds), rootBounds);
638 CATransform3D transform = CATransform3DMakeScale(bounds.size.width / sourceBounds.width(), bounds.size.height / sourceBounds.height(), 1);
639 [_videoSublayer setPosition:CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds))];
640 [_videoSublayer setSublayerTransform:transform];
641 [CATransaction commit];
644 - (void)setVideoLayerGravity:(AVVideoLayerGravity)videoLayerGravity
646 _videoLayerGravity = videoLayerGravity;
648 if (![_avPlayerController delegate])
651 WebCore::WebVideoFullscreenModel::VideoGravity gravity = WebCore::WebVideoFullscreenModel::VideoGravityResizeAspect;
652 if (videoLayerGravity == AVVideoLayerGravityResize)
653 gravity = WebCore::WebVideoFullscreenModel::VideoGravityResize;
654 if (videoLayerGravity == AVVideoLayerGravityResizeAspect)
655 gravity = WebCore::WebVideoFullscreenModel::VideoGravityResizeAspect;
656 else if (videoLayerGravity == AVVideoLayerGravityResizeAspectFill)
657 gravity = WebCore::WebVideoFullscreenModel::VideoGravityResizeAspectFill;
659 ASSERT_NOT_REACHED();
661 [_avPlayerController delegate]->setVideoLayerGravity(gravity);
664 - (AVVideoLayerGravity)videoLayerGravity
666 return _videoLayerGravity;
671 WebVideoFullscreenInterfaceAVKit::WebVideoFullscreenInterfaceAVKit()
672 : m_playerController(adoptNS([[WebAVPlayerController alloc] init]))
673 , m_videoFullscreenModel(nullptr)
674 , m_fullscreenChangeObserver(nullptr)
675 , m_mode(HTMLMediaElement::VideoFullscreenModeNone)
676 , m_exitRequested(false)
677 , m_exitCompleted(false)
678 , m_enterRequested(false)
680 [m_playerController setFullscreenInterface:this];
683 void WebVideoFullscreenInterfaceAVKit::resetMediaState()
685 RefPtr<WebVideoFullscreenInterfaceAVKit> strongThis(this);
687 dispatch_async(dispatch_get_main_queue(), [strongThis] {
688 if (!strongThis->m_playerController) {
689 strongThis->m_playerController = adoptNS([[WebAVPlayerController alloc] init]);
690 [strongThis->m_playerController setDelegate:strongThis->m_videoFullscreenModel];
691 [strongThis->m_playerController setFullscreenInterface:strongThis.get()];
694 [strongThis->m_playerController resetState];
698 void WebVideoFullscreenInterfaceAVKit::setWebVideoFullscreenModel(WebVideoFullscreenModel* model)
700 m_videoFullscreenModel = model;
701 [m_playerController setDelegate:m_videoFullscreenModel];
704 void WebVideoFullscreenInterfaceAVKit::setWebVideoFullscreenChangeObserver(WebVideoFullscreenChangeObserver* observer)
706 m_fullscreenChangeObserver = observer;
709 void WebVideoFullscreenInterfaceAVKit::setDuration(double duration)
711 RefPtr<WebVideoFullscreenInterfaceAVKit> strongThis(this);
713 dispatch_async(dispatch_get_main_queue(), [strongThis, duration] {
714 WebAVPlayerController* playerController = strongThis->m_playerController.get();
716 // FIXME: https://bugs.webkit.org/show_bug.cgi?id=127017 use correct values instead of duration for all these
717 playerController.contentDuration = duration;
718 playerController.maxTime = duration;
719 playerController.contentDurationWithinEndTimes = duration;
720 playerController.loadedTimeRanges = @[@0, @(duration)];
722 // FIXME: we take this as an indication that playback is ready.
723 playerController.canPlay = YES;
724 playerController.canPause = YES;
725 playerController.canTogglePlayback = YES;
726 playerController.hasEnabledAudio = YES;
727 playerController.canSeek = YES;
728 playerController.minTime = 0;
729 playerController.status = AVPlayerControllerStatusReadyToPlay;
733 void WebVideoFullscreenInterfaceAVKit::setCurrentTime(double currentTime, double anchorTime)
735 RefPtr<WebVideoFullscreenInterfaceAVKit> strongThis(this);
737 dispatch_async(dispatch_get_main_queue(), [strongThis, currentTime, anchorTime] {
738 NSTimeInterval anchorTimeStamp = ![strongThis->m_playerController rate] ? NAN : anchorTime;
739 AVValueTiming *timing = [getAVValueTimingClass() valueTimingWithAnchorValue:currentTime
740 anchorTimeStamp:anchorTimeStamp rate:0];
741 [strongThis->m_playerController setTiming:timing];
745 void WebVideoFullscreenInterfaceAVKit::setRate(bool isPlaying, float playbackRate)
747 RefPtr<WebVideoFullscreenInterfaceAVKit> strongThis(this);
749 dispatch_async(dispatch_get_main_queue(), [strongThis, isPlaying, playbackRate] {
750 [strongThis->m_playerController setRate:isPlaying ? playbackRate : 0.];
754 void WebVideoFullscreenInterfaceAVKit::setVideoDimensions(bool hasVideo, float width, float height)
756 RefPtr<WebVideoFullscreenInterfaceAVKit> strongThis(this);
758 dispatch_async(dispatch_get_main_queue(), [strongThis, hasVideo, width, height] {
759 [strongThis->m_playerController setHasEnabledVideo:hasVideo];
760 [strongThis->m_playerController setContentDimensions:CGSizeMake(width, height)];
764 void WebVideoFullscreenInterfaceAVKit::setSeekableRanges(const TimeRanges& timeRanges)
766 RetainPtr<NSMutableArray> seekableRanges = adoptNS([[NSMutableArray alloc] init]);
767 ExceptionCode exceptionCode;
769 for (unsigned i = 0; i < timeRanges.length(); i++) {
770 double start = timeRanges.start(i, exceptionCode);
771 double end = timeRanges.end(i, exceptionCode);
773 CMTimeRange range = CMTimeRangeMake(CMTimeMakeWithSeconds(start, 1000), CMTimeMakeWithSeconds(end-start, 1000));
774 [seekableRanges addObject:[NSValue valueWithCMTimeRange:range]];
777 RefPtr<WebVideoFullscreenInterfaceAVKit> strongThis(this);
779 dispatch_async(dispatch_get_main_queue(), [strongThis, seekableRanges] {
780 [strongThis->m_playerController setSeekableTimeRanges:seekableRanges.get()];
784 void WebVideoFullscreenInterfaceAVKit::setCanPlayFastReverse(bool canPlayFastReverse)
786 RefPtr<WebVideoFullscreenInterfaceAVKit> strongThis(this);
788 dispatch_async(dispatch_get_main_queue(), [strongThis, canPlayFastReverse] {
789 [strongThis->m_playerController setCanScanBackward:canPlayFastReverse];
793 static RetainPtr<NSMutableArray> mediaSelectionOptions(const Vector<String>& options)
795 RetainPtr<NSMutableArray> webOptions = adoptNS([[NSMutableArray alloc] initWithCapacity:options.size()]);
796 for (auto& name : options) {
797 RetainPtr<WebAVMediaSelectionOption> webOption = adoptNS([[WebAVMediaSelectionOption alloc] init]);
798 [webOption setLocalizedDisplayName:name];
799 [webOptions addObject:webOption.get()];
804 void WebVideoFullscreenInterfaceAVKit::setAudioMediaSelectionOptions(const Vector<String>& options, uint64_t selectedIndex)
806 RetainPtr<NSMutableArray> webOptions = mediaSelectionOptions(options);
807 RefPtr<WebVideoFullscreenInterfaceAVKit> strongThis(this);
809 dispatch_async(dispatch_get_main_queue(), [webOptions, strongThis, selectedIndex] {
810 [strongThis->m_playerController setAudioMediaSelectionOptions:webOptions.get()];
811 if (selectedIndex < [webOptions count])
812 [strongThis->m_playerController setCurrentAudioMediaSelectionOption:[webOptions objectAtIndex:static_cast<NSUInteger>(selectedIndex)]];
816 void WebVideoFullscreenInterfaceAVKit::setLegibleMediaSelectionOptions(const Vector<String>& options, uint64_t selectedIndex)
818 RetainPtr<NSMutableArray> webOptions = mediaSelectionOptions(options);
819 RefPtr<WebVideoFullscreenInterfaceAVKit> strongThis(this);
821 dispatch_async(dispatch_get_main_queue(), [webOptions, strongThis, selectedIndex] {
822 [strongThis->m_playerController setLegibleMediaSelectionOptions:webOptions.get()];
823 if (selectedIndex < [webOptions count])
824 [strongThis->m_playerController setCurrentLegibleMediaSelectionOption:[webOptions objectAtIndex:static_cast<NSUInteger>(selectedIndex)]];
828 void WebVideoFullscreenInterfaceAVKit::setExternalPlayback(bool enabled, ExternalPlaybackTargetType targetType, String localizedDeviceName)
830 AVPlayerControllerExternalPlaybackType externalPlaybackType = AVPlayerControllerExternalPlaybackTypeNone;
831 if (targetType == TargetTypeAirPlay)
832 externalPlaybackType = AVPlayerControllerExternalPlaybackTypeAirPlay;
833 else if (targetType == TargetTypeTVOut)
834 externalPlaybackType = AVPlayerControllerExternalPlaybackTypeTVOut;
836 RefPtr<WebVideoFullscreenInterfaceAVKit> strongThis(this);
838 dispatch_async(dispatch_get_main_queue(), [strongThis, enabled, localizedDeviceName, externalPlaybackType] {
839 WebAVPlayerController* playerController = strongThis->m_playerController.get();
840 playerController.externalPlaybackAirPlayDeviceLocalizedName = localizedDeviceName;
841 playerController.externalPlaybackType = externalPlaybackType;
842 playerController.externalPlaybackActive = enabled;
843 [strongThis->m_videoLayerContainer.get() setHidden:enabled];
847 void WebVideoFullscreenInterfaceAVKit::setupFullscreen(PlatformLayer& videoLayer, WebCore::IntRect initialRect, UIView* parentView, HTMLMediaElement::VideoFullscreenMode mode, bool allowOptimizedFullscreen)
849 RefPtr<WebVideoFullscreenInterfaceAVKit> strongThis(this);
851 ASSERT(mode != HTMLMediaElement::VideoFullscreenModeNone);
852 m_videoLayer = &videoLayer;
856 dispatch_async(dispatch_get_main_queue(), [strongThis, &videoLayer, initialRect, parentView, mode, allowOptimizedFullscreen] {
857 strongThis->setupFullscreenInternal(videoLayer, initialRect, parentView, mode, allowOptimizedFullscreen);
861 void WebVideoFullscreenInterfaceAVKit::setupFullscreenInternal(PlatformLayer& videoLayer, WebCore::IntRect initialRect, UIView* parentView, HTMLMediaElement::VideoFullscreenMode mode, bool allowOptimizedFullscreen)
863 UNUSED_PARAM(videoLayer);
866 [CATransaction begin];
867 [CATransaction setDisableActions:YES];
868 m_parentView = parentView;
869 m_parentWindow = parentView.window;
871 if (!applicationIsAdSheet()) {
872 m_window = adoptNS([allocUIWindowInstance() initWithFrame:[[getUIScreenClass() mainScreen] bounds]]);
873 [m_window setBackgroundColor:[getUIColorClass() clearColor]];
874 m_viewController = adoptNS([allocUIViewControllerInstance() init]);
875 [[m_viewController view] setFrame:[m_window bounds]];
876 [m_window setRootViewController:m_viewController.get()];
877 [m_window makeKeyAndVisible];
880 [m_videoLayer removeFromSuperlayer];
882 m_videoLayerContainer = [WebAVVideoLayer videoLayer];
883 [m_videoLayerContainer setHidden:[m_playerController isExternalPlaybackActive]];
884 [m_videoLayerContainer setVideoSublayer:m_videoLayer.get()];
886 CGSize videoSize = [m_playerController contentDimensions];
887 CGRect videoRect = CGRectMake(0, 0, videoSize.width, videoSize.height);
888 [m_videoLayerContainer setVideoRect:videoRect];
890 m_playerViewController = adoptNS([allocAVPlayerViewControllerInstance() initWithVideoLayer:m_videoLayerContainer.get()]);
891 [m_playerViewController setShowsPlaybackControls:NO];
892 [m_playerViewController setPlayerController:(AVPlayerController *)m_playerController.get()];
893 [m_playerViewController setDelegate:m_playerController.get()];
894 [m_playerViewController setAllowsOptimizedFullscreen:allowOptimizedFullscreen];
896 [m_videoLayerContainer setPlayerViewController:m_playerViewController.get()];
898 if (m_viewController) {
899 [m_viewController addChildViewController:m_playerViewController.get()];
900 [[m_viewController view] addSubview:[m_playerViewController view]];
901 [m_playerViewController view].frame = [parentView convertRect:initialRect toView:nil];
903 [parentView addSubview:[m_playerViewController view]];
904 [m_playerViewController view].frame = initialRect;
907 [[m_playerViewController view] setBackgroundColor:[getUIColorClass() clearColor]];
908 [[m_playerViewController view] setNeedsLayout];
909 [[m_playerViewController view] layoutIfNeeded];
911 [CATransaction commit];
913 RefPtr<WebVideoFullscreenInterfaceAVKit> strongThis(this);
914 WebThreadRun([strongThis] {
915 if (strongThis->m_fullscreenChangeObserver)
916 strongThis->m_fullscreenChangeObserver->didSetupFullscreen();
920 void WebVideoFullscreenInterfaceAVKit::enterFullscreen()
922 m_exitCompleted = false;
923 m_exitRequested = false;
924 m_enterRequested = true;
926 RefPtr<WebVideoFullscreenInterfaceAVKit> strongThis(this);
927 dispatch_async(dispatch_get_main_queue(), [strongThis] {
928 [strongThis->m_videoLayerContainer setBackgroundColor:[[getUIColorClass() blackColor] CGColor]];
929 if (strongThis->mode() == HTMLMediaElement::VideoFullscreenModeOptimized)
930 strongThis->enterFullscreenOptimized();
931 else if (strongThis->mode() == HTMLMediaElement::VideoFullscreenModeStandard)
932 strongThis->enterFullscreenStandard();
934 ASSERT_NOT_REACHED();
938 void WebVideoFullscreenInterfaceAVKit::enterFullscreenOptimized()
940 RefPtr<WebVideoFullscreenInterfaceAVKit> strongThis(this);
942 auto startCompletionHandler = [this, strongThis] (BOOL success, NSError *) {
943 [m_playerViewController setShowsPlaybackControls:YES];
945 WebThreadRun([this, strongThis, success] {
946 [m_window setHidden:YES];
947 if (m_fullscreenChangeObserver)
948 m_fullscreenChangeObserver->didEnterFullscreen();
951 if (m_videoFullscreenModel)
952 m_videoFullscreenModel->requestExitFullscreen();
957 #pragma clang diagnostic push
958 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
959 // FIXME: <rdar://problem/20018692> Fix AVKit deprecation warnings
960 auto stopCompletionHandler = [this, strongThis] (AVPlayerViewControllerOptimizedFullscreenStopReason reason) {
961 m_exitCompleted = true;
963 if (m_fullscreenChangeObserver && reason == AVPlayerViewControllerOptimizedFullscreenStopReasonStopped)
964 m_fullscreenChangeObserver->fullscreenMayReturnToInline();
965 if (m_exitRequested) {
966 [m_videoLayerContainer setBackgroundColor:[[getUIColorClass() clearColor] CGColor]];
967 [[m_playerViewController view] setBackgroundColor:[getUIColorClass() clearColor]];
968 WebThreadRun([this, strongThis] {
969 if (m_fullscreenChangeObserver)
970 m_fullscreenChangeObserver->didExitFullscreen();
973 if (m_videoFullscreenModel)
974 m_videoFullscreenModel->requestExitFullscreen();
978 [m_playerViewController startOptimizedFullscreenWithStartCompletionHandler:startCompletionHandler stopCompletionHandler:stopCompletionHandler];
979 #pragma clang diagnostic pop
982 void WebVideoFullscreenInterfaceAVKit::enterFullscreenStandard()
984 RefPtr<WebVideoFullscreenInterfaceAVKit> strongThis(this);
985 [m_playerViewController enterFullScreenWithCompletionHandler:[this, strongThis] (BOOL, NSError*) {
986 [m_playerViewController setShowsPlaybackControls:YES];
988 WebThreadRun([this, strongThis] {
989 if (m_fullscreenChangeObserver)
990 m_fullscreenChangeObserver->didEnterFullscreen();
995 void WebVideoFullscreenInterfaceAVKit::exitFullscreen(WebCore::IntRect finalRect)
997 RefPtr<WebVideoFullscreenInterfaceAVKit> strongThis(this);
999 m_exitRequested = true;
1000 if (m_exitCompleted) {
1001 WebThreadRun([strongThis] {
1002 if (strongThis->m_fullscreenChangeObserver)
1003 strongThis->m_fullscreenChangeObserver->didExitFullscreen();
1008 m_playerController = nil;
1010 dispatch_async(dispatch_get_main_queue(), [strongThis, finalRect] {
1011 strongThis->exitFullscreenInternal(finalRect);
1015 void WebVideoFullscreenInterfaceAVKit::exitFullscreenInternal(WebCore::IntRect finalRect)
1017 [m_playerViewController setShowsPlaybackControls:NO];
1018 if (m_viewController)
1019 [m_playerViewController view].frame = [m_parentView convertRect:finalRect toView:nil];
1021 [m_playerViewController view].frame = finalRect;
1023 if ([m_videoLayerContainer videoLayerGravity] != AVVideoLayerGravityResizeAspect)
1024 [m_videoLayerContainer setVideoLayerGravity:AVVideoLayerGravityResizeAspect];
1025 [[m_playerViewController view] layoutIfNeeded];
1028 if (m_mode == HTMLMediaElement::VideoFullscreenModeOptimized) {
1029 [m_window setHidden:NO];
1030 [m_playerViewController stopOptimizedFullscreen];
1031 } else if (m_mode == (HTMLMediaElement::VideoFullscreenModeOptimized | HTMLMediaElement::VideoFullscreenModeStandard)) {
1032 RefPtr<WebVideoFullscreenInterfaceAVKit> strongThis(this);
1033 [m_playerViewController exitFullScreenAnimated:NO completionHandler:[strongThis] (BOOL, NSError*) {
1034 [strongThis->m_window setHidden:NO];
1035 [strongThis->m_playerViewController stopOptimizedFullscreen];
1037 } else if (m_mode == HTMLMediaElement::VideoFullscreenModeStandard) {
1038 RefPtr<WebVideoFullscreenInterfaceAVKit> strongThis(this);
1039 [m_playerViewController exitFullScreenWithCompletionHandler:[strongThis] (BOOL, NSError*) {
1040 strongThis->m_exitCompleted = true;
1042 [CATransaction begin];
1043 [CATransaction setDisableActions:YES];
1044 [strongThis->m_videoLayerContainer setBackgroundColor:[[getUIColorClass() clearColor] CGColor]];
1045 [[strongThis->m_playerViewController view] setBackgroundColor:[getUIColorClass() clearColor]];
1046 [CATransaction commit];
1048 WebThreadRun([strongThis] {
1049 if (strongThis->m_fullscreenChangeObserver)
1050 strongThis->m_fullscreenChangeObserver->didExitFullscreen();
1056 @interface UIApplication ()
1057 -(void)_setStatusBarOrientation:(UIInterfaceOrientation)o;
1060 @interface UIWindow ()
1061 -(UIInterfaceOrientation)interfaceOrientation;
1064 void WebVideoFullscreenInterfaceAVKit::cleanupFullscreen()
1066 RefPtr<WebVideoFullscreenInterfaceAVKit> strongThis(this);
1068 dispatch_async(dispatch_get_main_queue(), [strongThis] {
1069 strongThis->cleanupFullscreenInternal();
1073 void WebVideoFullscreenInterfaceAVKit::cleanupFullscreenInternal()
1076 [m_window setHidden:YES];
1077 [m_window setRootViewController:nil];
1079 [[getUIApplicationClass() sharedApplication] _setStatusBarOrientation:[m_parentWindow interfaceOrientation]];
1082 [m_playerController setDelegate:nil];
1083 [m_playerController setFullscreenInterface:nil];
1085 [m_playerViewController setDelegate:nil];
1086 [m_playerViewController setPlayerController:nil];
1088 if (m_mode & HTMLMediaElement::VideoFullscreenModeOptimized)
1089 [m_playerViewController cancelOptimizedFullscreen];
1090 if (m_mode & HTMLMediaElement::VideoFullscreenModeStandard)
1091 [m_playerViewController exitFullScreenAnimated:NO completionHandler:nil];
1093 [[m_playerViewController view] removeFromSuperview];
1094 if (m_viewController)
1095 [m_playerViewController removeFromParentViewController];
1097 [m_videoLayer removeFromSuperlayer];
1098 [m_videoLayerContainer removeFromSuperlayer];
1099 [m_videoLayerContainer setPlayerViewController:nil];
1100 [[m_viewController view] removeFromSuperview];
1103 m_videoLayerContainer = nil;
1104 m_playerViewController = nil;
1105 m_playerController = nil;
1106 m_viewController = nil;
1109 m_parentWindow = nil;
1111 RefPtr<WebVideoFullscreenInterfaceAVKit> strongThis(this);
1112 WebThreadRun([strongThis] {
1113 if (strongThis->m_fullscreenChangeObserver)
1114 strongThis->m_fullscreenChangeObserver->didCleanupFullscreen();
1115 strongThis->m_enterRequested = false;
1119 void WebVideoFullscreenInterfaceAVKit::invalidate()
1121 m_videoFullscreenModel = nil;
1122 m_fullscreenChangeObserver = nil;
1124 cleanupFullscreenInternal();
1127 void WebVideoFullscreenInterfaceAVKit::requestHideAndExitFullscreen()
1129 if (!m_enterRequested)
1132 if (m_mode & HTMLMediaElement::VideoFullscreenModeOptimized)
1135 RefPtr<WebVideoFullscreenInterfaceAVKit> strongThis(this);
1136 dispatch_async(dispatch_get_main_queue(), [strongThis] {
1137 [strongThis->m_window setHidden:YES];
1138 [strongThis->m_playerViewController exitFullScreenAnimated:NO completionHandler:[strongThis] (BOOL, NSError*) {
1142 if (m_videoFullscreenModel && !m_exitRequested)
1143 m_videoFullscreenModel->requestExitFullscreen();
1146 void WebVideoFullscreenInterfaceAVKit::setIsOptimized(bool active)
1148 if (m_mode & HTMLMediaElement::VideoFullscreenModeStandard) {
1150 m_mode &= ~HTMLMediaElement::VideoFullscreenModeOptimized;
1152 m_mode |= HTMLMediaElement::VideoFullscreenModeOptimized;
1155 if (m_videoFullscreenModel)
1156 m_videoFullscreenModel->fullscreenModeChanged(m_exitRequested ? HTMLMediaElement::VideoFullscreenModeNone : m_mode);
1159 if (m_mode == HTMLMediaElement::VideoFullscreenModeOptimized)
1162 [m_window setHidden:m_mode & HTMLMediaElement::VideoFullscreenModeOptimized];
1164 if (m_fullscreenChangeObserver && ~m_mode & HTMLMediaElement::VideoFullscreenModeOptimized)
1165 m_fullscreenChangeObserver->fullscreenMayReturnToInline();
1167 if (!m_exitRequested || active)
1170 m_exitCompleted = true;
1171 [m_videoLayerContainer setBackgroundColor:[[getUIColorClass() clearColor] CGColor]];
1172 [[m_playerViewController view] setBackgroundColor:[getUIColorClass() clearColor]];
1174 if (m_fullscreenChangeObserver)
1175 m_fullscreenChangeObserver->didExitFullscreen();
1179 bool WebVideoFullscreenInterfaceAVKit::mayAutomaticallyShowVideoOptimized()
1181 return [m_playerController isPlaying] && m_mode == HTMLMediaElement::VideoFullscreenModeStandard && wkIsOptimizedFullscreenSupported();
1184 bool WebVideoFullscreenInterfaceAVKit::fullscreenMayReturnToInline()
1186 m_fullscreenChangeObserver->fullscreenMayReturnToInline();