blob: a0c571075896d64547e72984df1a08318d84debe [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"
#import "WebVideoFullscreenControllerAVKit.h"
#if PLATFORM(IOS_FAMILY)
#import "FrameView.h"
#import "HTMLVideoElement.h"
#import "Logging.h"
#import "MediaSelectionOption.h"
#import "PlaybackSessionInterfaceAVKit.h"
#import "PlaybackSessionModelMediaElement.h"
#import "RenderVideo.h"
#import "TimeRanges.h"
#import "VideoFullscreenChangeObserver.h"
#import "VideoFullscreenInterfaceAVKit.h"
#import "VideoFullscreenModelVideoElement.h"
#import "WebCoreThreadRun.h"
#import <QuartzCore/CoreAnimation.h>
#import <UIKit/UIView.h>
#import <pal/spi/cocoa/QuartzCoreSPI.h>
#import <wtf/CrossThreadCopier.h>
#import <wtf/WorkQueue.h>
#import <pal/ios/UIKitSoftLink.h>
using namespace WebCore;
#if !(ENABLE(VIDEO_PRESENTATION_MODE) && HAVE(AVKIT))
@implementation WebVideoFullscreenController
- (void)setVideoElement:(NakedPtr<WebCore::HTMLVideoElement>)videoElement
{
UNUSED_PARAM(videoElement);
}
- (NakedPtr<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)
return { };
auto* renderer = videoElement->renderer();
auto* view = videoElement->document().view();
if (!renderer || !view)
return { };
return view->convertToContainingWindow(renderer->absoluteBoundingBoxRect());
}
class VideoFullscreenControllerContext;
@interface WebVideoFullscreenController (delegate)
-(void)didFinishFullscreen:(VideoFullscreenControllerContext*)context;
@end
class VideoFullscreenControllerContext final
: private VideoFullscreenModel
, private VideoFullscreenModelClient
, private VideoFullscreenChangeObserver
, private PlaybackSessionModel
, private PlaybackSessionModelClient
, public ThreadSafeRefCounted<VideoFullscreenControllerContext> {
public:
static Ref<VideoFullscreenControllerContext> create()
{
return adoptRef(*new VideoFullscreenControllerContext);
}
~VideoFullscreenControllerContext();
void setController(WebVideoFullscreenController* controller) { m_controller = controller; }
void setUpFullscreen(HTMLVideoElement&, UIView *, HTMLMediaElementEnums::VideoFullscreenMode);
void exitFullscreen();
void requestHideAndExitFullscreen();
void invalidate();
private:
VideoFullscreenControllerContext() { }
// VideoFullscreenChangeObserver
void requestUpdateInlineRect() final;
void requestVideoContentLayer() final;
void returnVideoContentLayer() final;
void didSetupFullscreen() final;
void didEnterFullscreen(const FloatSize&) final { }
void willExitFullscreen() final;
void didExitFullscreen() final;
void didCleanupFullscreen() final;
void fullscreenMayReturnToInline() final;
// VideoFullscreenModelClient
void hasVideoChanged(bool) override;
void videoDimensionsChanged(const FloatSize&) override;
// PlaybackSessionModel
void addClient(PlaybackSessionModelClient&) override;
void removeClient(PlaybackSessionModelClient&) override;
void play() override;
void pause() override;
void togglePlayState() override;
void beginScrubbing() override;
void endScrubbing() override;
void seekToTime(double, double, double) override;
void fastSeek(double time) override;
void beginScanningForward() override;
void beginScanningBackward() override;
void endScanning() override;
void setDefaultPlaybackRate(double) override;
void setPlaybackRate(double) override;
void selectAudioMediaOption(uint64_t) override;
void selectLegibleMediaOption(uint64_t) override;
double duration() const override;
double playbackStartedTime() const override { return 0; }
double currentTime() const override;
double bufferedTime() const override;
bool isPlaying() const override;
bool isStalled() const override;
bool isScrubbing() const override { return false; }
double defaultPlaybackRate() const override;
double playbackRate() const override;
Ref<TimeRanges> seekableRanges() const override;
double seekableTimeRangesLastModifiedTime() const override;
double liveUpdateInterval() const override;
bool canPlayFastReverse() const override;
Vector<MediaSelectionOption> audioMediaSelectionOptions() const override;
uint64_t audioMediaSelectedIndex() const override;
Vector<MediaSelectionOption> legibleMediaSelectionOptions() const override;
uint64_t legibleMediaSelectedIndex() const override;
bool externalPlaybackEnabled() const override;
ExternalPlaybackTargetType externalPlaybackTargetType() const override;
String externalPlaybackLocalizedDeviceName() const override;
bool wirelessVideoPlaybackDisabled() const override;
void togglePictureInPicture() override { }
void toggleMuted() override;
void setMuted(bool) final;
void setVolume(double) final;
void setPlayingOnSecondScreen(bool) final;
// PlaybackSessionModelClient
void durationChanged(double) override;
void currentTimeChanged(double currentTime, double anchorTime) override;
void bufferedTimeChanged(double) override;
void rateChanged(OptionSet<PlaybackSessionModel::PlaybackState>, double playbackRate, double defaultPlaybackRate) override;
void seekableRangesChanged(const TimeRanges&, double lastModifiedTime, double liveUpdateInterval) override;
void canPlayFastReverseChanged(bool) override;
void audioMediaSelectionOptionsChanged(const Vector<MediaSelectionOption>& options, uint64_t selectedIndex) override;
void legibleMediaSelectionOptionsChanged(const Vector<MediaSelectionOption>& options, uint64_t selectedIndex) override;
void externalPlaybackChanged(bool enabled, PlaybackSessionModel::ExternalPlaybackTargetType, const String& localizedDeviceName) override;
void wirelessVideoPlaybackDisabledChanged(bool) override;
void mutedChanged(bool) override;
void volumeChanged(double) override;
// VideoFullscreenModel
void addClient(VideoFullscreenModelClient&) override;
void removeClient(VideoFullscreenModelClient&) override;
void requestFullscreenMode(HTMLMediaElementEnums::VideoFullscreenMode, bool finishedWithMedia = false) override;
void setVideoLayerFrame(FloatRect) override;
void setVideoLayerGravity(MediaPlayerEnums::VideoGravity) override;
void fullscreenModeChanged(HTMLMediaElementEnums::VideoFullscreenMode) override;
bool hasVideo() const override;
FloatSize videoDimensions() const override;
bool isMuted() const override;
double volume() const override;
bool isPictureInPictureSupported() const override;
bool isPictureInPictureActive() const override;
void willEnterPictureInPicture() final;
void didEnterPictureInPicture() final;
void failedToEnterPictureInPicture() final;
void willExitPictureInPicture() final;
void didExitPictureInPicture() final;
HashSet<PlaybackSessionModelClient*> m_playbackClients;
HashSet<VideoFullscreenModelClient*> m_fullscreenClients;
RefPtr<VideoFullscreenInterfaceAVKit> m_interface;
RefPtr<VideoFullscreenModelVideoElement> m_fullscreenModel;
RefPtr<PlaybackSessionModelMediaElement> m_playbackModel;
RefPtr<HTMLVideoElement> m_videoElement;
RetainPtr<UIView> m_videoFullscreenView;
RetainPtr<WebVideoFullscreenController> m_controller;
};
VideoFullscreenControllerContext::~VideoFullscreenControllerContext()
{
auto notifyClientsModelWasDestroyed = [this] {
while (!m_playbackClients.isEmpty())
(*m_playbackClients.begin())->modelDestroyed();
while (!m_fullscreenClients.isEmpty())
(*m_fullscreenClients.begin())->modelDestroyed();
};
if (isUIThread()) {
WebThreadLock();
notifyClientsModelWasDestroyed();
m_playbackModel = nullptr;
m_fullscreenModel = nullptr;
} else
WorkQueue::main().dispatchSync(WTFMove(notifyClientsModelWasDestroyed));
}
#pragma mark VideoFullscreenChangeObserver
void VideoFullscreenControllerContext::requestUpdateInlineRect()
{
#if PLATFORM(IOS_FAMILY)
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this] () mutable {
IntRect clientRect = elementRectInWindow(m_videoElement.get());
RunLoop::main().dispatch([protectedThis = WTFMove(protectedThis), this, clientRect] {
m_interface->setInlineRect(clientRect, clientRect != IntRect(0, 0, 0, 0));
});
});
#else
ASSERT_NOT_REACHED();
#endif
}
void VideoFullscreenControllerContext::requestVideoContentLayer()
{
#if PLATFORM(IOS_FAMILY)
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this, videoFullscreenLayer = retainPtr([m_videoFullscreenView layer])] () mutable {
[videoFullscreenLayer setBackgroundColor:cachedCGColor(WebCore::Color::transparentBlack).get()];
m_fullscreenModel->setVideoFullscreenLayer(videoFullscreenLayer.get(), [protectedThis = WTFMove(protectedThis), this] () mutable {
RunLoop::main().dispatch([protectedThis = WTFMove(protectedThis), this] {
if (!m_interface)
return;
m_interface->setHasVideoContentLayer(true);
});
});
});
#else
ASSERT_NOT_REACHED();
#endif
}
void VideoFullscreenControllerContext::returnVideoContentLayer()
{
#if PLATFORM(IOS_FAMILY)
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this, videoFullscreenLayer = retainPtr([m_videoFullscreenView layer])] () mutable {
[videoFullscreenLayer setBackgroundColor:cachedCGColor(WebCore::Color::transparentBlack).get()];
m_fullscreenModel->setVideoFullscreenLayer(nil, [protectedThis = WTFMove(protectedThis), this] () mutable {
RunLoop::main().dispatch([protectedThis = WTFMove(protectedThis), this] {
if (!m_interface)
return;
m_interface->setHasVideoContentLayer(false);
});
});
});
#else
ASSERT_NOT_REACHED();
#endif
}
void VideoFullscreenControllerContext::didSetupFullscreen()
{
ASSERT(isUIThread());
#if PLATFORM(IOS_FAMILY)
RunLoop::main().dispatch([protectedThis = Ref { *this }, this] {
m_interface->enterFullscreen();
});
#else
WebThreadRun([protectedThis = Ref { *this }, this, videoFullscreenLayer = retainPtr([m_videoFullscreenView layer])] () mutable {
[videoFullscreenLayer setBackgroundColor:cachedCGColor(WebCore::Color::transparentBlack)];
m_fullscreenModel->setVideoFullscreenLayer(videoFullscreenLayer.get(), [protectedThis = WTFMove(protectedThis), this] () mutable {
RunLoop::main().dispatch([protectedThis = WTFMove(protectedThis), this] {
m_interface->enterFullscreen();
});
});
});
#endif
}
void VideoFullscreenControllerContext::willExitFullscreen()
{
#if PLATFORM(WATCHOS)
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this] () mutable {
m_fullscreenModel->willExitFullscreen();
});
#endif
}
void VideoFullscreenControllerContext::didExitFullscreen()
{
ASSERT(isUIThread());
#if PLATFORM(IOS_FAMILY)
RunLoop::main().dispatch([protectedThis = Ref { *this }, this] {
m_interface->cleanupFullscreen();
});
#else
WebThreadRun([protectedThis = Ref { *this }, this] () mutable {
m_fullscreenModel->setVideoFullscreenLayer(nil, [protectedThis = WTFMove(protectedThis), this] () mutable {
RunLoop::main().dispatch([protectedThis = WTFMove(protectedThis), this] {
m_interface->cleanupFullscreen();
});
});
});
#endif
}
void VideoFullscreenControllerContext::didCleanupFullscreen()
{
ASSERT(isUIThread());
m_interface->setVideoFullscreenModel(nullptr);
m_interface->setVideoFullscreenChangeObserver(nullptr);
m_interface = nullptr;
m_videoFullscreenView = nil;
WebThreadRun([protectedThis = Ref { *this }, this] {
m_fullscreenModel->setVideoFullscreenLayer(nil);
m_fullscreenModel->setVideoElement(nullptr);
m_playbackModel->setMediaElement(nullptr);
m_playbackModel->removeClient(*this);
m_fullscreenModel->removeClient(*this);
m_fullscreenModel = nullptr;
m_videoElement = nullptr;
[m_controller didFinishFullscreen:this];
});
}
void VideoFullscreenControllerContext::fullscreenMayReturnToInline()
{
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this] () mutable {
IntRect clientRect = elementRectInWindow(m_videoElement.get());
RunLoop::main().dispatch([protectedThis = WTFMove(protectedThis), this, clientRect] {
m_interface->preparedToReturnToInline(true, clientRect);
});
});
}
#pragma mark PlaybackSessionModelClient
void VideoFullscreenControllerContext::durationChanged(double duration)
{
if (WebThreadIsCurrent()) {
RunLoop::main().dispatch([protectedThis = Ref { *this }, duration] {
protectedThis->durationChanged(duration);
});
return;
}
for (auto& client : m_playbackClients)
client->durationChanged(duration);
}
void VideoFullscreenControllerContext::currentTimeChanged(double currentTime, double anchorTime)
{
if (WebThreadIsCurrent()) {
RunLoop::main().dispatch([protectedThis = Ref { *this }, currentTime, anchorTime] {
protectedThis->currentTimeChanged(currentTime, anchorTime);
});
return;
}
for (auto& client : m_playbackClients)
client->currentTimeChanged(currentTime, anchorTime);
}
void VideoFullscreenControllerContext::bufferedTimeChanged(double bufferedTime)
{
if (WebThreadIsCurrent()) {
RunLoop::main().dispatch([protectedThis = Ref { *this }, bufferedTime] {
protectedThis->bufferedTimeChanged(bufferedTime);
});
return;
}
for (auto& client : m_playbackClients)
client->bufferedTimeChanged(bufferedTime);
}
void VideoFullscreenControllerContext::rateChanged(OptionSet<PlaybackSessionModel::PlaybackState> playbackState, double playbackRate, double defaultPlaybackRate)
{
if (WebThreadIsCurrent()) {
RunLoop::main().dispatch([protectedThis = Ref { *this }, playbackState, playbackRate, defaultPlaybackRate] {
protectedThis->rateChanged(playbackState, playbackRate, defaultPlaybackRate);
});
return;
}
for (auto& client : m_playbackClients)
client->rateChanged(playbackState, playbackRate, defaultPlaybackRate);
}
void VideoFullscreenControllerContext::hasVideoChanged(bool hasVideo)
{
if (WebThreadIsCurrent()) {
RunLoop::main().dispatch([protectedThis = Ref { *this }, hasVideo] {
protectedThis->hasVideoChanged(hasVideo);
});
return;
}
for (auto& client : m_fullscreenClients)
client->hasVideoChanged(hasVideo);
}
void VideoFullscreenControllerContext::videoDimensionsChanged(const FloatSize& videoDimensions)
{
if (WebThreadIsCurrent()) {
RunLoop::main().dispatch([protectedThis = Ref { *this }, videoDimensions = videoDimensions] {
protectedThis->videoDimensionsChanged(videoDimensions);
});
return;
}
for (auto& client : m_fullscreenClients)
client->videoDimensionsChanged(videoDimensions);
}
void VideoFullscreenControllerContext::seekableRangesChanged(const TimeRanges& timeRanges, double lastModifiedTime, double liveUpdateInterval)
{
if (WebThreadIsCurrent()) {
RunLoop::main().dispatch([protectedThis = Ref { *this }, platformTimeRanges = timeRanges.ranges(), lastModifiedTime, liveUpdateInterval] {
protectedThis->seekableRangesChanged(TimeRanges::create(platformTimeRanges), lastModifiedTime, liveUpdateInterval);
});
return;
}
for (auto &client : m_playbackClients)
client->seekableRangesChanged(timeRanges, lastModifiedTime, liveUpdateInterval);
}
void VideoFullscreenControllerContext::canPlayFastReverseChanged(bool canPlayFastReverse)
{
if (WebThreadIsCurrent()) {
RunLoop::main().dispatch([protectedThis = Ref { *this }, canPlayFastReverse] {
protectedThis->canPlayFastReverseChanged(canPlayFastReverse);
});
return;
}
for (auto &client : m_playbackClients)
client->canPlayFastReverseChanged(canPlayFastReverse);
}
void VideoFullscreenControllerContext::audioMediaSelectionOptionsChanged(const Vector<MediaSelectionOption>& options, uint64_t selectedIndex)
{
if (WebThreadIsCurrent()) {
RunLoop::main().dispatch([protectedThis = Ref { *this }, options = crossThreadCopy(options), selectedIndex] {
protectedThis->audioMediaSelectionOptionsChanged(options, selectedIndex);
});
return;
}
for (auto& client : m_playbackClients)
client->audioMediaSelectionOptionsChanged(options, selectedIndex);
}
void VideoFullscreenControllerContext::legibleMediaSelectionOptionsChanged(const Vector<MediaSelectionOption>& options, uint64_t selectedIndex)
{
if (WebThreadIsCurrent()) {
RunLoop::main().dispatch([protectedThis = Ref { *this }, options = crossThreadCopy(options), selectedIndex] {
protectedThis->legibleMediaSelectionOptionsChanged(options, selectedIndex);
});
return;
}
for (auto& client : m_playbackClients)
client->legibleMediaSelectionOptionsChanged(options, selectedIndex);
}
void VideoFullscreenControllerContext::externalPlaybackChanged(bool enabled, PlaybackSessionModel::ExternalPlaybackTargetType type, const String& localizedDeviceName)
{
if (WebThreadIsCurrent()) {
callOnMainThread([protectedThis = Ref { *this }, this, enabled, type, localizedDeviceName = localizedDeviceName.isolatedCopy()] {
for (auto& client : m_playbackClients)
client->externalPlaybackChanged(enabled, type, localizedDeviceName);
});
return;
}
for (auto& client : m_playbackClients)
client->externalPlaybackChanged(enabled, type, localizedDeviceName);
}
void VideoFullscreenControllerContext::wirelessVideoPlaybackDisabledChanged(bool disabled)
{
if (WebThreadIsCurrent()) {
RunLoop::main().dispatch([protectedThis = Ref { *this }, disabled] {
protectedThis->wirelessVideoPlaybackDisabledChanged(disabled);
});
return;
}
for (auto& client : m_playbackClients)
client->wirelessVideoPlaybackDisabledChanged(disabled);
}
void VideoFullscreenControllerContext::mutedChanged(bool muted)
{
if (WebThreadIsCurrent()) {
RunLoop::main().dispatch([protectedThis = Ref { *this }, muted] {
protectedThis->mutedChanged(muted);
});
return;
}
for (auto& client : m_playbackClients)
client->mutedChanged(muted);
}
void VideoFullscreenControllerContext::volumeChanged(double volume)
{
if (WebThreadIsCurrent()) {
RunLoop::main().dispatch([protectedThis = Ref { *this }, volume] {
protectedThis->volumeChanged(volume);
});
return;
}
for (auto& client : m_playbackClients)
client->volumeChanged(volume);
}
#pragma mark VideoFullscreenModel
void VideoFullscreenControllerContext::addClient(VideoFullscreenModelClient& client)
{
ASSERT(isUIThread());
ASSERT(!m_fullscreenClients.contains(&client));
m_fullscreenClients.add(&client);
}
void VideoFullscreenControllerContext::removeClient(VideoFullscreenModelClient& client)
{
ASSERT(isUIThread());
ASSERT(m_fullscreenClients.contains(&client));
m_fullscreenClients.remove(&client);
}
void VideoFullscreenControllerContext::requestFullscreenMode(HTMLMediaElementEnums::VideoFullscreenMode mode, bool finishedWithMedia)
{
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this, mode, finishedWithMedia] {
if (m_fullscreenModel)
m_fullscreenModel->requestFullscreenMode(mode, finishedWithMedia);
});
}
void VideoFullscreenControllerContext::setVideoLayerFrame(FloatRect frame)
{
ASSERT(isUIThread());
RetainPtr<CALayer> videoFullscreenLayer = [m_videoFullscreenView layer];
[videoFullscreenLayer setSublayerTransform:[videoFullscreenLayer transform]];
dispatchAsyncOnMainThreadWithWebThreadLockIfNeeded([protectedThis = Ref { *this }, this, frame, videoFullscreenLayer = WTFMove(videoFullscreenLayer)] {
[CATransaction begin];
[CATransaction setDisableActions:YES];
[CATransaction setAnimationDuration:0];
[videoFullscreenLayer setSublayerTransform:CATransform3DIdentity];
if (m_fullscreenModel)
m_fullscreenModel->setVideoLayerFrame(frame);
[CATransaction commit];
});
}
void VideoFullscreenControllerContext::setVideoLayerGravity(MediaPlayerEnums::VideoGravity videoGravity)
{
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this, videoGravity] {
if (m_fullscreenModel)
m_fullscreenModel->setVideoLayerGravity(videoGravity);
});
}
void VideoFullscreenControllerContext::fullscreenModeChanged(HTMLMediaElementEnums::VideoFullscreenMode mode)
{
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this, mode] {
if (m_fullscreenModel)
m_fullscreenModel->fullscreenModeChanged(mode);
});
}
bool VideoFullscreenControllerContext::hasVideo() const
{
ASSERT(isUIThread());
return m_fullscreenModel ? m_fullscreenModel->hasVideo() : false;
}
bool VideoFullscreenControllerContext::isMuted() const
{
ASSERT(isUIThread());
return m_playbackModel ? m_playbackModel->isMuted() : false;
}
double VideoFullscreenControllerContext::volume() const
{
ASSERT(isUIThread());
return m_playbackModel ? m_playbackModel->volume() : 0;
}
bool VideoFullscreenControllerContext::isPictureInPictureActive() const
{
ASSERT(isUIThread());
return m_playbackModel ? m_playbackModel->isPictureInPictureActive() : false;
}
bool VideoFullscreenControllerContext::isPictureInPictureSupported() const
{
ASSERT(isUIThread());
return m_playbackModel ? m_playbackModel->isPictureInPictureSupported() : false;
}
void VideoFullscreenControllerContext::willEnterPictureInPicture()
{
ASSERT(isUIThread());
for (auto* client : m_fullscreenClients)
client->willEnterPictureInPicture();
}
void VideoFullscreenControllerContext::didEnterPictureInPicture()
{
ASSERT(isUIThread());
for (auto* client : m_fullscreenClients)
client->didEnterPictureInPicture();
}
void VideoFullscreenControllerContext::failedToEnterPictureInPicture()
{
ASSERT(isUIThread());
for (auto* client : m_fullscreenClients)
client->failedToEnterPictureInPicture();
}
void VideoFullscreenControllerContext::willExitPictureInPicture()
{
ASSERT(isUIThread());
for (auto* client : m_fullscreenClients)
client->willExitPictureInPicture();
}
void VideoFullscreenControllerContext::didExitPictureInPicture()
{
ASSERT(isUIThread());
for (auto* client : m_fullscreenClients)
client->didExitPictureInPicture();
}
FloatSize VideoFullscreenControllerContext::videoDimensions() const
{
ASSERT(isUIThread());
return m_fullscreenModel ? m_fullscreenModel->videoDimensions() : FloatSize();
}
#pragma mark - PlaybackSessionModel
void VideoFullscreenControllerContext::addClient(PlaybackSessionModelClient& client)
{
ASSERT(!m_playbackClients.contains(&client));
m_playbackClients.add(&client);
}
void VideoFullscreenControllerContext::removeClient(PlaybackSessionModelClient& client)
{
ASSERT(m_playbackClients.contains(&client));
m_playbackClients.remove(&client);
}
void VideoFullscreenControllerContext::play()
{
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this] {
if (m_playbackModel)
m_playbackModel->play();
});
}
void VideoFullscreenControllerContext::pause()
{
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this] {
if (m_playbackModel)
m_playbackModel->pause();
});
}
void VideoFullscreenControllerContext::togglePlayState()
{
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this] {
if (m_playbackModel)
m_playbackModel->togglePlayState();
});
}
void VideoFullscreenControllerContext::toggleMuted()
{
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this] {
if (m_playbackModel)
m_playbackModel->toggleMuted();
});
}
void VideoFullscreenControllerContext::setMuted(bool muted)
{
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this, muted] {
if (m_playbackModel)
m_playbackModel->setMuted(muted);
});
}
void VideoFullscreenControllerContext::setVolume(double volume)
{
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this, volume] {
if (m_playbackModel)
m_playbackModel->setVolume(volume);
});
}
void VideoFullscreenControllerContext::setPlayingOnSecondScreen(bool value)
{
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this, value] {
if (m_playbackModel)
m_playbackModel->setPlayingOnSecondScreen(value);
});
}
void VideoFullscreenControllerContext::beginScrubbing()
{
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this] {
if (m_playbackModel)
m_playbackModel->beginScrubbing();
});
}
void VideoFullscreenControllerContext::endScrubbing()
{
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this] {
if (m_playbackModel)
m_playbackModel->endScrubbing();
});
}
void VideoFullscreenControllerContext::seekToTime(double time, double toleranceBefore, double toleranceAfter)
{
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this, time, toleranceBefore, toleranceAfter] {
if (m_playbackModel)
m_playbackModel->seekToTime(time, toleranceBefore, toleranceAfter);
});
}
void VideoFullscreenControllerContext::fastSeek(double time)
{
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this, time] {
if (m_playbackModel)
m_playbackModel->fastSeek(time);
});
}
void VideoFullscreenControllerContext::beginScanningForward()
{
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this] {
if (m_playbackModel)
m_playbackModel->beginScanningForward();
});
}
void VideoFullscreenControllerContext::beginScanningBackward()
{
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this] {
if (m_playbackModel)
m_playbackModel->beginScanningBackward();
});
}
void VideoFullscreenControllerContext::endScanning()
{
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this] {
if (m_playbackModel)
m_playbackModel->endScanning();
});
}
void VideoFullscreenControllerContext::setDefaultPlaybackRate(double defaultPlaybackRate)
{
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this, defaultPlaybackRate] {
if (m_playbackModel)
m_playbackModel->setDefaultPlaybackRate(defaultPlaybackRate);
});
}
void VideoFullscreenControllerContext::setPlaybackRate(double playbackRate)
{
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this, playbackRate] {
if (m_playbackModel)
m_playbackModel->setPlaybackRate(playbackRate);
});
}
void VideoFullscreenControllerContext::selectAudioMediaOption(uint64_t index)
{
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this, index] {
if (m_playbackModel)
m_playbackModel->selectAudioMediaOption(index);
});
}
void VideoFullscreenControllerContext::selectLegibleMediaOption(uint64_t index)
{
ASSERT(isUIThread());
WebThreadRun([protectedThis = Ref { *this }, this, index] {
if (m_playbackModel)
m_playbackModel->selectLegibleMediaOption(index);
});
}
double VideoFullscreenControllerContext::duration() const
{
ASSERT(isUIThread());
return m_playbackModel ? m_playbackModel->duration() : 0;
}
double VideoFullscreenControllerContext::currentTime() const
{
ASSERT(isUIThread());
return m_playbackModel ? m_playbackModel->currentTime() : 0;
}
double VideoFullscreenControllerContext::bufferedTime() const
{
ASSERT(isUIThread());
return m_playbackModel ? m_playbackModel->bufferedTime() : 0;
}
bool VideoFullscreenControllerContext::isPlaying() const
{
ASSERT(isUIThread());
return m_playbackModel ? m_playbackModel->isPlaying() : false;
}
bool VideoFullscreenControllerContext::isStalled() const
{
ASSERT(isUIThread());
return m_playbackModel ? m_playbackModel->isStalled() : false;
}
double VideoFullscreenControllerContext::defaultPlaybackRate() const
{
ASSERT(isUIThread());
return m_playbackModel ? m_playbackModel->defaultPlaybackRate() : 0;
}
double VideoFullscreenControllerContext::playbackRate() const
{
ASSERT(isUIThread());
return m_playbackModel ? m_playbackModel->playbackRate() : 0;
}
Ref<TimeRanges> VideoFullscreenControllerContext::seekableRanges() const
{
ASSERT(isUIThread());
return m_playbackModel ? m_playbackModel->seekableRanges() : TimeRanges::create();
}
double VideoFullscreenControllerContext::seekableTimeRangesLastModifiedTime() const
{
ASSERT(isUIThread());
return m_playbackModel ? m_playbackModel->seekableTimeRangesLastModifiedTime() : 0;
}
double VideoFullscreenControllerContext::liveUpdateInterval() const
{
ASSERT(isUIThread());
return m_playbackModel ? m_playbackModel->liveUpdateInterval() : 0;
}
bool VideoFullscreenControllerContext::canPlayFastReverse() const
{
ASSERT(isUIThread());
return m_playbackModel ? m_playbackModel->canPlayFastReverse() : false;
}
Vector<MediaSelectionOption> VideoFullscreenControllerContext::audioMediaSelectionOptions() const
{
ASSERT(isUIThread());
if (m_playbackModel)
return m_playbackModel->audioMediaSelectionOptions();
return { };
}
uint64_t VideoFullscreenControllerContext::audioMediaSelectedIndex() const
{
ASSERT(isUIThread());
return m_playbackModel ? m_playbackModel->audioMediaSelectedIndex() : -1;
}
Vector<MediaSelectionOption> VideoFullscreenControllerContext::legibleMediaSelectionOptions() const
{
ASSERT(isUIThread());
if (m_playbackModel)
return m_playbackModel->legibleMediaSelectionOptions();
return { };
}
uint64_t VideoFullscreenControllerContext::legibleMediaSelectedIndex() const
{
ASSERT(isUIThread());
return m_playbackModel ? m_playbackModel->legibleMediaSelectedIndex() : -1;
}
bool VideoFullscreenControllerContext::externalPlaybackEnabled() const
{
ASSERT(isUIThread());
return m_playbackModel ? m_playbackModel->externalPlaybackEnabled() : false;
}
PlaybackSessionModel::ExternalPlaybackTargetType VideoFullscreenControllerContext::externalPlaybackTargetType() const
{
ASSERT(isUIThread());
return m_playbackModel ? m_playbackModel->externalPlaybackTargetType() : TargetTypeNone;
}
String VideoFullscreenControllerContext::externalPlaybackLocalizedDeviceName() const
{
ASSERT(isUIThread());
return m_playbackModel ? m_playbackModel->externalPlaybackLocalizedDeviceName() : String();
}
bool VideoFullscreenControllerContext::wirelessVideoPlaybackDisabled() const
{
ASSERT(isUIThread());
return m_playbackModel ? m_playbackModel->wirelessVideoPlaybackDisabled() : true;
}
#pragma mark Other
void VideoFullscreenControllerContext::setUpFullscreen(HTMLVideoElement& videoElement, UIView *view, HTMLMediaElementEnums::VideoFullscreenMode mode)
{
ASSERT(isMainThread());
RetainPtr<UIView> viewRef = view;
m_videoElement = &videoElement;
m_playbackModel = PlaybackSessionModelMediaElement::create();
m_playbackModel->addClient(*this);
m_playbackModel->setMediaElement(m_videoElement.get());
m_fullscreenModel = VideoFullscreenModelVideoElement::create();
m_fullscreenModel->addClient(*this);
m_fullscreenModel->setVideoElement(m_videoElement.get());
bool allowsPictureInPicture = m_videoElement->webkitSupportsPresentationMode(HTMLVideoElement::VideoPresentationMode::PictureInPicture);
IntRect videoElementClientRect = elementRectInWindow(m_videoElement.get());
FloatRect videoLayerFrame = FloatRect(FloatPoint(), videoElementClientRect.size());
m_fullscreenModel->setVideoLayerFrame(videoLayerFrame);
FloatSize videoDimensions = { (float)videoElement.videoWidth(), (float)videoElement.videoHeight() };
RunLoop::main().dispatch([protectedThis = Ref { *this }, this, videoElementClientRect, videoDimensions, viewRef, mode, allowsPictureInPicture] {
ASSERT(isUIThread());
WebThreadLock();
Ref<PlaybackSessionInterfaceAVKit> sessionInterface = PlaybackSessionInterfaceAVKit::create(*this);
m_interface = VideoFullscreenInterfaceAVKit::create(sessionInterface.get());
m_interface->setVideoFullscreenChangeObserver(this);
m_interface->setVideoFullscreenModel(this);
m_videoFullscreenView = adoptNS([PAL::allocUIViewInstance() init]);
m_interface->setupFullscreen(*m_videoFullscreenView.get(), videoElementClientRect, videoDimensions, viewRef.get(), mode, allowsPictureInPicture, false, false);
});
}
void VideoFullscreenControllerContext::exitFullscreen()
{
ASSERT(WebThreadIsCurrent() || isMainThread());
IntRect clientRect = elementRectInWindow(m_videoElement.get());
RunLoop::main().dispatch([protectedThis = Ref { *this }, this, clientRect] {
ASSERT(isUIThread());
m_interface->exitFullscreen(clientRect);
});
}
void VideoFullscreenControllerContext::requestHideAndExitFullscreen()
{
ASSERT(isUIThread());
m_interface->requestHideAndExitFullscreen();
}
@implementation WebVideoFullscreenController {
RefPtr<VideoFullscreenControllerContext> _context;
RefPtr<HTMLVideoElement> _videoElement;
}
- (instancetype)init
{
if (!(self = [super init]))
return nil;
return self;
}
- (void)setVideoElement:(NakedPtr<HTMLVideoElement>)videoElement
{
_videoElement = videoElement;
}
- (NakedPtr<HTMLVideoElement>)videoElement
{
return _videoElement.get();
}
- (void)enterFullscreen:(UIView *)view mode:(HTMLMediaElementEnums::VideoFullscreenMode)mode
{
ASSERT(isMainThread());
_context = VideoFullscreenControllerContext::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:(VideoFullscreenControllerContext*)context
{
ASSERT(WebThreadIsCurrent());
ASSERT_UNUSED(context, context == _context);
auto strongSelf = retainPtr(self); // retain self before breaking a retain cycle.
_context->setController(nil);
_context = nullptr;
_videoElement = nullptr;
}
@end
#endif // !HAVE(AVKIT)
#endif // PLATFORM(IOS_FAMILY)