| /* |
| * 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) |