/* * 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 #import #import #import #import #import using namespace WebCore; #if !(ENABLE(VIDEO_PRESENTATION_MODE) && HAVE(AVKIT)) @implementation WebVideoFullscreenController - (void)setVideoElement:(NakedPtr)videoElement { UNUSED_PARAM(videoElement); } - (NakedPtr)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 { public: static Ref 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 seekableRanges() const override; double seekableTimeRangesLastModifiedTime() const override; double liveUpdateInterval() const override; bool canPlayFastReverse() const override; Vector audioMediaSelectionOptions() const override; uint64_t audioMediaSelectedIndex() const override; Vector 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, double playbackRate, double defaultPlaybackRate) override; void seekableRangesChanged(const TimeRanges&, double lastModifiedTime, double liveUpdateInterval) override; void canPlayFastReverseChanged(bool) override; void audioMediaSelectionOptionsChanged(const Vector& options, uint64_t selectedIndex) override; void legibleMediaSelectionOptionsChanged(const Vector& 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 m_playbackClients; HashSet m_fullscreenClients; RefPtr m_interface; RefPtr m_fullscreenModel; RefPtr m_playbackModel; RefPtr m_videoElement; RetainPtr m_videoFullscreenView; RetainPtr 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 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& 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& 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 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 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 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 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 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 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 _context; RefPtr _videoElement; } - (instancetype)init { if (!(self = [super init])) return nil; return self; } - (void)setVideoElement:(NakedPtr)videoElement { _videoElement = videoElement; } - (NakedPtr)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)