/* * Copyright (C) 2014-2018 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #if PLATFORM(IOS_FAMILY) #import "VideoFullscreenInterfaceAVKit.h" #if HAVE(AVKIT) #import "GeometryUtilities.h" #import "Logging.h" #import "PlaybackSessionInterfaceAVKit.h" #import "RuntimeApplicationChecks.h" #import "TimeRanges.h" #import "VideoFullscreenChangeObserver.h" #import "VideoFullscreenModel.h" #import "WebAVPlayerController.h" #import #import #import #import #import #import #import #import #import #import using namespace WebCore; // Soft-linking headers must be included last since they #define functions, constants, etc. #import SOFT_LINK_FRAMEWORK(AVFoundation) SOFT_LINK_CLASS(AVFoundation, AVPlayerLayer) SOFT_LINK_CONSTANT(AVFoundation, AVLayerVideoGravityResize, NSString *) SOFT_LINK_CONSTANT(AVFoundation, AVLayerVideoGravityResizeAspect, NSString *) SOFT_LINK_CONSTANT(AVFoundation, AVLayerVideoGravityResizeAspectFill, NSString *) SOFTLINK_AVKIT_FRAMEWORK() SOFT_LINK_CLASS_OPTIONAL(AVKit, AVPictureInPictureController) SOFT_LINK_CLASS_OPTIONAL(AVKit, AVPlayerViewController) SOFT_LINK_CLASS_OPTIONAL(AVKit, __AVPlayerLayerView) SOFT_LINK_FRAMEWORK(UIKit) SOFT_LINK_CLASS(UIKit, UIApplication) SOFT_LINK_CLASS(UIKit, UIScreen) SOFT_LINK_CLASS(UIKit, UIWindow) SOFT_LINK_CLASS(UIKit, UIView) SOFT_LINK_CLASS(UIKit, UIViewController) SOFT_LINK_CLASS(UIKit, UIColor) SOFT_LINK_CONSTANT(UIKit, UITextEffectsBeneathStatusBarWindowLevel, UIWindowLevel) @interface UIWindow () - (BOOL)_isHostedInAnotherProcess; @end @interface UIViewController () @property (nonatomic, assign, setter=_setIgnoreAppSupportedOrientations:) BOOL _ignoreAppSupportedOrientations; @end static UIColor *clearUIColor() { return (UIColor *)[getUIColorClass() clearColor]; } #if !LOG_DISABLED static const char* boolString(bool val) { return val ? "true" : "false"; } #endif static const Seconds defaultWatchdogTimerInterval { 1_s }; static bool ignoreWatchdogForDebugging = false; @interface AVPlayerViewController (Details) @property (nonatomic) BOOL showsPlaybackControls; @property (nonatomic) UIView* view; @end @class WebAVMediaSelectionOption; @interface WebAVPlayerViewControllerDelegate : NSObject { RefPtr _fullscreenInterface; } @property (assign) VideoFullscreenInterfaceAVKit* fullscreenInterface; - (BOOL)playerViewController:(AVPlayerViewController *)playerViewController shouldExitFullScreenWithReason:(AVPlayerViewControllerExitFullScreenReason)reason; @end @implementation WebAVPlayerViewControllerDelegate - (VideoFullscreenInterfaceAVKit*)fullscreenInterface { return _fullscreenInterface.get(); } - (void)setFullscreenInterface:(VideoFullscreenInterfaceAVKit*)fullscreenInterface { _fullscreenInterface = fullscreenInterface; } - (void)playerViewControllerWillStartPictureInPicture:(AVPlayerViewController *)playerViewController { UNUSED_PARAM(playerViewController); self.fullscreenInterface->willStartPictureInPicture(); } - (void)playerViewControllerDidStartPictureInPicture:(AVPlayerViewController *)playerViewController { UNUSED_PARAM(playerViewController); self.fullscreenInterface->didStartPictureInPicture(); } - (void)playerViewController:(AVPlayerViewController *)playerViewController failedToStartPictureInPictureWithError:(NSError *)error { UNUSED_PARAM(playerViewController); UNUSED_PARAM(error); self.fullscreenInterface->failedToStartPictureInPicture(); } - (void)playerViewControllerWillStopPictureInPicture:(AVPlayerViewController *)playerViewController { UNUSED_PARAM(playerViewController); self.fullscreenInterface->willStopPictureInPicture(); } - (void)playerViewControllerDidStopPictureInPicture:(AVPlayerViewController *)playerViewController { UNUSED_PARAM(playerViewController); self.fullscreenInterface->didStopPictureInPicture(); } - (BOOL)playerViewControllerShouldAutomaticallyDismissAtPictureInPictureStart:(AVPlayerViewController *)playerViewController { UNUSED_PARAM(playerViewController); return NO; } static VideoFullscreenInterfaceAVKit::ExitFullScreenReason convertToExitFullScreenReason(AVPlayerViewControllerExitFullScreenReason reason) { switch (reason) { case AVPlayerViewControllerExitFullScreenReasonDoneButtonTapped: return VideoFullscreenInterfaceAVKit::ExitFullScreenReason::DoneButtonTapped; case AVPlayerViewControllerExitFullScreenReasonFullScreenButtonTapped: return VideoFullscreenInterfaceAVKit::ExitFullScreenReason::FullScreenButtonTapped; case AVPlayerViewControllerExitFullScreenReasonPictureInPictureStarted: return VideoFullscreenInterfaceAVKit::ExitFullScreenReason::PictureInPictureStarted; case AVPlayerViewControllerExitFullScreenReasonPinchGestureHandled: return VideoFullscreenInterfaceAVKit::ExitFullScreenReason::PinchGestureHandled; case AVPlayerViewControllerExitFullScreenReasonRemoteControlStopEventReceived: return VideoFullscreenInterfaceAVKit::ExitFullScreenReason::RemoteControlStopEventReceived; } } - (BOOL)playerViewController:(AVPlayerViewController *)playerViewController shouldExitFullScreenWithReason:(AVPlayerViewControllerExitFullScreenReason)reason { UNUSED_PARAM(playerViewController); return self.fullscreenInterface->shouldExitFullscreenWithReason(convertToExitFullScreenReason(reason)); } - (void)playerViewController:(AVPlayerViewController *)playerViewController restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:(void (^)(BOOL restored))completionHandler { UNUSED_PARAM(playerViewController); self.fullscreenInterface->prepareForPictureInPictureStopWithCompletionHandler(completionHandler); } - (BOOL)playerViewControllerShouldStartPictureInPictureFromInlineWhenEnteringBackground:(AVPlayerViewController *)playerViewController { UNUSED_PARAM(playerViewController); return YES; } @end @interface WebAVPlayerLayer : CALayer @property (nonatomic, retain) NSString *videoGravity; @property (nonatomic, getter=isReadyForDisplay) BOOL readyForDisplay; @property (nonatomic, assign) VideoFullscreenInterfaceAVKit* fullscreenInterface; @property (nonatomic, retain) AVPlayerController *playerController; @property (nonatomic, retain) CALayer *videoSublayer; @property (nonatomic, copy, nullable) NSDictionary *pixelBufferAttributes; @property CGSize videoDimensions; @property CGRect modelVideoLayerFrame; @end @implementation WebAVPlayerLayer { RefPtr _fullscreenInterface; RetainPtr _avPlayerController; RetainPtr _videoSublayer; RetainPtr _videoGravity; } - (instancetype)init { self = [super init]; if (self) { [self setMasksToBounds:YES]; _videoGravity = getAVLayerVideoGravityResizeAspect(); } return self; } - (void)dealloc { [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(resolveBounds) object:nil]; [_pixelBufferAttributes release]; [super dealloc]; } - (VideoFullscreenInterfaceAVKit*)fullscreenInterface { return _fullscreenInterface.get(); } - (void)setFullscreenInterface:(VideoFullscreenInterfaceAVKit*)fullscreenInterface { _fullscreenInterface = fullscreenInterface; } - (AVPlayerController *)playerController { return (AVPlayerController *)_avPlayerController.get(); } - (void)setPlayerController:(AVPlayerController *)playerController { ASSERT(!playerController || [playerController isKindOfClass:[WebAVPlayerController class]]); _avPlayerController = (WebAVPlayerController *)playerController; } - (void)setVideoSublayer:(CALayer *)videoSublayer { _videoSublayer = videoSublayer; } - (CALayer*)videoSublayer { return _videoSublayer.get(); } - (void)layoutSublayers { if ([_videoSublayer superlayer] != self) return; if (![_avPlayerController delegate]) return; [_videoSublayer setPosition:CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds))]; if (self.videoDimensions.height <= 0 || self.videoDimensions.width <= 0) return; FloatRect sourceVideoFrame; FloatRect targetVideoFrame; float videoAspectRatio = self.videoDimensions.width / self.videoDimensions.height; if ([getAVLayerVideoGravityResize() isEqualToString:self.videoGravity]) { sourceVideoFrame = self.modelVideoLayerFrame; targetVideoFrame = self.bounds; } else if ([getAVLayerVideoGravityResizeAspect() isEqualToString:self.videoGravity]) { sourceVideoFrame = largestRectWithAspectRatioInsideRect(videoAspectRatio, self.modelVideoLayerFrame); targetVideoFrame = largestRectWithAspectRatioInsideRect(videoAspectRatio, self.bounds); } else if ([getAVLayerVideoGravityResizeAspectFill() isEqualToString:self.videoGravity]) { sourceVideoFrame = smallestRectWithAspectRatioAroundRect(videoAspectRatio, self.modelVideoLayerFrame); self.modelVideoLayerFrame = CGRectMake(0, 0, sourceVideoFrame.width(), sourceVideoFrame.height()); if (auto* model = _fullscreenInterface->videoFullscreenModel()) model->setVideoLayerFrame(self.modelVideoLayerFrame); targetVideoFrame = smallestRectWithAspectRatioAroundRect(videoAspectRatio, self.bounds); } else ASSERT_NOT_REACHED(); UIView *view = (UIView *)[_videoSublayer delegate]; CGAffineTransform transform = CGAffineTransformMakeScale(targetVideoFrame.width() / sourceVideoFrame.width(), targetVideoFrame.height() / sourceVideoFrame.height()); [view setTransform:transform]; NSTimeInterval animationDuration = [CATransaction animationDuration]; dispatch_async(dispatch_get_main_queue(), ^{ [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(resolveBounds) object:nil]; [self performSelector:@selector(resolveBounds) withObject:nil afterDelay:animationDuration + 0.1]; }); } - (void)resolveBounds { [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(resolveBounds) object:nil]; if (![_avPlayerController delegate]) return; if ([_videoSublayer superlayer] != self) return; if (CGRectEqualToRect(self.modelVideoLayerFrame, [self bounds]) && CGAffineTransformIsIdentity([(UIView *)[_videoSublayer delegate] transform])) return; [CATransaction begin]; [CATransaction setAnimationDuration:0]; [CATransaction setDisableActions:YES]; if (!CGRectEqualToRect(self.modelVideoLayerFrame, [self bounds])) { self.modelVideoLayerFrame = [self bounds]; if (auto* model = _fullscreenInterface->videoFullscreenModel()) model->setVideoLayerFrame(self.modelVideoLayerFrame); } [(UIView *)[_videoSublayer delegate] setTransform:CGAffineTransformIdentity]; [CATransaction commit]; } - (void)setVideoGravity:(NSString *)videoGravity { #if PLATFORM(IOSMAC) // FIXME: remove this #if once this radar lands. if (!videoGravity) videoGravity = getAVLayerVideoGravityResizeAspect(); #endif _videoGravity = videoGravity; if (![_avPlayerController delegate]) return; WebCore::MediaPlayerEnums::VideoGravity gravity = WebCore::MediaPlayerEnums::VideoGravityResizeAspect; if (videoGravity == getAVLayerVideoGravityResize()) gravity = WebCore::MediaPlayerEnums::VideoGravityResize; if (videoGravity == getAVLayerVideoGravityResizeAspect()) gravity = WebCore::MediaPlayerEnums::VideoGravityResizeAspect; else if (videoGravity == getAVLayerVideoGravityResizeAspectFill()) gravity = WebCore::MediaPlayerEnums::VideoGravityResizeAspectFill; else ASSERT_NOT_REACHED(); if (auto* model = _fullscreenInterface->videoFullscreenModel()) model->setVideoLayerGravity(gravity); } - (NSString *)videoGravity { return _videoGravity.get(); } - (CGRect)videoRect { if (self.videoDimensions.width <= 0 || self.videoDimensions.height <= 0) return self.bounds; float videoAspectRatio = self.videoDimensions.width / self.videoDimensions.height; if ([getAVLayerVideoGravityResizeAspect() isEqualToString:self.videoGravity]) return largestRectWithAspectRatioInsideRect(videoAspectRatio, self.bounds); if ([getAVLayerVideoGravityResizeAspectFill() isEqualToString:self.videoGravity]) return smallestRectWithAspectRatioAroundRect(videoAspectRatio, self.bounds); return self.bounds; } + (NSSet *)keyPathsForValuesAffectingVideoRect { return [NSSet setWithObjects:@"videoDimensions", @"videoGravity", nil]; } @end @interface WebAVPictureInPicturePlayerLayerView : UIView @end static Class WebAVPictureInPicturePlayerLayerView_layerClass(id, SEL) { return [WebAVPlayerLayer class]; } static WebAVPictureInPicturePlayerLayerView *allocWebAVPictureInPicturePlayerLayerViewInstance() { static Class theClass = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ theClass = objc_allocateClassPair(getUIViewClass(), "WebAVPictureInPicturePlayerLayerView", 0); objc_registerClassPair(theClass); Class metaClass = objc_getMetaClass("WebAVPictureInPicturePlayerLayerView"); class_addMethod(metaClass, @selector(layerClass), (IMP)WebAVPictureInPicturePlayerLayerView_layerClass, "@@:"); }); return (WebAVPictureInPicturePlayerLayerView *)[theClass alloc]; } @interface WebAVPlayerLayerView : __AVPlayerLayerView @property (retain) UIView* videoView; @end static Class WebAVPlayerLayerView_layerClass(id, SEL) { return [WebAVPlayerLayer class]; } static AVPlayerController *WebAVPlayerLayerView_playerController(id aSelf, SEL) { __AVPlayerLayerView *playerLayer = aSelf; WebAVPlayerLayer *webAVPlayerLayer = (WebAVPlayerLayer *)[playerLayer playerLayer]; return [webAVPlayerLayer playerController]; } static void WebAVPlayerLayerView_setPlayerController(id aSelf, SEL, AVPlayerController *playerController) { __AVPlayerLayerView *playerLayerView = aSelf; WebAVPlayerLayer *webAVPlayerLayer = (WebAVPlayerLayer *)[playerLayerView playerLayer]; [webAVPlayerLayer setPlayerController: playerController]; } static AVPlayerLayer *WebAVPlayerLayerView_playerLayer(id aSelf, SEL) { __AVPlayerLayerView *playerLayerView = aSelf; if ([get__AVPlayerLayerViewClass() instancesRespondToSelector:@selector(playerLayer)]) { objc_super superClass { playerLayerView, get__AVPlayerLayerViewClass() }; auto superClassMethod = reinterpret_cast(objc_msgSendSuper); return superClassMethod(&superClass, @selector(playerLayer)); } return (AVPlayerLayer *)[playerLayerView layer]; } static UIView *WebAVPlayerLayerView_videoView(id aSelf, SEL) { __AVPlayerLayerView *playerLayerView = aSelf; WebAVPlayerLayer *webAVPlayerLayer = (WebAVPlayerLayer *)[playerLayerView playerLayer]; CALayer* videoLayer = [webAVPlayerLayer videoSublayer]; if (!videoLayer || !videoLayer.delegate) return nil; ASSERT([[videoLayer delegate] isKindOfClass:getUIViewClass()]); return (UIView *)[videoLayer delegate]; } static void WebAVPlayerLayerView_setVideoView(id aSelf, SEL, UIView *videoView) { __AVPlayerLayerView *playerLayerView = aSelf; WebAVPlayerLayer *webAVPlayerLayer = (WebAVPlayerLayer *)[playerLayerView playerLayer]; [webAVPlayerLayer setVideoSublayer:[videoView layer]]; } static void WebAVPlayerLayerView_startRoutingVideoToPictureInPicturePlayerLayerView(id aSelf, SEL) { WebAVPlayerLayerView *playerLayerView = aSelf; WebAVPictureInPicturePlayerLayerView *pipView = (WebAVPictureInPicturePlayerLayerView *)[playerLayerView pictureInPicturePlayerLayerView]; WebAVPlayerLayer *playerLayer = (WebAVPlayerLayer *)[playerLayerView playerLayer]; WebAVPlayerLayer *pipPlayerLayer = (WebAVPlayerLayer *)[pipView layer]; [playerLayer setVideoGravity:getAVLayerVideoGravityResizeAspect()]; [pipPlayerLayer setVideoSublayer:playerLayer.videoSublayer]; [pipPlayerLayer setVideoDimensions:playerLayer.videoDimensions]; [pipPlayerLayer setVideoGravity:playerLayer.videoGravity]; [pipPlayerLayer setModelVideoLayerFrame:playerLayer.modelVideoLayerFrame]; [pipPlayerLayer setPlayerController:playerLayer.playerController]; [pipPlayerLayer setFullscreenInterface:playerLayer.fullscreenInterface]; [pipView addSubview:playerLayerView.videoView]; } static void WebAVPlayerLayerView_stopRoutingVideoToPictureInPicturePlayerLayerView(id aSelf, SEL) { WebAVPlayerLayerView *playerLayerView = aSelf; if (UIView *videoView = playerLayerView.videoView) [playerLayerView addSubview:videoView]; WebAVPictureInPicturePlayerLayerView *pipView = (WebAVPictureInPicturePlayerLayerView *)[playerLayerView pictureInPicturePlayerLayerView]; WebAVPlayerLayer *playerLayer = (WebAVPlayerLayer *)[playerLayerView playerLayer]; WebAVPlayerLayer *pipPlayerLayer = (WebAVPlayerLayer *)[pipView layer]; [playerLayer setModelVideoLayerFrame:pipPlayerLayer.modelVideoLayerFrame]; } static WebAVPictureInPicturePlayerLayerView *WebAVPlayerLayerView_pictureInPicturePlayerLayerView(id aSelf, SEL) { WebAVPlayerLayerView *playerLayerView = aSelf; WebAVPictureInPicturePlayerLayerView *pipView = [playerLayerView valueForKey:@"_pictureInPicturePlayerLayerView"]; if (!pipView) { pipView = [allocWebAVPictureInPicturePlayerLayerViewInstance() initWithFrame:CGRectZero]; [playerLayerView setValue:pipView forKey:@"_pictureInPicturePlayerLayerView"]; } return pipView; } static void WebAVPlayerLayerView_dealloc(id aSelf, SEL) { WebAVPlayerLayerView *playerLayerView = aSelf; RetainPtr pipView = adoptNS([playerLayerView valueForKey:@"_pictureInPicturePlayerLayerView"]); [playerLayerView setValue:nil forKey:@"_pictureInPicturePlayerLayerView"]; objc_super superClass { playerLayerView, get__AVPlayerLayerViewClass() }; auto super_dealloc = reinterpret_cast(objc_msgSendSuper); super_dealloc(&superClass, @selector(dealloc)); } #pragma mark - Methods static WebAVPlayerLayerView *allocWebAVPlayerLayerViewInstance() { static Class theClass = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ ASSERT(get__AVPlayerLayerViewClass()); theClass = objc_allocateClassPair(get__AVPlayerLayerViewClass(), "WebAVPlayerLayerView", 0); class_addMethod(theClass, @selector(dealloc), (IMP)WebAVPlayerLayerView_dealloc, "v@:"); class_addMethod(theClass, @selector(setPlayerController:), (IMP)WebAVPlayerLayerView_setPlayerController, "v@:@"); class_addMethod(theClass, @selector(playerController), (IMP)WebAVPlayerLayerView_playerController, "@@:"); class_addMethod(theClass, @selector(setVideoView:), (IMP)WebAVPlayerLayerView_setVideoView, "v@:@"); class_addMethod(theClass, @selector(videoView), (IMP)WebAVPlayerLayerView_videoView, "@@:"); class_addMethod(theClass, @selector(playerLayer), (IMP)WebAVPlayerLayerView_playerLayer, "@@:"); class_addMethod(theClass, @selector(startRoutingVideoToPictureInPicturePlayerLayerView), (IMP)WebAVPlayerLayerView_startRoutingVideoToPictureInPicturePlayerLayerView, "v@:"); class_addMethod(theClass, @selector(stopRoutingVideoToPictureInPicturePlayerLayerView), (IMP)WebAVPlayerLayerView_stopRoutingVideoToPictureInPicturePlayerLayerView, "v@:"); class_addMethod(theClass, @selector(pictureInPicturePlayerLayerView), (IMP)WebAVPlayerLayerView_pictureInPicturePlayerLayerView, "@@:"); class_addIvar(theClass, "_pictureInPicturePlayerLayerView", sizeof(WebAVPictureInPicturePlayerLayerView *), log2(sizeof(WebAVPictureInPicturePlayerLayerView *)), "@"); objc_registerClassPair(theClass); Class metaClass = objc_getMetaClass("WebAVPlayerLayerView"); class_addMethod(metaClass, @selector(layerClass), (IMP)WebAVPlayerLayerView_layerClass, "@@:"); }); return (WebAVPlayerLayerView *)[theClass alloc]; } NS_ASSUME_NONNULL_BEGIN @interface WebAVPlayerViewController : NSObject - (instancetype)initWithFullscreenInterface:(VideoFullscreenInterfaceAVKit *)interface; - (void)enterFullScreenAnimated:(BOOL)animated completionHandler:(void (^)(BOOL success, NSError *))completionHandler; - (void)exitFullScreenAnimated:(BOOL)animated completionHandler:(void (^)(BOOL success, NSError *))completionHandler; - (void)startPictureInPicture; - (void)stopPictureInPicture; - (BOOL)playerViewControllerShouldHandleDoneButtonTap:(AVPlayerViewController *)playerViewController; - (void)setWebKitOverrideRouteSharingPolicy:(NSUInteger)routeSharingPolicy routingContextUID:(NSString *)routingContextUID; @end NS_ASSUME_NONNULL_END @implementation WebAVPlayerViewController { VideoFullscreenInterfaceAVKit *_fullscreenInterface; RetainPtr _presentingViewController; RetainPtr _avPlayerViewController; id _delegate; } - (instancetype)initWithFullscreenInterface:(VideoFullscreenInterfaceAVKit *)interface { if (!(self = [super init])) return nil; _fullscreenInterface = interface; _avPlayerViewController = adoptNS([allocAVPlayerViewControllerInstance() initWithPlayerLayerView:interface->playerLayerView()]); #if PLATFORM(WATCHOS) _avPlayerViewController.get().delegate = self; #endif return self; } - (BOOL)playerViewControllerShouldHandleDoneButtonTap:(AVPlayerViewController *)playerViewController { ASSERT(playerViewController == _avPlayerViewController.get()); if (_delegate) return [_delegate playerViewController:playerViewController shouldExitFullScreenWithReason:AVPlayerViewControllerExitFullScreenReasonDoneButtonTapped]; return YES; } - (void)setWebKitOverrideRouteSharingPolicy:(NSUInteger)routeSharingPolicy routingContextUID:(NSString *)routingContextUID { ALLOW_NEW_API_WITHOUT_GUARDS_BEGIN if ([_avPlayerViewController respondsToSelector:@selector(setWebKitOverrideRouteSharingPolicy:routingContextUID:)]) [_avPlayerViewController setWebKitOverrideRouteSharingPolicy:routeSharingPolicy routingContextUID:routingContextUID]; ALLOW_NEW_API_WITHOUT_GUARDS_END } - (void)enterFullScreenAnimated:(BOOL)animated completionHandler:(void (^)(BOOL success, NSError * __nullable error))completionHandler { #if PLATFORM(WATCHOS) _presentingViewController = _fullscreenInterface->presentingViewController(); _avPlayerViewController.get().view.frame = _presentingViewController.get().view.frame; [_presentingViewController presentViewController:_fullscreenInterface->fullscreenViewController() animated:animated completion:^{ if (completionHandler) completionHandler(YES, nil); }]; #else [_avPlayerViewController.get() enterFullScreenAnimated:animated completionHandler:completionHandler]; #endif } - (void)exitFullScreenAnimated:(BOOL)animated completionHandler:(void (^)(BOOL success, NSError * __nullable error))completionHandler { #if PLATFORM(WATCHOS) if (!_presentingViewController) return; [_presentingViewController dismissViewControllerAnimated:animated completion:^{ _presentingViewController = nil; if (completionHandler) completionHandler(YES, nil); }]; #else [_avPlayerViewController.get() exitFullScreenAnimated:animated completionHandler:completionHandler]; #endif } #if PLATFORM(WATCHOS) #define MY_NO_RETURN NO_RETURN_DUE_TO_ASSERT #else #define MY_NO_RETURN #endif - (void)startPictureInPicture MY_NO_RETURN { #if PLATFORM(WATCHOS) ASSERT_NOT_REACHED(); #else [_avPlayerViewController.get() startPictureInPicture]; #endif } - (void)stopPictureInPicture MY_NO_RETURN { #if PLATFORM(WATCHOS) ASSERT_NOT_REACHED(); #else [_avPlayerViewController.get() stopPictureInPicture]; #endif } - (BOOL)isPictureInPicturePossible { #if PLATFORM(WATCHOS) return NO; #else return _avPlayerViewController.get().isPictureInPicturePossible; #endif } - (BOOL)isPictureInPictureActive { #if PLATFORM(WATCHOS) return NO; #else return _avPlayerViewController.get().isPictureInPictureActive; #endif } - (BOOL)pictureInPictureActive { #if PLATFORM(WATCHOS) return NO; #else return _avPlayerViewController.get().pictureInPictureActive; #endif } - (BOOL)pictureInPictureWasStartedWhenEnteringBackground { #if PLATFORM(WATCHOS) return NO; #else return _avPlayerViewController.get().pictureInPictureWasStartedWhenEnteringBackground; #endif } - (UIView *) view { return _avPlayerViewController.get().view; } - (BOOL)showsPlaybackControls { #if PLATFORM(WATCHOS) return YES; #else return _avPlayerViewController.get().showsPlaybackControls; #endif } - (void)setShowsPlaybackControls:(BOOL)showsPlaybackControls { #if PLATFORM(WATCHOS) UNUSED_PARAM(showsPlaybackControls); #else _avPlayerViewController.get().showsPlaybackControls = showsPlaybackControls; #endif } - (void)setAllowsPictureInPicturePlayback:(BOOL)allowsPictureInPicturePlayback { #if PLATFORM(WATCHOS) UNUSED_PARAM(allowsPictureInPicturePlayback); #else _avPlayerViewController.get().allowsPictureInPicturePlayback = allowsPictureInPicturePlayback; #endif } - (void)setDelegate:(id )delegate { #if PLATFORM(WATCHOS) ASSERT(!delegate || [delegate respondsToSelector:@selector(playerViewController:shouldExitFullScreenWithReason:)]); _delegate = id(delegate); #else _avPlayerViewController.get().delegate = delegate; #endif } - (void)setPlayerController:(AVPlayerController *)playerController { _avPlayerViewController.get().playerController = playerController; } - (AVPlayerViewController *) avPlayerViewController { return _avPlayerViewController.get(); } - (void)removeFromParentViewController { [_avPlayerViewController.get() removeFromParentViewController]; } @end Ref VideoFullscreenInterfaceAVKit::create(PlaybackSessionInterfaceAVKit& playbackSessionInterface) { Ref interface = adoptRef(*new VideoFullscreenInterfaceAVKit(playbackSessionInterface)); [interface->m_playerViewControllerDelegate setFullscreenInterface:interface.ptr()]; return interface; } VideoFullscreenInterfaceAVKit::VideoFullscreenInterfaceAVKit(PlaybackSessionInterfaceAVKit& playbackSessionInterface) : m_playbackSessionInterface(playbackSessionInterface) , m_playerViewControllerDelegate(adoptNS([[WebAVPlayerViewControllerDelegate alloc] init])) , m_watchdogTimer(RunLoop::main(), this, &VideoFullscreenInterfaceAVKit::watchdogTimerFired) { } VideoFullscreenInterfaceAVKit::~VideoFullscreenInterfaceAVKit() { WebAVPlayerController* playerController = this->playerController(); if (playerController && playerController.externalPlaybackActive) externalPlaybackChanged(false, PlaybackSessionModel::TargetTypeNone, ""); if (m_videoFullscreenModel) m_videoFullscreenModel->removeClient(*this); } WebAVPlayerController *VideoFullscreenInterfaceAVKit::playerController() const { return m_playbackSessionInterface->playerController(); } void VideoFullscreenInterfaceAVKit::setVideoFullscreenModel(VideoFullscreenModel* model) { if (m_videoFullscreenModel) m_videoFullscreenModel->removeClient(*this); m_videoFullscreenModel = model; if (m_videoFullscreenModel) { m_videoFullscreenModel->addClient(*this); m_videoFullscreenModel->requestRouteSharingPolicyAndContextUID([this, protectedThis = makeRefPtr(this)] (RouteSharingPolicy policy, String contextUID) { m_routeSharingPolicy = policy; m_routingContextUID = contextUID; if (m_playerViewController && !m_routingContextUID.isEmpty()) [m_playerViewController setWebKitOverrideRouteSharingPolicy:(NSUInteger)m_routeSharingPolicy routingContextUID:m_routingContextUID]; }); } hasVideoChanged(m_videoFullscreenModel ? m_videoFullscreenModel->hasVideo() : false); videoDimensionsChanged(m_videoFullscreenModel ? m_videoFullscreenModel->videoDimensions() : FloatSize()); } void VideoFullscreenInterfaceAVKit::setVideoFullscreenChangeObserver(VideoFullscreenChangeObserver* observer) { m_fullscreenChangeObserver = observer; } void VideoFullscreenInterfaceAVKit::hasVideoChanged(bool hasVideo) { [playerController() setHasEnabledVideo:hasVideo]; [playerController() setHasVideo:hasVideo]; } void VideoFullscreenInterfaceAVKit::videoDimensionsChanged(const FloatSize& videoDimensions) { if (videoDimensions.isZero()) return; WebAVPlayerLayer *playerLayer = (WebAVPlayerLayer *)[m_playerLayerView playerLayer]; [playerLayer setVideoDimensions:videoDimensions]; [playerController() setContentDimensions:videoDimensions]; [m_playerLayerView setNeedsLayout]; WebAVPictureInPicturePlayerLayerView *pipView = (WebAVPictureInPicturePlayerLayerView *)[m_playerLayerView pictureInPicturePlayerLayerView]; WebAVPlayerLayer *pipPlayerLayer = (WebAVPlayerLayer *)[pipView layer]; [pipPlayerLayer setVideoDimensions:playerLayer.videoDimensions]; [pipView setNeedsLayout]; } void VideoFullscreenInterfaceAVKit::externalPlaybackChanged(bool enabled, PlaybackSessionModel::ExternalPlaybackTargetType, const String&) { [m_playerLayerView setHidden:enabled]; } bool VideoFullscreenInterfaceAVKit::pictureInPictureWasStartedWhenEnteringBackground() const { return [m_playerViewController pictureInPictureWasStartedWhenEnteringBackground]; } static UIViewController *fallbackViewController(UIView *view) { for (UIView *currentView = view; currentView; currentView = currentView.superview) { if (UIViewController *viewController = [getUIViewControllerClass() viewControllerForView:currentView]) { if (![viewController parentViewController]) return viewController; } } LOG_ERROR("Failed to find a view controller suitable to present fullscreen video"); return nil; } UIViewController *VideoFullscreenInterfaceAVKit::presentingViewController() { auto *controller = videoFullscreenModel()->presentingViewController(); if (!controller) controller = fallbackViewController(m_parentView.get()); return controller; } void VideoFullscreenInterfaceAVKit::applicationDidBecomeActive() { LOG(Fullscreen, "VideoFullscreenInterfaceAVKit::applicationDidBecomeActive(%p)", this); } void VideoFullscreenInterfaceAVKit::setupFullscreen(UIView& videoView, const IntRect& initialRect, UIView* parentView, HTMLMediaElementEnums::VideoFullscreenMode mode, bool allowsPictureInPicturePlayback, bool standby) { ASSERT(standby || mode != HTMLMediaElementEnums::VideoFullscreenModeNone); LOG(Fullscreen, "VideoFullscreenInterfaceAVKit::setupFullscreen(%p)", this); m_allowsPictureInPicturePlayback = allowsPictureInPicturePlayback; m_videoView = &videoView; m_parentView = parentView; m_parentWindow = parentView.window; m_targetStandby = standby; m_targetMode = mode; setInlineRect(initialRect, true); doSetup(); } void VideoFullscreenInterfaceAVKit::enterFullscreen() { LOG(Fullscreen, "VideoFullscreenInterfaceAVKit::enterFullscreen(%p) %d", this, mode()); doEnterFullscreen(); } void VideoFullscreenInterfaceAVKit::exitFullscreen(const IntRect& finalRect) { m_watchdogTimer.stop(); m_targetMode = HTMLMediaElementEnums::VideoFullscreenModeNone; setInlineRect(finalRect, true); doExitFullscreen(); } void VideoFullscreenInterfaceAVKit::cleanupFullscreen() { LOG(Fullscreen, "VideoFullscreenInterfaceAVKit::cleanupFullscreen(%p)", this); m_cleanupNeedsReturnVideoContentLayer = true; if (m_hasVideoContentLayer && m_fullscreenChangeObserver) { m_fullscreenChangeObserver->returnVideoContentLayer(); return; } m_cleanupNeedsReturnVideoContentLayer = false; if (m_window) { [m_window setHidden:YES]; [m_window setRootViewController:nil]; } [m_playerViewController setDelegate:nil]; [m_playerViewController setPlayerController:nil]; if (m_currentMode.hasPictureInPicture()) [m_playerViewController stopPictureInPicture]; if (m_currentMode.hasFullscreen()) { [[m_playerViewController view] layoutIfNeeded]; [m_playerViewController exitFullScreenAnimated:NO completionHandler:[] (BOOL success, NSError* error) { if (!success) WTFLogAlways("-[AVPlayerViewController exitFullScreenAnimated:completionHandler:] failed with error %s", [[error localizedDescription] UTF8String]); }]; } [[m_playerViewController view] removeFromSuperview]; if (m_viewController) [m_playerViewController removeFromParentViewController]; [m_playerLayerView removeFromSuperview]; [[m_viewController view] removeFromSuperview]; m_playerLayerView = nil; m_playerViewController = nil; m_window = nil; m_videoView = nil; m_parentView = nil; m_parentWindow = nil; if (m_fullscreenChangeObserver) m_fullscreenChangeObserver->didCleanupFullscreen(); } void VideoFullscreenInterfaceAVKit::invalidate() { m_videoFullscreenModel = nil; m_fullscreenChangeObserver = nil; cleanupFullscreen(); } void VideoFullscreenInterfaceAVKit::requestHideAndExitFullscreen() { if (m_currentMode.hasPictureInPicture()) return; LOG(Fullscreen, "VideoFullscreenInterfaceAVKit::requestHideAndExitFullscreen(%p)", this); [m_window setHidden:YES]; [[m_playerViewController view] setHidden:YES]; if (playbackSessionModel() && m_videoFullscreenModel) { playbackSessionModel()->pause(); m_videoFullscreenModel->requestFullscreenMode(HTMLMediaElementEnums::VideoFullscreenModeNone); } } void VideoFullscreenInterfaceAVKit::preparedToReturnToInline(bool visible, const IntRect& inlineRect) { LOG(Fullscreen, "VideoFullscreenInterfaceAVKit::preparedToReturnToInline(%p) - visible(%s)", this, boolString(visible)); setInlineRect(inlineRect, visible); if (m_prepareToInlineCallback) { WTF::Function callback = WTFMove(m_prepareToInlineCallback); callback(visible); } } void VideoFullscreenInterfaceAVKit::preparedToExitFullscreen() { #if PLATFORM(WATCHOS) if (!m_waitingForPreparedToExit) return; m_waitingForPreparedToExit = false; m_videoFullscreenModel->requestFullscreenMode(HTMLMediaElementEnums::VideoFullscreenModeNone, true); #endif } bool VideoFullscreenInterfaceAVKit::mayAutomaticallyShowVideoPictureInPicture() const { return [playerController() isPlaying] && (m_standby || m_currentMode.isFullscreen()) && supportsPictureInPicture(); } void VideoFullscreenInterfaceAVKit::fullscreenMayReturnToInline(WTF::Function&& callback) { m_prepareToInlineCallback = WTFMove(callback); if (m_fullscreenChangeObserver) m_fullscreenChangeObserver->fullscreenMayReturnToInline(); } void VideoFullscreenInterfaceAVKit::willStartPictureInPicture() { LOG(Fullscreen, "VideoFullscreenInterfaceAVKit::willStartPictureInPicture(%p)", this); if (m_standby && !m_currentMode.hasVideo()) { [m_window setHidden:NO]; [[m_playerViewController view] setHidden:NO]; } if (!m_hasVideoContentLayer) m_fullscreenChangeObserver->requestVideoContentLayer(); if (m_videoFullscreenModel) m_videoFullscreenModel->willEnterPictureInPicture(); } void VideoFullscreenInterfaceAVKit::didStartPictureInPicture() { LOG(Fullscreen, "VideoFullscreenInterfaceAVKit::didStartPictureInPicture(%p)", this); setMode(HTMLMediaElementEnums::VideoFullscreenModePictureInPicture); [m_playerViewController setShowsPlaybackControls:YES]; if (m_currentMode.hasFullscreen()) { m_shouldReturnToFullscreenWhenStoppingPiP = YES; [[m_playerViewController view] layoutIfNeeded]; [m_playerViewController exitFullScreenAnimated:YES completionHandler:[protectedThis = makeRefPtr(this), this] (BOOL success, NSError *error) { exitFullscreenHandler(success, error); }]; } else { [m_window setHidden:YES]; [[m_playerViewController view] setHidden:YES]; } if (m_videoFullscreenModel) m_videoFullscreenModel->didEnterPictureInPicture(); if (m_enterFullscreenNeedsEnterPictureInPicture) doEnterFullscreen(); } void VideoFullscreenInterfaceAVKit::failedToStartPictureInPicture() { LOG(Fullscreen, "VideoFullscreenInterfaceAVKit::failedToStartPictureInPicture(%p)", this); [m_playerViewController setShowsPlaybackControls:YES]; m_targetMode.setPictureInPicture(false); if (m_currentMode.hasFullscreen()) return; if (m_fullscreenChangeObserver) m_fullscreenChangeObserver->didEnterFullscreen(); if (m_videoFullscreenModel) m_videoFullscreenModel->failedToEnterPictureInPicture(); if (m_videoFullscreenModel) m_videoFullscreenModel->requestFullscreenMode(HTMLMediaElementEnums::VideoFullscreenModeNone); } void VideoFullscreenInterfaceAVKit::willStopPictureInPicture() { LOG(Fullscreen, "VideoFullscreenInterfaceAVKit::willStopPictureInPicture(%p)", this); m_shouldReturnToFullscreenWhenStoppingPiP = false; if (m_currentMode.hasFullscreen() || m_restoringFullscreenForPictureInPictureStop) return; [m_window setHidden:NO]; [[m_playerViewController view] setHidden:NO]; if (m_videoFullscreenModel) m_videoFullscreenModel->willExitPictureInPicture(); } void VideoFullscreenInterfaceAVKit::didStopPictureInPicture() { LOG(Fullscreen, "VideoFullscreenInterfaceAVKit::didStopPictureInPicture(%p)", this); m_targetMode.setPictureInPicture(false); if (m_currentMode.hasFullscreen() || m_restoringFullscreenForPictureInPictureStop) { clearMode(HTMLMediaElementEnums::VideoFullscreenModePictureInPicture); [m_playerViewController setShowsPlaybackControls:YES]; if (m_exitFullscreenNeedsExitPictureInPicture) doExitFullscreen(); return; } [m_playerLayerView setBackgroundColor:clearUIColor()]; [[m_playerViewController view] setBackgroundColor:clearUIColor()]; if (m_videoFullscreenModel) m_videoFullscreenModel->requestFullscreenMode(HTMLMediaElementEnums::VideoFullscreenModeNone); clearMode(HTMLMediaElementEnums::VideoFullscreenModePictureInPicture); if (m_videoFullscreenModel) m_videoFullscreenModel->didExitPictureInPicture(); if (m_enterFullscreenNeedsExitPictureInPicture) doEnterFullscreen(); if (m_exitFullscreenNeedsExitPictureInPicture) doExitFullscreen(); } void VideoFullscreenInterfaceAVKit::prepareForPictureInPictureStopWithCompletionHandler(void (^completionHandler)(BOOL restored)) { LOG(Fullscreen, "VideoFullscreenInterfaceAVKit::prepareForPictureInPictureStopWithCompletionHandler(%p)", this); if (m_shouldReturnToFullscreenWhenStoppingPiP) { m_shouldReturnToFullscreenWhenStoppingPiP = false; m_restoringFullscreenForPictureInPictureStop = true; [m_window setHidden:NO]; [[m_playerViewController view] setHidden:NO]; [[m_playerViewController view] layoutIfNeeded]; [m_playerViewController enterFullScreenAnimated:YES completionHandler:^(BOOL success, NSError *error) { enterFullscreenHandler(success, error); completionHandler(success); }]; return; } fullscreenMayReturnToInline([protectedThis = makeRefPtr(this), strongCompletionHandler = adoptNS([completionHandler copy])](bool restored) { LOG(Fullscreen, "VideoFullscreenInterfaceAVKit::prepareForPictureInPictureStopWithCompletionHandler lambda(%p) - restored(%s)", protectedThis.get(), boolString(restored)); ((void (^)(BOOL))strongCompletionHandler.get())(restored); }); } bool VideoFullscreenInterfaceAVKit::shouldExitFullscreenWithReason(VideoFullscreenInterfaceAVKit::ExitFullScreenReason reason) { if (!m_videoFullscreenModel) return true; if (reason == ExitFullScreenReason::PictureInPictureStarted) return false; if (playbackSessionModel() && (reason == ExitFullScreenReason::DoneButtonTapped || reason == ExitFullScreenReason::RemoteControlStopEventReceived)) playbackSessionModel()->pause(); if (!m_watchdogTimer.isActive() && !ignoreWatchdogForDebugging) m_watchdogTimer.startOneShot(defaultWatchdogTimerInterval); #if PLATFORM(WATCHOS) if (m_fullscreenChangeObserver) { m_waitingForPreparedToExit = true; m_fullscreenChangeObserver->willExitFullscreen(); return false; } #endif BOOL finished = reason == ExitFullScreenReason::DoneButtonTapped || reason == ExitFullScreenReason::PinchGestureHandled; m_videoFullscreenModel->requestFullscreenMode(HTMLMediaElementEnums::VideoFullscreenModeNone, finished); return false; } void VideoFullscreenInterfaceAVKit::setHasVideoContentLayer(bool value) { m_hasVideoContentLayer = value; if (m_hasVideoContentLayer && m_finalizeSetupNeedsVideoContentLayer) finalizeSetup(); if (!m_hasVideoContentLayer && m_cleanupNeedsReturnVideoContentLayer) cleanupFullscreen(); if (!m_hasVideoContentLayer && m_returnToStandbyNeedsReturnVideoContentLayer) returnToStandby(); if (!m_hasVideoContentLayer && m_finalizeSetupNeedsReturnVideoContentLayer) finalizeSetup(); if (!m_hasVideoContentLayer && m_exitFullscreenNeedsReturnContentLayer) doExitFullscreen(); } void VideoFullscreenInterfaceAVKit::setInlineRect(const IntRect& inlineRect, bool visible) { m_inlineRect = inlineRect; m_inlineIsVisible = visible; m_hasUpdatedInlineRect = true; if (m_playerViewController && m_parentView) { [CATransaction begin]; [CATransaction setDisableActions:YES]; [m_playerViewController view].frame = [m_parentView convertRect:inlineRect toView:[m_playerViewController view].superview]; [CATransaction commit]; } if (m_setupNeedsInlineRect) doSetup(); if (m_exitFullscreenNeedInlineRect) doExitFullscreen(); } void VideoFullscreenInterfaceAVKit::doSetup() { Mode changes { m_currentMode.mode() ^ m_targetMode.mode() }; if (m_currentMode.hasVideo() && m_targetMode.hasVideo() && (m_standby != m_targetStandby)) { m_standby = m_targetStandby; finalizeSetup(); return; } if (!m_hasUpdatedInlineRect && m_fullscreenChangeObserver) { m_setupNeedsInlineRect = true; m_fullscreenChangeObserver->requestUpdateInlineRect(); return; } m_setupNeedsInlineRect = false; [CATransaction begin]; [CATransaction setDisableActions:YES]; #if !PLATFORM(WATCHOS) if (![[m_parentView window] _isHostedInAnotherProcess] && !m_window) { if (!m_window) m_window = adoptNS([allocUIWindowInstance() initWithFrame:[[getUIScreenClass() mainScreen] bounds]]); [m_window setBackgroundColor:clearUIColor()]; if (!m_viewController) m_viewController = adoptNS([allocUIViewControllerInstance() init]); [[m_viewController view] setFrame:[m_window bounds]]; [m_viewController _setIgnoreAppSupportedOrientations:YES]; [m_window setRootViewController:m_viewController.get()]; [m_window setWindowLevel:getUITextEffectsBeneathStatusBarWindowLevel() + 1]; [m_window makeKeyAndVisible]; } #endif if (!m_playerLayerView) m_playerLayerView = adoptNS([allocWebAVPlayerLayerViewInstance() init]); [m_playerLayerView setHidden:[playerController() isExternalPlaybackActive]]; [m_playerLayerView setBackgroundColor:clearUIColor()]; if (!m_currentMode.hasPictureInPicture()) { [m_playerLayerView setVideoView:m_videoView.get()]; [m_playerLayerView addSubview:m_videoView.get()]; } WebAVPlayerLayer *playerLayer = (WebAVPlayerLayer *)[m_playerLayerView playerLayer]; auto modelVideoLayerFrame = CGRectMake(0, 0, m_inlineRect.width(), m_inlineRect.height()); [playerLayer setModelVideoLayerFrame:modelVideoLayerFrame]; [playerLayer setVideoDimensions:[playerController() contentDimensions]]; playerLayer.fullscreenInterface = this; if (m_videoFullscreenModel) m_videoFullscreenModel->setVideoLayerFrame(modelVideoLayerFrame); if (!m_playerViewController) m_playerViewController = adoptNS([[WebAVPlayerViewController alloc] initWithFullscreenInterface:this]); [m_playerViewController setShowsPlaybackControls:NO]; [m_playerViewController setPlayerController:(AVPlayerController *)playerController()]; [m_playerViewController setDelegate:m_playerViewControllerDelegate.get()]; [m_playerViewController setAllowsPictureInPicturePlayback:m_allowsPictureInPicturePlayback]; [playerController() setPictureInPicturePossible:m_allowsPictureInPicturePlayback]; if (!m_routingContextUID.isEmpty()) [m_playerViewController setWebKitOverrideRouteSharingPolicy:(NSUInteger)m_routeSharingPolicy routingContextUID:m_routingContextUID]; #if PLATFORM(WATCHOS) m_viewController = videoFullscreenModel()->createVideoFullscreenViewController(m_playerViewController.get().avPlayerViewController); #endif if (m_viewController) { [m_viewController addChildViewController:m_playerViewController.get().avPlayerViewController]; [[m_viewController view] addSubview:[m_playerViewController view]]; } else [m_parentView addSubview:[m_playerViewController view]]; [m_playerViewController view].frame = [m_parentView convertRect:m_inlineRect toView:[m_playerViewController view].superview]; [[m_playerViewController view] setBackgroundColor:clearUIColor()]; [[m_playerViewController view] setAutoresizingMask:(UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin)]; [[m_playerViewController view] setNeedsLayout]; [[m_playerViewController view] layoutIfNeeded]; if (m_targetStandby && !m_currentMode.hasVideo()) { [m_window setHidden:YES]; [[m_playerViewController view] setHidden:YES]; } [CATransaction commit]; finalizeSetup(); } void VideoFullscreenInterfaceAVKit::finalizeSetup() { dispatch_async(dispatch_get_main_queue(), [protectedThis = makeRefPtr(this), this] { if (m_fullscreenChangeObserver) { if (!m_hasVideoContentLayer && m_targetMode.hasVideo()) { m_finalizeSetupNeedsVideoContentLayer = true; m_fullscreenChangeObserver->requestVideoContentLayer(); return; } m_finalizeSetupNeedsVideoContentLayer = false; if (m_hasVideoContentLayer && !m_targetMode.hasVideo()) { m_finalizeSetupNeedsReturnVideoContentLayer = true; m_fullscreenChangeObserver->returnVideoContentLayer(); return; } m_finalizeSetupNeedsReturnVideoContentLayer = false; m_fullscreenChangeObserver->didSetupFullscreen(); } }); } void VideoFullscreenInterfaceAVKit::doEnterFullscreen() { m_standby = m_targetStandby; [[m_playerViewController view] layoutIfNeeded]; if (m_targetMode.hasFullscreen() && !m_currentMode.hasFullscreen()) { m_enterFullscreenNeedsEnterFullscreen = true; [m_playerViewController enterFullScreenAnimated:YES completionHandler:[this, protectedThis = makeRefPtr(this)] (BOOL success, NSError *error) { enterFullscreenHandler(success, error); }]; return; } m_enterFullscreenNeedsEnterFullscreen = false; if (m_targetMode.hasPictureInPicture() && !m_currentMode.hasPictureInPicture()) { m_enterFullscreenNeedsEnterPictureInPicture = true; if ([m_playerViewController isPictureInPicturePossible]) [m_playerViewController startPictureInPicture]; else failedToStartPictureInPicture(); return; } m_enterFullscreenNeedsEnterPictureInPicture = false; if (!m_targetMode.hasFullscreen() && m_currentMode.hasFullscreen()) { m_enterFullscreenNeedsExitFullscreen = true; [m_playerViewController exitFullScreenAnimated:YES completionHandler:[protectedThis = makeRefPtr(this), this] (BOOL success, NSError *error) { exitFullscreenHandler(success, error); }]; return; } m_enterFullscreenNeedsExitFullscreen = false; if (!m_targetMode.hasPictureInPicture() && m_currentMode.hasPictureInPicture()) { m_enterFullscreenNeedsExitPictureInPicture = true; [m_playerViewController stopPictureInPicture]; return; } m_enterFullscreenNeedsExitPictureInPicture = false; if (m_fullscreenChangeObserver) m_fullscreenChangeObserver->didEnterFullscreen(); } void VideoFullscreenInterfaceAVKit::doExitFullscreen() { LOG(Fullscreen, "VideoFullscreenInterfaceAVKit::doExitFullscreen(%p)", this); if (m_currentMode.hasVideo() && !m_hasUpdatedInlineRect && m_fullscreenChangeObserver) { m_exitFullscreenNeedInlineRect = true; m_fullscreenChangeObserver->requestUpdateInlineRect(); return; } m_exitFullscreenNeedInlineRect = false; if (m_currentMode.hasMode(HTMLMediaElementEnums::VideoFullscreenModeStandard)) { m_exitFullscreenNeedsExitFullscreen = true; [m_playerViewController exitFullScreenAnimated:YES completionHandler:[protectedThis = makeRefPtr(this), this] (BOOL success, NSError *error) { exitFullscreenHandler(success, error); }]; return; } m_exitFullscreenNeedsExitFullscreen = false; if (m_currentMode.hasMode(HTMLMediaElementEnums::VideoFullscreenModePictureInPicture)) { m_exitFullscreenNeedsExitPictureInPicture = true; m_shouldReturnToFullscreenWhenStoppingPiP = false; [m_window setHidden:NO]; [m_playerViewController stopPictureInPicture]; return; } m_exitFullscreenNeedsExitPictureInPicture = false; if (m_hasVideoContentLayer && m_fullscreenChangeObserver) { m_exitFullscreenNeedsReturnContentLayer = true; m_fullscreenChangeObserver->returnVideoContentLayer(); return; } m_exitFullscreenNeedsReturnContentLayer = false; m_standby = false; dispatch_async(dispatch_get_main_queue(), [protectedThis = makeRefPtr(this), this] { if (m_fullscreenChangeObserver) m_fullscreenChangeObserver->didExitFullscreen(); }); } void VideoFullscreenInterfaceAVKit::exitFullscreenHandler(BOOL success, NSError* error) { if (!success) WTFLogAlways("-[AVPlayerViewController exitFullScreenAnimated:completionHandler:] failed with error %s", [[error localizedDescription] UTF8String]); LOG(Fullscreen, "VideoFullscreenInterfaceAVKit::didExitFullscreen(%p) - %d", this, success); clearMode(HTMLMediaElementEnums::VideoFullscreenModeStandard); if (hasMode(HTMLMediaElementEnums::VideoFullscreenModePictureInPicture)) { [m_window setHidden:YES]; [[m_playerViewController view] setHidden:YES]; } else { [CATransaction begin]; [CATransaction setDisableActions:YES]; [m_playerLayerView setBackgroundColor:clearUIColor()]; [[m_playerViewController view] setBackgroundColor:clearUIColor()]; [CATransaction commit]; } if (m_enterFullscreenNeedsExitFullscreen) doEnterFullscreen(); if (m_exitFullscreenNeedsExitFullscreen) doExitFullscreen(); } void VideoFullscreenInterfaceAVKit::enterFullscreenHandler(BOOL success, NSError* error) { if (!success) WTFLogAlways("-[AVPlayerViewController enterFullScreenAnimated:completionHandler:] failed with error %s", [[error localizedDescription] UTF8String]); LOG(Fullscreen, "VideoFullscreenInterfaceAVKit::enterFullscreenStandard - lambda(%p)", this); setMode(HTMLMediaElementEnums::VideoFullscreenModeStandard); [m_playerViewController setShowsPlaybackControls:YES]; m_restoringFullscreenForPictureInPictureStop = false; if (m_enterFullscreenNeedsEnterFullscreen) doEnterFullscreen(); } void VideoFullscreenInterfaceAVKit::returnToStandby() { if (m_hasVideoContentLayer && m_fullscreenChangeObserver) { m_returnToStandbyNeedsReturnVideoContentLayer = true; m_fullscreenChangeObserver->returnVideoContentLayer(); return; } m_returnToStandbyNeedsReturnVideoContentLayer = false; [m_window setHidden:YES]; [[m_playerViewController view] setHidden:YES]; } NO_RETURN_DUE_TO_ASSERT void VideoFullscreenInterfaceAVKit::watchdogTimerFired() { LOG(Fullscreen, "VideoFullscreenInterfaceAVKit::watchdogTimerFired(%p) - no exit fullscreen response in %gs; forcing fullscreen hidden.", this, defaultWatchdogTimerInterval.value()); ASSERT_NOT_REACHED(); [m_window setHidden:YES]; [[m_playerViewController view] setHidden:YES]; } void VideoFullscreenInterfaceAVKit::setMode(HTMLMediaElementEnums::VideoFullscreenMode mode) { if ((m_currentMode.mode() & mode) == mode) return; m_currentMode.setMode(mode); if (m_videoFullscreenModel) m_videoFullscreenModel->fullscreenModeChanged(m_currentMode.mode()); } void VideoFullscreenInterfaceAVKit::clearMode(HTMLMediaElementEnums::VideoFullscreenMode mode) { if ((~m_currentMode.mode() & mode) == mode) return; m_currentMode.clearMode(mode); if (m_videoFullscreenModel) m_videoFullscreenModel->fullscreenModeChanged(m_currentMode.mode()); } bool VideoFullscreenInterfaceAVKit::isPlayingVideoInEnhancedFullscreen() const { return hasMode(WebCore::HTMLMediaElementEnums::VideoFullscreenModePictureInPicture) && [playerController() isPlaying]; } #endif // HAVE(AVKIT) bool WebCore::supportsPictureInPicture() { #if PLATFORM(IOS_FAMILY) && HAVE(AVKIT) && !PLATFORM(WATCHOS) return [getAVPictureInPictureControllerClass() isPictureInPictureSupported]; #else return false; #endif } #endif // PLATFORM(IOS_FAMILY)