blob: f6ec909e8b22cdea70eae481e7c684d22d26bd01 [file] [log] [blame]
/*
* Copyright (C) 2013 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)
#import "WebVideoFullscreenControllerAVKit.h"
#import "Logging.h"
#import "QuartzCoreSPI.h"
#import "SoftLinking.h"
#import "TimeRanges.h"
#import "WebVideoFullscreenInterfaceAVKit.h"
#import "WebVideoFullscreenModelVideoElement.h"
#import <QuartzCore/CoreAnimation.h>
#import <WebCore/FrameView.h>
#import <WebCore/HTMLVideoElement.h>
#import <WebCore/RenderElement.h>
#import <WebCore/WebCoreThreadRun.h>
SOFT_LINK_FRAMEWORK(UIKit)
SOFT_LINK_CLASS(UIKit, UIView)
using namespace WebCore;
#if !HAVE(AVKIT)
@implementation WebVideoFullscreenController
- (void)setVideoElement:(WebCore::HTMLVideoElement*)videoElement
{
UNUSED_PARAM(videoElement);
}
- (WebCore::HTMLVideoElement*)videoElement
{
return nullptr;
}
- (void)enterFullscreen:(UIView *)view mode:(WebCore::HTMLMediaElementEnums::VideoFullscreenMode)mode
{
UNUSED_PARAM(view);
UNUSED_PARAM(mode);
}
- (void)requestHideAndExitFullscreen
{
}
- (void)exitFullscreen
{
}
@end
#else
static IntRect elementRectInWindow(HTMLVideoElement* videoElement)
{
if (!videoElement || !videoElement->renderer() || !videoElement->document().view())
return IntRect();
return videoElement->document().view()->convertToContainingWindow(videoElement->renderer()->absoluteBoundingBoxRect());
}
class WebVideoFullscreenControllerContext;
@interface WebVideoFullscreenController (delegate)
-(void)didFinishFullscreen:(WebVideoFullscreenControllerContext*)context;
@end
class WebVideoFullscreenControllerContext final
: private WebVideoFullscreenInterface
, private WebVideoFullscreenModel
, private WebVideoFullscreenChangeObserver
, public ThreadSafeRefCounted<WebVideoFullscreenControllerContext> {
public:
static Ref<WebVideoFullscreenControllerContext> create()
{
return adoptRef(*new WebVideoFullscreenControllerContext);
}
void setController(WebVideoFullscreenController* controller) { m_controller = controller; }
void setUpFullscreen(HTMLVideoElement&, UIView *, HTMLMediaElementEnums::VideoFullscreenMode);
void exitFullscreen();
void requestHideAndExitFullscreen();
void invalidate();
private:
WebVideoFullscreenControllerContext() { }
// WebVideoFullscreenChangeObserver
virtual void didSetupFullscreen() override;
virtual void didEnterFullscreen() override { }
virtual void didExitFullscreen() override;
virtual void didCleanupFullscreen() override;
virtual void fullscreenMayReturnToInline() override;
// WebVideoFullscreenInterface
virtual void resetMediaState() override;
virtual void setDuration(double) override;
virtual void setCurrentTime(double currentTime, double anchorTime) override;
virtual void setBufferedTime(double) override;
virtual void setRate(bool isPlaying, float playbackRate) override;
virtual void setVideoDimensions(bool hasVideo, float width, float height) override;
virtual void setSeekableRanges(const TimeRanges&) override;
virtual void setCanPlayFastReverse(bool) override;
virtual void setAudioMediaSelectionOptions(const Vector<String>& options, uint64_t selectedIndex) override;
virtual void setLegibleMediaSelectionOptions(const Vector<String>& options, uint64_t selectedIndex) override;
virtual void setExternalPlayback(bool enabled, ExternalPlaybackTargetType, String localizedDeviceName) override;
virtual void setWirelessVideoPlaybackDisabled(bool) override;
// WebVideoFullscreenModel
virtual void play() override;
virtual void pause() override;
virtual void togglePlayState() override;
virtual void beginScrubbing() override;
virtual void endScrubbing() override;
virtual void seekToTime(double time) override;
virtual void fastSeek(double time) override;
virtual void beginScanningForward() override;
virtual void beginScanningBackward() override;
virtual void endScanning() override;
virtual void requestExitFullscreen() override;
virtual void setVideoLayerFrame(FloatRect) override;
virtual void setVideoLayerGravity(WebVideoFullscreenModel::VideoGravity) override;
virtual void selectAudioMediaOption(uint64_t index) override;
virtual void selectLegibleMediaOption(uint64_t index) override;
virtual void fullscreenModeChanged(HTMLMediaElementEnums::VideoFullscreenMode) override;
RefPtr<WebVideoFullscreenInterfaceAVKit> m_interface;
RefPtr<WebVideoFullscreenModelVideoElement> m_model;
RefPtr<HTMLVideoElement> m_videoElement;
RetainPtr<UIView> m_videoFullscreenView;
RetainPtr<WebVideoFullscreenController> m_controller;
};
#pragma mark WebVideoFullscreenChangeObserver
void WebVideoFullscreenControllerContext::didSetupFullscreen()
{
ASSERT(isUIThread());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
RetainPtr<CALayer> videoFullscreenLayer = [m_videoFullscreenView layer];
WebThreadRun([strongThis, this, videoFullscreenLayer] {
[videoFullscreenLayer setBackgroundColor:cachedCGColor(WebCore::Color::transparent, WebCore::ColorSpaceDeviceRGB)];
m_model->setVideoFullscreenLayer(videoFullscreenLayer.get());
dispatch_async(dispatch_get_main_queue(), [strongThis, this] {
m_interface->enterFullscreen();
});
});
}
void WebVideoFullscreenControllerContext::didExitFullscreen()
{
ASSERT(isUIThread());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
WebThreadRun([strongThis, this] {
m_model->setVideoFullscreenLayer(nil);
dispatch_async(dispatch_get_main_queue(), [strongThis, this] {
m_interface->cleanupFullscreen();
});
});
}
void WebVideoFullscreenControllerContext::didCleanupFullscreen()
{
ASSERT(isUIThread());
m_interface->setWebVideoFullscreenModel(nullptr);
m_interface->setWebVideoFullscreenChangeObserver(nullptr);
m_interface = nullptr;
m_videoFullscreenView = nil;
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
WebThreadRun([strongThis, this] {
m_model->setVideoFullscreenLayer(nil);
m_model->setWebVideoFullscreenInterface(nullptr);
m_model->setVideoElement(nullptr);
m_model = nullptr;
m_videoElement = nullptr;
[m_controller didFinishFullscreen:this];
});
}
void WebVideoFullscreenControllerContext::fullscreenMayReturnToInline()
{
ASSERT(isUIThread());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
WebThreadRun([strongThis, this] {
IntRect clientRect = elementRectInWindow(m_videoElement.get());
dispatch_async(dispatch_get_main_queue(), [strongThis, this, clientRect] {
m_interface->preparedToReturnToInline(true, clientRect);
});
});
}
#pragma mark WebVideoFullscreenInterface
void WebVideoFullscreenControllerContext::resetMediaState()
{
ASSERT(WebThreadIsCurrent() || isMainThread());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
dispatch_async(dispatch_get_main_queue(), [strongThis, this] {
if (m_interface)
m_interface->resetMediaState();
});
}
void WebVideoFullscreenControllerContext::setDuration(double duration)
{
ASSERT(WebThreadIsCurrent());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
dispatch_async(dispatch_get_main_queue(), [strongThis, this, duration] {
if (m_interface)
m_interface->setDuration(duration);
});
}
void WebVideoFullscreenControllerContext::setCurrentTime(double currentTime, double anchorTime)
{
ASSERT(WebThreadIsCurrent());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
dispatch_async(dispatch_get_main_queue(), [strongThis, this, currentTime, anchorTime] {
if (m_interface)
m_interface->setCurrentTime(currentTime, anchorTime);
});
}
void WebVideoFullscreenControllerContext::setBufferedTime(double bufferedTime)
{
ASSERT(WebThreadIsCurrent());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
dispatch_async(dispatch_get_main_queue(), [strongThis, this, bufferedTime] {
if (m_interface)
m_interface->setBufferedTime(bufferedTime);
});
}
void WebVideoFullscreenControllerContext::setRate(bool isPlaying, float playbackRate)
{
ASSERT(WebThreadIsCurrent());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
dispatch_async(dispatch_get_main_queue(), [strongThis, this, isPlaying, playbackRate] {
if (m_interface)
m_interface->setRate(isPlaying, playbackRate);
});
}
void WebVideoFullscreenControllerContext::setVideoDimensions(bool hasVideo, float width, float height)
{
ASSERT(WebThreadIsCurrent());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
dispatch_async(dispatch_get_main_queue(), [strongThis, this, hasVideo, width, height] {
if (m_interface)
m_interface->setVideoDimensions(hasVideo, width, height);
});
}
void WebVideoFullscreenControllerContext::setSeekableRanges(const TimeRanges& timeRanges)
{
ASSERT(WebThreadIsCurrent());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
const PlatformTimeRanges& platformTimeRanges = timeRanges.ranges();
dispatch_async(dispatch_get_main_queue(), [strongThis, this, platformTimeRanges] {
if (m_interface)
m_interface->setSeekableRanges(TimeRanges::create(platformTimeRanges));
});
}
void WebVideoFullscreenControllerContext::setCanPlayFastReverse(bool canPlayFastReverse)
{
ASSERT(WebThreadIsCurrent());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
dispatch_async(dispatch_get_main_queue(), [strongThis, this, canPlayFastReverse] {
if (m_interface)
m_interface->setCanPlayFastReverse(canPlayFastReverse);
});
}
void WebVideoFullscreenControllerContext::setAudioMediaSelectionOptions(const Vector<String>& options, uint64_t selectedIndex)
{
ASSERT(WebThreadIsCurrent());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
RetainPtr<NSMutableArray> optionsArray = adoptNS([[NSMutableArray alloc] initWithCapacity:options.size()]);
for (auto& name : options)
[optionsArray addObject:name];
dispatch_async(dispatch_get_main_queue(), [strongThis, this, optionsArray, selectedIndex] {
Vector<String> options;
for (NSString *name : optionsArray.get())
options.append(name);
if (m_interface)
m_interface->setAudioMediaSelectionOptions(options, selectedIndex);
});
}
void WebVideoFullscreenControllerContext::setLegibleMediaSelectionOptions(const Vector<String>& options, uint64_t selectedIndex)
{
ASSERT(WebThreadIsCurrent());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
RetainPtr<NSMutableArray> optionsArray = adoptNS([[NSMutableArray alloc] initWithCapacity:options.size()]);
for (auto& name : options)
[optionsArray addObject:name];
dispatch_async(dispatch_get_main_queue(), [strongThis, this, optionsArray, selectedIndex] {
Vector<String> options;
for (NSString *name : optionsArray.get())
options.append(name);
if (m_interface)
m_interface->setLegibleMediaSelectionOptions(options, selectedIndex);
});
}
void WebVideoFullscreenControllerContext::setExternalPlayback(bool enabled, ExternalPlaybackTargetType type, String localizedDeviceName)
{
ASSERT(WebThreadIsCurrent());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
StringCapture capturedLocalizedDeviceName(localizedDeviceName);
dispatch_async(dispatch_get_main_queue(), [strongThis, this, enabled, type, capturedLocalizedDeviceName] {
if (m_interface)
m_interface->setExternalPlayback(enabled, type, capturedLocalizedDeviceName.string());
});
}
void WebVideoFullscreenControllerContext::setWirelessVideoPlaybackDisabled(bool disabled)
{
ASSERT(WebThreadIsCurrent());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
dispatch_async(dispatch_get_main_queue(), [strongThis, this, disabled] {
if (m_interface)
m_interface->setWirelessVideoPlaybackDisabled(disabled);
});
}
#pragma mark WebVideoFullscreenModel
void WebVideoFullscreenControllerContext::play()
{
ASSERT(isUIThread());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
WebThreadRun([strongThis, this] {
if (m_model)
m_model->play();
});
}
void WebVideoFullscreenControllerContext::pause()
{
ASSERT(isUIThread());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
WebThreadRun([strongThis, this] {
if (m_model)
m_model->pause();
});
}
void WebVideoFullscreenControllerContext::togglePlayState()
{
ASSERT(isUIThread());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
WebThreadRun([strongThis, this] {
if (m_model)
m_model->togglePlayState();
});
}
void WebVideoFullscreenControllerContext::beginScrubbing()
{
ASSERT(isUIThread());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
WebThreadRun([strongThis, this] {
if (m_model)
m_model->beginScrubbing();
});
}
void WebVideoFullscreenControllerContext::endScrubbing()
{
ASSERT(isUIThread());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
WebThreadRun([strongThis, this] {
if (m_model)
m_model->endScrubbing();
});
}
void WebVideoFullscreenControllerContext::seekToTime(double time)
{
ASSERT(isUIThread());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
WebThreadRun([strongThis, this, time] {
if (m_model)
m_model->seekToTime(time);
});
}
void WebVideoFullscreenControllerContext::fastSeek(double time)
{
ASSERT(isUIThread());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
WebThreadRun([strongThis, this, time] {
if (m_model)
m_model->fastSeek(time);
});
}
void WebVideoFullscreenControllerContext::beginScanningForward()
{
ASSERT(isUIThread());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
WebThreadRun([strongThis, this] {
if (m_model)
m_model->beginScanningForward();
});
}
void WebVideoFullscreenControllerContext::beginScanningBackward()
{
ASSERT(isUIThread());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
WebThreadRun([strongThis, this] {
if (m_model)
m_model->beginScanningBackward();
});
}
void WebVideoFullscreenControllerContext::endScanning()
{
ASSERT(isUIThread());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
WebThreadRun([strongThis, this] {
if (m_model)
m_model->endScanning();
});
}
void WebVideoFullscreenControllerContext::requestExitFullscreen()
{
ASSERT(isUIThread());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
WebThreadRun([strongThis, this] {
if (m_model)
m_model->requestExitFullscreen();
});
}
void WebVideoFullscreenControllerContext::setVideoLayerFrame(FloatRect frame)
{
ASSERT(isUIThread());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
RetainPtr<CALayer> videoFullscreenLayer = [m_videoFullscreenView layer];
[videoFullscreenLayer setSublayerTransform:[videoFullscreenLayer transform]];
dispatch_async(dispatch_get_main_queue(), ^{
WebThreadRun([strongThis, this, frame, videoFullscreenLayer] {
[CATransaction begin];
[CATransaction setDisableActions:YES];
[CATransaction setAnimationDuration:0];
[videoFullscreenLayer setSublayerTransform:CATransform3DIdentity];
if (m_model)
m_model->setVideoLayerFrame(frame);
[CATransaction commit];
});
});
}
void WebVideoFullscreenControllerContext::setVideoLayerGravity(WebVideoFullscreenModel::VideoGravity videoGravity)
{
ASSERT(isUIThread());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
WebThreadRun([strongThis, this, videoGravity] {
if (m_model)
m_model->setVideoLayerGravity(videoGravity);
});
}
void WebVideoFullscreenControllerContext::selectAudioMediaOption(uint64_t index)
{
ASSERT(isUIThread());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
WebThreadRun([strongThis, this, index] {
if (m_model)
m_model->selectAudioMediaOption(index);
});
}
void WebVideoFullscreenControllerContext::selectLegibleMediaOption(uint64_t index)
{
ASSERT(isUIThread());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
WebThreadRun([strongThis, this, index] {
if (m_model)
m_model->selectLegibleMediaOption(index);
});
}
void WebVideoFullscreenControllerContext::fullscreenModeChanged(HTMLMediaElementEnums::VideoFullscreenMode mode)
{
ASSERT(isUIThread());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
WebThreadRun([strongThis, this, mode] {
if (m_model)
m_model->fullscreenModeChanged(mode);
});
}
#pragma mark Other
void WebVideoFullscreenControllerContext::setUpFullscreen(HTMLVideoElement& videoElement, UIView *view, HTMLMediaElementEnums::VideoFullscreenMode mode)
{
ASSERT(isMainThread());
RetainPtr<UIView> viewRef = view;
m_videoElement = &videoElement;
m_interface = WebVideoFullscreenInterfaceAVKit::create();
m_interface->setWebVideoFullscreenChangeObserver(this);
m_interface->setWebVideoFullscreenModel(this);
m_videoFullscreenView = adoptNS([[getUIViewClass() alloc] init]);
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
WebThreadRun([strongThis, this, viewRef, mode] {
m_model = WebVideoFullscreenModelVideoElement::create();
m_model->setWebVideoFullscreenInterface(this);
m_model->setVideoElement(m_videoElement.get());
bool allowsPictureInPicture = m_videoElement->mediaSession().allowsPictureInPicture(*m_videoElement.get());
IntRect videoElementClientRect = elementRectInWindow(m_videoElement.get());
FloatRect videoLayerFrame = FloatRect(FloatPoint(), videoElementClientRect.size());
m_model->setVideoLayerFrame(videoLayerFrame);
dispatch_async(dispatch_get_main_queue(), [strongThis, this, videoElementClientRect, viewRef, mode, allowsPictureInPicture] {
m_interface->setupFullscreen(*m_videoFullscreenView.get(), videoElementClientRect, viewRef.get(), mode, allowsPictureInPicture);
});
});
}
void WebVideoFullscreenControllerContext::exitFullscreen()
{
ASSERT(WebThreadIsCurrent() || isMainThread());
IntRect clientRect = elementRectInWindow(m_videoElement.get());
RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
dispatch_async(dispatch_get_main_queue(), [strongThis, this, clientRect] {
ASSERT(isUIThread());
m_interface->exitFullscreen(clientRect);
});
}
void WebVideoFullscreenControllerContext::requestHideAndExitFullscreen()
{
ASSERT(isUIThread());
m_interface->requestHideAndExitFullscreen();
}
@implementation WebVideoFullscreenController {
RefPtr<WebVideoFullscreenControllerContext> _context;
RefPtr<HTMLVideoElement> _videoElement;
}
- (instancetype)init
{
if (!(self = [super init]))
return nil;
return self;
}
- (void)setVideoElement:(HTMLVideoElement*)videoElement
{
_videoElement = videoElement;
}
- (HTMLVideoElement*)videoElement
{
return _videoElement.get();
}
- (void)enterFullscreen:(UIView *)view mode:(HTMLMediaElementEnums::VideoFullscreenMode)mode
{
ASSERT(isMainThread());
_context = WebVideoFullscreenControllerContext::create();
_context->setController(self);
_context->setUpFullscreen(*_videoElement.get(), view, mode);
}
- (void)exitFullscreen
{
ASSERT(WebThreadIsCurrent() || isMainThread());
_context->exitFullscreen();
}
- (void)requestHideAndExitFullscreen
{
ASSERT(isUIThread());
if (_context)
_context->requestHideAndExitFullscreen();
}
- (void)didFinishFullscreen:(WebVideoFullscreenControllerContext*)context
{
ASSERT(WebThreadIsCurrent());
ASSERT_UNUSED(context, context == _context);
[[self retain] autorelease]; // retain self before breaking a retain cycle.
_context->setController(nil);
_context = nullptr;
_videoElement = nullptr;
}
@end
#endif // !HAVE(AVKIT)
#endif // PLATFORM(IOS)