WebVideoFullscreenInterfaceAVKit.mm [plain text]
/*
* Copyright (C) 2014 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) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000
#import "WebVideoFullscreenInterfaceAVKit.h"
#import "Logging.h"
#import "GeometryUtilities.h"
#import "WebVideoFullscreenModel.h"
#import <AVFoundation/AVTime.h>
#import <AVKit/AVKit.h>
#import <AVKit/AVPlayerController.h>
#import <AVKit/AVPlayerViewController_Private.h>
#import <AVKit/AVPlayerViewController_WebKitOnly.h>
#import <AVKit/AVValueTiming.h>
#import <AVKit/AVVideoLayer.h>
#import <CoreMedia/CMTime.h>
#import <UIKit/UIKit.h>
#import <WebCore/RuntimeApplicationChecksIOS.h>
#import <WebCore/SoftLinking.h>
#import <WebCore/TimeRanges.h>
#import <WebCore/WebCoreThreadRun.h>
#import <wtf/RetainPtr.h>
#import <wtf/text/CString.h>
using namespace WebCore;
SOFT_LINK_FRAMEWORK(AVFoundation)
SOFT_LINK_CLASS(AVFoundation, AVPlayerLayer)
SOFT_LINK_FRAMEWORK(AVKit)
SOFT_LINK_CLASS(AVKit, AVPlayerController)
SOFT_LINK_CLASS(AVKit, AVPlayerViewController)
SOFT_LINK_CLASS(AVKit, AVValueTiming)
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_FRAMEWORK(CoreMedia)
SOFT_LINK(CoreMedia, CMTimeMakeWithSeconds, CMTime, (Float64 seconds, int32_t preferredTimeScale), (seconds, preferredTimeScale))
SOFT_LINK(CoreMedia, CMTimeGetSeconds, Float64, (CMTime time), (time))
SOFT_LINK(CoreMedia, CMTimeMake, CMTime, (int64_t value, int32_t timescale), (value, timescale))
SOFT_LINK(CoreMedia, CMTimeRangeContainsTime, Boolean, (CMTimeRange range, CMTime time), (range, time))
SOFT_LINK(CoreMedia, CMTimeRangeGetEnd, CMTime, (CMTimeRange range), (range))
SOFT_LINK(CoreMedia, CMTimeRangeMake, CMTimeRange, (CMTime start, CMTime duration), (start, duration))
SOFT_LINK(CoreMedia, CMTimeSubtract, CMTime, (CMTime minuend, CMTime subtrahend), (minuend, subtrahend))
SOFT_LINK(CoreMedia, CMTimeMaximum, CMTime, (CMTime time1, CMTime time2), (time1, time2))
SOFT_LINK(CoreMedia, CMTimeMinimum, CMTime, (CMTime time1, CMTime time2), (time1, time2))
SOFT_LINK_CONSTANT(CoreMedia, kCMTimeIndefinite, CMTime)
#define kCMTimeIndefinite getkCMTimeIndefinite()
@class WebAVMediaSelectionOption;
@interface WebAVPlayerController : NSObject <AVPlayerViewControllerDelegate>
{
WebAVMediaSelectionOption *_currentAudioMediaSelectionOption;
WebAVMediaSelectionOption *_currentLegibleMediaSelectionOption;
}
@property(retain) AVPlayerController* playerControllerProxy;
@property(assign) WebVideoFullscreenModel* delegate;
@property (readonly) BOOL canScanForward;
@property BOOL canScanBackward;
@property (readonly) BOOL canSeekToBeginning;
@property (readonly) BOOL canSeekToEnd;
@property BOOL canPlay;
@property(getter=isPlaying) BOOL playing;
@property BOOL canPause;
@property BOOL canTogglePlayback;
@property double rate;
@property BOOL canSeek;
@property NSTimeInterval contentDuration;
@property NSSize contentDimensions;
@property BOOL hasEnabledAudio;
@property BOOL hasEnabledVideo;
@property NSTimeInterval minTime;
@property NSTimeInterval maxTime;
@property NSTimeInterval contentDurationWithinEndTimes;
@property(retain) NSArray *loadedTimeRanges;
@property AVPlayerControllerStatus status;
@property(retain) AVValueTiming *timing;
@property(retain) NSArray *seekableTimeRanges;
@property (readonly) BOOL hasMediaSelectionOptions;
@property (readonly) BOOL hasAudioMediaSelectionOptions;
@property (retain) NSArray *audioMediaSelectionOptions;
@property (retain) WebAVMediaSelectionOption *currentAudioMediaSelectionOption;
@property (readonly) BOOL hasLegibleMediaSelectionOptions;
@property (retain) NSArray *legibleMediaSelectionOptions;
@property (retain) WebAVMediaSelectionOption *currentLegibleMediaSelectionOption;
@property (readonly, getter=isPlayingOnExternalScreen) BOOL playingOnExternalScreen;
@property (getter=isExternalPlaybackActive) BOOL externalPlaybackActive;
@property AVPlayerControllerExternalPlaybackType externalPlaybackType;
@property (retain) NSString *externalPlaybackAirPlayDeviceLocalizedName;
- (BOOL)playerViewController:(AVPlayerViewController *)playerViewController shouldExitFullScreenWithReason:(AVPlayerViewControllerExitFullScreenReason)reason;
@end
@implementation WebAVPlayerController
- (instancetype)init
{
if (!(self = [super init]))
return self;
initAVPlayerController();
self.playerControllerProxy = [[[getAVPlayerControllerClass() alloc] init] autorelease];
return self;
}
- (void)dealloc
{
[_playerControllerProxy release];
[_loadedTimeRanges release];
[_seekableTimeRanges release];
[_timing release];
[_audioMediaSelectionOptions release];
[_legibleMediaSelectionOptions release];
[_currentAudioMediaSelectionOption release];
[_currentLegibleMediaSelectionOption release];
[super dealloc];
}
- (id)forwardingTargetForSelector:(SEL)selector
{
UNUSED_PARAM(selector);
return self.playerControllerProxy;
}
- (BOOL)playerViewController:(AVPlayerViewController *)playerViewController shouldExitFullScreenWithReason:(AVPlayerViewControllerExitFullScreenReason)reason
{
UNUSED_PARAM(playerViewController);
UNUSED_PARAM(reason);
ASSERT(self.delegate);
if (reason == AVPlayerViewControllerExitFullScreenReasonDoneButtonTapped || reason == AVPlayerViewControllerExitFullScreenReasonRemoteControlStopEventReceived)
self.delegate->pause();
self.delegate->requestExitFullscreen();
return NO;
}
- (void)play:(id)sender
{
UNUSED_PARAM(sender);
ASSERT(self.delegate);
self.delegate->play();
}
- (void)pause:(id)sender
{
UNUSED_PARAM(sender);
ASSERT(self.delegate);
self.delegate->pause();
}
- (void)togglePlayback:(id)sender
{
UNUSED_PARAM(sender);
ASSERT(self.delegate);
self.delegate->togglePlayState();
}
- (BOOL)isPlaying
{
return [self rate] != 0;
}
- (void)setPlaying:(BOOL)playing
{
ASSERT(self.delegate);
if (playing)
self.delegate->play();
else
self.delegate->pause();
}
+ (NSSet *)keyPathsForValuesAffectingPlaying
{
return [NSSet setWithObject:@"rate"];
}
- (void)beginScrubbing:(id)sender
{
UNUSED_PARAM(sender);
ASSERT(self.delegate);
self.delegate->beginScrubbing();
}
- (void)endScrubbing:(id)sender
{
UNUSED_PARAM(sender);
ASSERT(self.delegate);
self.delegate->endScrubbing();
}
- (void)seekToTime:(NSTimeInterval)time
{
ASSERT(self.delegate);
self.delegate->fastSeek(time);
}
- (BOOL)hasLiveStreamingContent
{
if ([self status] == AVPlayerControllerStatusReadyToPlay)
return [self contentDuration] == std::numeric_limits<float>::infinity();
return NO;
}
+ (NSSet *)keyPathsForValuesAffectingHasLiveStreamingContent
{
return [NSSet setWithObjects:@"contentDuration", @"status", nil];
}
- (void)skipBackwardThirtySeconds:(id)sender
{
UNUSED_PARAM(sender);
BOOL isTimeWithinSeekableTimeRanges = NO;
CMTime currentTime = CMTimeMakeWithSeconds([[self timing] currentValue], 1000);
CMTime thirtySecondsBeforeCurrentTime = CMTimeSubtract(currentTime, CMTimeMake(30, 1));
for (NSValue *seekableTimeRangeValue in [self seekableTimeRanges]) {
if (CMTimeRangeContainsTime([seekableTimeRangeValue CMTimeRangeValue], thirtySecondsBeforeCurrentTime)) {
isTimeWithinSeekableTimeRanges = YES;
break;
}
}
if (isTimeWithinSeekableTimeRanges)
[self seekToTime:CMTimeGetSeconds(thirtySecondsBeforeCurrentTime)];
}
- (void)gotoEndOfSeekableRanges:(id)sender
{
UNUSED_PARAM(sender);
NSTimeInterval timeAtEndOfSeekableTimeRanges = NAN;
for (NSValue *seekableTimeRangeValue in [self seekableTimeRanges]) {
CMTimeRange seekableTimeRange = [seekableTimeRangeValue CMTimeRangeValue];
NSTimeInterval endOfSeekableTimeRange = CMTimeGetSeconds(CMTimeRangeGetEnd(seekableTimeRange));
if (isnan(timeAtEndOfSeekableTimeRanges) || endOfSeekableTimeRange > timeAtEndOfSeekableTimeRanges)
timeAtEndOfSeekableTimeRanges = endOfSeekableTimeRange;
}
if (!isnan(timeAtEndOfSeekableTimeRanges))
[self seekToTime:timeAtEndOfSeekableTimeRanges];
}
- (BOOL)canScanForward
{
return [self canPlay];
}
+ (NSSet *)keyPathsForValuesAffectingCanScanForward
{
return [NSSet setWithObject:@"canPlay"];
}
- (void)beginScanningForward:(id)sender
{
UNUSED_PARAM(sender);
ASSERT(self.delegate);
self.delegate->beginScanningForward();
}
- (void)endScanningForward:(id)sender
{
UNUSED_PARAM(sender);
ASSERT(self.delegate);
self.delegate->endScanning();
}
- (void)beginScanningBackward:(id)sender
{
UNUSED_PARAM(sender);
ASSERT(self.delegate);
self.delegate->beginScanningBackward();
}
- (void)endScanningBackward:(id)sender
{
UNUSED_PARAM(sender);
ASSERT(self.delegate);
self.delegate->endScanning();
}
- (BOOL)canSeekToBeginning
{
CMTime minimumTime = kCMTimeIndefinite;
for (NSValue *value in [self seekableTimeRanges])
minimumTime = CMTimeMinimum([value CMTimeRangeValue].start, minimumTime);
return CMTIME_IS_NUMERIC(minimumTime);
}
+ (NSSet *)keyPathsForValuesAffectingCanSeekToBeginning
{
return [NSSet setWithObject:@"seekableTimeRanges"];
}
- (void)seekToBeginning:(id)sender
{
UNUSED_PARAM(sender);
ASSERT(self.delegate);
self.delegate->seekToTime(-INFINITY);
}
- (void)seekChapterBackward:(id)sender
{
[self seekToBeginning:sender];
}
- (BOOL)canSeekToEnd
{
CMTime maximumTime = kCMTimeIndefinite;
for (NSValue *value in [self seekableTimeRanges])
maximumTime = CMTimeMaximum(CMTimeRangeGetEnd([value CMTimeRangeValue]), maximumTime);
return CMTIME_IS_NUMERIC(maximumTime);
}
+ (NSSet *)keyPathsForValuesAffectingCanSeekToEnd
{
return [NSSet setWithObject:@"seekableTimeRanges"];
}
- (void)seekToEnd:(id)sender
{
UNUSED_PARAM(sender);
ASSERT(self.delegate);
self.delegate->seekToTime(INFINITY);
}
- (void)seekChapterForward:(id)sender
{
[self seekToEnd:sender];
}
- (BOOL)hasMediaSelectionOptions
{
return [self hasAudioMediaSelectionOptions] || [self hasLegibleMediaSelectionOptions];
}
+ (NSSet *)keyPathsForValuesAffectingHasMediaSelectionOptions
{
return [NSSet setWithObjects:@"hasAudioMediaSelectionOptions", @"hasLegibleMediaSelectionOptions", nil];
}
- (BOOL)hasAudioMediaSelectionOptions
{
return [[self audioMediaSelectionOptions] count] > 0;
}
+ (NSSet *)keyPathsForValuesAffectingHasAudioMediaSelectionOptions
{
return [NSSet setWithObject:@"audioMediaSelectionOptions"];
}
- (BOOL)hasLegibleMediaSelectionOptions
{
return [[self legibleMediaSelectionOptions] count] > 0;
}
+ (NSSet *)keyPathsForValuesAffectingHasLegibleMediaSelectionOptions
{
return [NSSet setWithObject:@"legibleMediaSelectionOptions"];
}
- (WebAVMediaSelectionOption *)currentAudioMediaSelectionOption
{
return _currentAudioMediaSelectionOption;
}
- (void)setCurrentAudioMediaSelectionOption:(WebAVMediaSelectionOption *)option
{
if (option == _currentAudioMediaSelectionOption)
return;
[_currentAudioMediaSelectionOption release];
_currentAudioMediaSelectionOption = [option retain];
ASSERT(self.delegate);
NSInteger index = NSNotFound;
if (option && self.audioMediaSelectionOptions)
index = [self.audioMediaSelectionOptions indexOfObject:option];
self.delegate->selectAudioMediaOption(index != NSNotFound ? index : UINT64_MAX);
}
- (WebAVMediaSelectionOption *)currentLegibleMediaSelectionOption
{
return _currentLegibleMediaSelectionOption;
}
- (void)setCurrentLegibleMediaSelectionOption:(WebAVMediaSelectionOption *)option
{
if (option == _currentLegibleMediaSelectionOption)
return;
[_currentLegibleMediaSelectionOption release];
_currentLegibleMediaSelectionOption = [option retain];
ASSERT(self.delegate);
NSInteger index = NSNotFound;
if (option && self.legibleMediaSelectionOptions)
index = [self.legibleMediaSelectionOptions indexOfObject:option];
self.delegate->selectLegibleMediaOption(index != NSNotFound ? index : UINT64_MAX);
}
- (BOOL)isPlayingOnExternalScreen
{
return [self isExternalPlaybackActive];
}
+ (NSSet *)keyPathsForValuesAffectingPlayingOnExternalScreen
{
return [NSSet setWithObjects:@"externalPlaybackActive", nil];
}
@end
@interface WebAVMediaSelectionOption : NSObject
@property (retain) NSString *localizedDisplayName;
@end
@implementation WebAVMediaSelectionOption
@end
@interface WebAVVideoLayer : CALayer <AVVideoLayer>
+(WebAVVideoLayer *)videoLayer;
@property (nonatomic) AVVideoLayerGravity videoLayerGravity;
@property (nonatomic, getter = isReadyForDisplay) BOOL readyForDisplay;
@property (nonatomic) CGRect videoRect;
- (void)setPlayerViewController:(AVPlayerViewController *)playerViewController;
- (void)setPlayerController:(AVPlayerController *)playerController;
@end
@implementation WebAVVideoLayer
{
RetainPtr<WebAVPlayerController> _avPlayerController;
RetainPtr<AVPlayerViewController> _avPlayerViewController;
AVVideoLayerGravity _videoLayerGravity;
}
+(WebAVVideoLayer *)videoLayer
{
return [[[WebAVVideoLayer alloc] init] autorelease];
}
- (instancetype)init
{
self = [super init];
if (self) {
[self setMasksToBounds:YES];
[self setVideoLayerGravity:AVVideoLayerGravityResizeAspect];
}
return self;
}
- (void)setPlayerController:(AVPlayerController *)playerController
{
ASSERT(!playerController || [playerController isKindOfClass:[WebAVPlayerController class]]);
_avPlayerController = (WebAVPlayerController *)playerController;
}
- (void)setPlayerViewController:(AVPlayerViewController *)playerViewController
{
_avPlayerViewController = playerViewController;
}
- (void)setBounds:(CGRect)bounds
{
[super setBounds:bounds];
if (![_avPlayerController delegate] || !_avPlayerViewController)
return;
UIView* rootView = [[_avPlayerViewController view] window];
if (!rootView)
return;
FloatRect rootBounds = [rootView bounds];
[_avPlayerController delegate]->setVideoLayerFrame(rootBounds);
FloatRect sourceBounds = largestRectWithAspectRatioInsideRect(CGRectGetWidth(bounds) / CGRectGetHeight(bounds), rootBounds);
CATransform3D transform = CATransform3DMakeScale(bounds.size.width / sourceBounds.width(), bounds.size.height / sourceBounds.height(), 1);
transform = CATransform3DTranslate(transform, bounds.origin.x - sourceBounds.x(), bounds.origin.y - sourceBounds.y(), 0);
[self setSublayerTransform:transform];
}
- (void)setVideoLayerGravity:(AVVideoLayerGravity)videoLayerGravity
{
_videoLayerGravity = videoLayerGravity;
if (![_avPlayerController delegate])
return;
WebCore::WebVideoFullscreenModel::VideoGravity gravity = WebCore::WebVideoFullscreenModel::VideoGravityResizeAspect;
if (videoLayerGravity == AVVideoLayerGravityResize)
gravity = WebCore::WebVideoFullscreenModel::VideoGravityResize;
if (videoLayerGravity == AVVideoLayerGravityResizeAspect)
gravity = WebCore::WebVideoFullscreenModel::VideoGravityResizeAspect;
else if (videoLayerGravity == AVVideoLayerGravityResizeAspectFill)
gravity = WebCore::WebVideoFullscreenModel::VideoGravityResizeAspectFill;
else
ASSERT_NOT_REACHED();
[_avPlayerController delegate]->setVideoLayerGravity(gravity);
}
- (AVVideoLayerGravity)videoLayerGravity
{
return _videoLayerGravity;
}
@end
WebVideoFullscreenInterfaceAVKit::WebVideoFullscreenInterfaceAVKit()
: m_videoFullscreenModel(nullptr)
{
}
WebAVPlayerController *WebVideoFullscreenInterfaceAVKit::playerController()
{
if (!m_playerController)
{
m_playerController = adoptNS([[WebAVPlayerController alloc] init]);
if (m_videoFullscreenModel)
[m_playerController setDelegate:m_videoFullscreenModel];
}
return m_playerController.get();
}
void WebVideoFullscreenInterfaceAVKit::setWebVideoFullscreenModel(WebVideoFullscreenModel* model)
{
m_videoFullscreenModel = model;
[m_playerController setDelegate:m_videoFullscreenModel];
}
void WebVideoFullscreenInterfaceAVKit::setWebVideoFullscreenChangeObserver(WebVideoFullscreenChangeObserver* observer)
{
m_fullscreenChangeObserver = observer;
}
void WebVideoFullscreenInterfaceAVKit::setDuration(double duration)
{
WebAVPlayerController* playerController = this->playerController();
__block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
dispatch_async(dispatch_get_main_queue(), ^{
// FIXME: https://bugs.webkit.org/show_bug.cgi?id=127017 use correct values instead of duration for all these
playerController.contentDuration = duration;
playerController.maxTime = duration;
playerController.contentDurationWithinEndTimes = duration;
playerController.loadedTimeRanges = @[@0, @(duration)];
// FIXME: we take this as an indication that playback is ready.
playerController.canPlay = YES;
playerController.canPause = YES;
playerController.canTogglePlayback = YES;
playerController.hasEnabledAudio = YES;
playerController.canSeek = YES;
playerController.minTime = 0;
playerController.status = AVPlayerControllerStatusReadyToPlay;
protect = nullptr;
});
}
void WebVideoFullscreenInterfaceAVKit::setCurrentTime(double currentTime, double anchorTime)
{
__block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
dispatch_async(dispatch_get_main_queue(), ^{
NSTimeInterval anchorTimeStamp = ![playerController() rate] ? NAN : anchorTime;
AVValueTiming *timing = [getAVValueTimingClass() valueTimingWithAnchorValue:currentTime
anchorTimeStamp:anchorTimeStamp rate:0];
playerController().timing = timing;
protect = nullptr;
});
}
void WebVideoFullscreenInterfaceAVKit::setRate(bool isPlaying, float playbackRate)
{
__block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
dispatch_async(dispatch_get_main_queue(), ^{
playerController().rate = isPlaying ? playbackRate : 0.;
protect = nullptr;
});
}
void WebVideoFullscreenInterfaceAVKit::setVideoDimensions(bool hasVideo, float width, float height)
{
__block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
dispatch_async(dispatch_get_main_queue(), ^{
playerController().hasEnabledVideo = hasVideo;
playerController().contentDimensions = CGSizeMake(width, height);
protect = nullptr;
});
}
void WebVideoFullscreenInterfaceAVKit::setSeekableRanges(const TimeRanges& timeRanges)
{
NSMutableArray* seekableRanges = [NSMutableArray array];
ExceptionCode exceptionCode;
for (unsigned i = 0; i < timeRanges.length(); i++) {
double start = timeRanges.start(i, exceptionCode);
double end = timeRanges.end(i, exceptionCode);
CMTimeRange range = CMTimeRangeMake(CMTimeMakeWithSeconds(start, 1000), CMTimeMakeWithSeconds(end-start, 1000));
[seekableRanges addObject:[NSValue valueWithCMTimeRange:range]];
}
__block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
dispatch_async(dispatch_get_main_queue(), ^{
playerController().seekableTimeRanges = seekableRanges;
protect = nullptr;
});
}
void WebVideoFullscreenInterfaceAVKit::setCanPlayFastReverse(bool canPlayFastReverse)
{
playerController().canScanBackward = canPlayFastReverse;
}
static NSMutableArray *mediaSelectionOptions(const Vector<String>& options)
{
NSMutableArray *webOptions = [NSMutableArray arrayWithCapacity:options.size()];
for (auto& name : options) {
RetainPtr<WebAVMediaSelectionOption> webOption = adoptNS([[WebAVMediaSelectionOption alloc] init]);
[webOption setLocalizedDisplayName:name];
[webOptions addObject:webOption.get()];
}
return webOptions;
}
void WebVideoFullscreenInterfaceAVKit::setAudioMediaSelectionOptions(const Vector<String>& options, uint64_t selectedIndex)
{
NSMutableArray *webOptions = mediaSelectionOptions(options);
__block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
dispatch_async(dispatch_get_main_queue(), ^{
playerController().audioMediaSelectionOptions = webOptions;
if (selectedIndex < webOptions.count)
playerController().currentAudioMediaSelectionOption = webOptions[(size_t)selectedIndex];
protect = nullptr;
});
}
void WebVideoFullscreenInterfaceAVKit::setLegibleMediaSelectionOptions(const Vector<String>& options, uint64_t selectedIndex)
{
NSMutableArray *webOptions = mediaSelectionOptions(options);
__block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
dispatch_async(dispatch_get_main_queue(), ^{
playerController().legibleMediaSelectionOptions = webOptions;
if (selectedIndex < webOptions.count)
playerController().currentLegibleMediaSelectionOption = webOptions[(size_t)selectedIndex];
protect = nullptr;
});
}
void WebVideoFullscreenInterfaceAVKit::setExternalPlayback(bool enabled, ExternalPlaybackTargetType targetType, String localizedDeviceName)
{
AVPlayerControllerExternalPlaybackType externalPlaybackType = AVPlayerControllerExternalPlaybackTypeNone;
if (targetType == TargetTypeAirPlay)
externalPlaybackType = AVPlayerControllerExternalPlaybackTypeAirPlay;
else if (targetType == TargetTypeTVOut)
externalPlaybackType = AVPlayerControllerExternalPlaybackTypeTVOut;
__block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
dispatch_async(dispatch_get_main_queue(), ^{
playerController().externalPlaybackAirPlayDeviceLocalizedName = localizedDeviceName;
playerController().externalPlaybackType = externalPlaybackType;
playerController().externalPlaybackActive = enabled;
[m_videoLayerContainer.get() setHidden:enabled];
protect = nullptr;
});
}
void WebVideoFullscreenInterfaceAVKit::setupFullscreen(PlatformLayer& videoLayer, WebCore::IntRect initialRect, UIView* parentView)
{
__block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
m_videoLayer = &videoLayer;
dispatch_async(dispatch_get_main_queue(), ^{
[CATransaction begin];
[CATransaction setDisableActions:YES];
m_parentView = parentView;
if (!applicationIsAdSheet()) {
m_window = adoptNS([[getUIWindowClass() alloc] initWithFrame:[[getUIScreenClass() mainScreen] bounds]]);
[m_window setBackgroundColor:[getUIColorClass() clearColor]];
m_viewController = adoptNS([[getUIViewControllerClass() alloc] init]);
[[m_viewController view] setFrame:[m_window bounds]];
[m_window setRootViewController:m_viewController.get()];
[m_window makeKeyAndVisible];
}
[m_videoLayer removeFromSuperlayer];
m_videoLayerContainer = [WebAVVideoLayer videoLayer];
[m_videoLayerContainer setHidden:playerController().externalPlaybackActive];
[m_videoLayerContainer addSublayer:m_videoLayer.get()];
CGSize videoSize = playerController().contentDimensions;
CGRect videoRect = CGRectMake(0, 0, videoSize.width, videoSize.height);
[m_videoLayerContainer setVideoRect:videoRect];
m_playerViewController = adoptNS([[getAVPlayerViewControllerClass() alloc] initWithVideoLayer:m_videoLayerContainer.get()]);
[m_playerViewController setShowsPlaybackControls:NO];
[m_playerViewController setPlayerController:(AVPlayerController *)playerController()];
[m_playerViewController setDelegate:playerController()];
[m_videoLayerContainer setPlayerViewController:m_playerViewController.get()];
if (m_viewController) {
[m_viewController addChildViewController:m_playerViewController.get()];
[[m_viewController view] addSubview:[m_playerViewController view]];
[m_playerViewController view].frame = [parentView convertRect:initialRect toView:nil];
} else {
[parentView addSubview:[m_playerViewController view]];
[m_playerViewController view].frame = initialRect;
}
[[m_playerViewController view] setBackgroundColor:[getUIColorClass() clearColor]];
[[m_playerViewController view] setNeedsLayout];
[[m_playerViewController view] layoutIfNeeded];
[CATransaction commit];
dispatch_async(dispatch_get_main_queue(), ^{
if (m_fullscreenChangeObserver)
m_fullscreenChangeObserver->didSetupFullscreen();
protect = nullptr;
});
});
}
void WebVideoFullscreenInterfaceAVKit::enterFullscreen()
{
__block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
dispatch_async(dispatch_get_main_queue(), ^{
[m_videoLayerContainer setBackgroundColor:[[getUIColorClass() blackColor] CGColor]];
[m_playerViewController enterFullScreenWithCompletionHandler:^(BOOL, NSError*)
{
[m_playerViewController setShowsPlaybackControls:YES];
if (m_fullscreenChangeObserver)
m_fullscreenChangeObserver->didEnterFullscreen();
protect = nullptr;
}];
});
}
void WebVideoFullscreenInterfaceAVKit::exitFullscreen(WebCore::IntRect finalRect)
{
__block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
m_playerController = nil;
dispatch_async(dispatch_get_main_queue(), ^{
[m_playerViewController setShowsPlaybackControls:NO];
if (m_viewController)
[m_playerViewController view].frame = [m_parentView convertRect:finalRect toView:nil];
else
[m_playerViewController view].frame = finalRect;
if ([m_videoLayerContainer videoLayerGravity] != AVVideoLayerGravityResizeAspect)
[m_videoLayerContainer setVideoLayerGravity:AVVideoLayerGravityResizeAspect];
[[m_playerViewController view] layoutIfNeeded];
[m_playerViewController exitFullScreenWithCompletionHandler:^(BOOL, NSError*) {
[m_videoLayerContainer setBackgroundColor:[[getUIColorClass() clearColor] CGColor]];
[[m_playerViewController view] setBackgroundColor:[getUIColorClass() clearColor]];
if (m_fullscreenChangeObserver)
m_fullscreenChangeObserver->didExitFullscreen();
protect = nullptr;
}];
});
}
@interface UIApplication ()
-(void)_setStatusBarOrientation:(UIInterfaceOrientation)o;
@end
@interface UIWindow ()
-(UIInterfaceOrientation)interfaceOrientation;
@end
void WebVideoFullscreenInterfaceAVKit::cleanupFullscreen()
{
// Retain this to extend object life until async block completes.
__block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
dispatch_async(dispatch_get_main_queue(), ^{
if (m_window) {
[m_window setHidden:YES];
[m_window setRootViewController:nil];
[[getUIApplicationClass() sharedApplication] _setStatusBarOrientation:[[m_parentView window] interfaceOrientation]];
}
[m_playerViewController setDelegate:nil];
[[m_playerViewController view] removeFromSuperview];
if (m_viewController)
[m_playerViewController removeFromParentViewController];
[m_playerViewController setPlayerController:nil];
m_playerViewController = nil;
[m_videoLayer removeFromSuperlayer];
m_videoLayer = nil;
[m_videoLayerContainer removeFromSuperlayer];
[m_videoLayerContainer setPlayerViewController:nil];
m_videoLayerContainer = nil;
[[m_viewController view] removeFromSuperview];
m_viewController = nil;
m_window = nil;
m_parentView = nil;
if (m_fullscreenChangeObserver)
m_fullscreenChangeObserver->didCleanupFullscreen();
protect = nullptr;
});
}
void WebVideoFullscreenInterfaceAVKit::invalidate()
{
[m_window setHidden:YES];
[m_window setRootViewController:nil];
[m_playerViewController exitFullScreenAnimated:NO completionHandler:nil];
m_playerController = nil;
[m_playerViewController setDelegate:nil];
[[m_playerViewController view] removeFromSuperview];
if (m_viewController)
[m_playerViewController removeFromParentViewController];
[m_playerViewController setPlayerController:nil];
m_playerViewController = nil;
[m_videoLayer removeFromSuperlayer];
m_videoLayer = nil;
[m_videoLayerContainer removeFromSuperlayer];
[m_videoLayerContainer setPlayerViewController:nil];
m_videoLayerContainer = nil;
[[m_viewController view] removeFromSuperview];
m_viewController = nil;
m_window = nil;
m_parentView = nil;
}
void WebVideoFullscreenInterfaceAVKit::requestHideAndExitFullscreen()
{
__block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
dispatch_async(dispatch_get_main_queue(), ^{
[m_window setHidden:YES];
[m_playerViewController exitFullScreenAnimated:NO completionHandler:^(BOOL, NSError*) {
protect = nullptr;
}];
});
if (m_videoFullscreenModel)
m_videoFullscreenModel->requestExitFullscreen();
}
#endif