/* * Copyright (C) 2013 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #if PLATFORM(IOS) #import "WebVideoFullscreenControllerAVKit.h" #import "Logging.h" #import "TimeRanges.h" #import "WebVideoFullscreenInterfaceAVKit.h" #import "WebVideoFullscreenModelVideoElement.h" #import #import #import using namespace WebCore; #if __IPHONE_OS_VERSION_MIN_REQUIRED <= 80200 || !HAVE(AVKIT) @implementation WebVideoFullscreenController - (void)setVideoElement:(WebCore::HTMLVideoElement*)videoElement { UNUSED_PARAM(videoElement); } - (WebCore::HTMLVideoElement*)videoElement { return nullptr; } - (void)enterFullscreen:(UIView *)view mode:(WebCore::HTMLMediaElementEnums::VideoFullscreenMode)mode { UNUSED_PARAM(view); UNUSED_PARAM(mode); } - (void)requestHideAndExitFullscreen { } - (void)exitFullscreen { } @end #else class WebVideoFullscreenControllerContext; @interface WebVideoFullscreenController (delegate) -(void)didFinishFullscreen:(WebVideoFullscreenControllerContext*)context; @end class WebVideoFullscreenControllerContext final : private WebVideoFullscreenInterface , private WebVideoFullscreenModel , private WebVideoFullscreenChangeObserver , public ThreadSafeRefCounted { public: static Ref create() { return adoptRef(*new WebVideoFullscreenControllerContext); } void setController(WebVideoFullscreenController* controller) { m_controller = controller; } void setUpFullscreen(HTMLVideoElement&, UIView *, HTMLMediaElementEnums::VideoFullscreenMode); void exitFullscreen(); void requestHideAndExitFullscreen(); void invalidate(); private: WebVideoFullscreenControllerContext() { } // WebVideoFullscreenChangeObserver virtual void didSetupFullscreen() override; virtual void didEnterFullscreen() override { } virtual void didExitFullscreen() override; virtual void didCleanupFullscreen() override; virtual void fullscreenMayReturnToInline() override; // WebVideoFullscreenInterface virtual void resetMediaState() override; virtual void setDuration(double) override; virtual void setCurrentTime(double currentTime, double anchorTime) override; virtual void setBufferedTime(double) override; virtual void setRate(bool isPlaying, float playbackRate) override; virtual void setVideoDimensions(bool hasVideo, float width, float height) override; virtual void setSeekableRanges(const TimeRanges&) override; virtual void setCanPlayFastReverse(bool) override; virtual void setAudioMediaSelectionOptions(const Vector& options, uint64_t selectedIndex) override; virtual void setLegibleMediaSelectionOptions(const Vector& options, uint64_t selectedIndex) override; virtual void setExternalPlayback(bool enabled, ExternalPlaybackTargetType, String localizedDeviceName) override; // WebVideoFullscreenModel virtual void play() override; virtual void pause() override; virtual void togglePlayState() override; virtual void beginScrubbing() override; virtual void endScrubbing() override; virtual void seekToTime(double time) override; virtual void fastSeek(double time) override; virtual void beginScanningForward() override; virtual void beginScanningBackward() override; virtual void endScanning() override; virtual void requestExitFullscreen() override; virtual void setVideoLayerFrame(FloatRect) override; virtual FloatRect videoLayerFrame() const override { return m_frame; } virtual void setVideoLayerGravity(WebVideoFullscreenModel::VideoGravity) override; virtual VideoGravity videoLayerGravity() const override { return m_gravity; } virtual void selectAudioMediaOption(uint64_t index) override; virtual void selectLegibleMediaOption(uint64_t index) override; virtual void fullscreenModeChanged(HTMLMediaElementEnums::VideoFullscreenMode) override; RefPtr m_interface; RefPtr m_model; RefPtr m_videoElement; RetainPtr m_videoFullscreenLayer; RetainPtr m_controller; FloatRect m_frame; VideoGravity m_gravity; }; #pragma mark WebVideoFullscreenChangeObserver void WebVideoFullscreenControllerContext::didSetupFullscreen() { ASSERT(isUIThread()); RefPtr strongThis(this); WebThreadRun([strongThis, this] { m_model->setVideoFullscreenLayer(m_videoFullscreenLayer.get()); dispatch_async(dispatch_get_main_queue(), [strongThis, this] { m_interface->enterFullscreen(); }); }); } void WebVideoFullscreenControllerContext::didExitFullscreen() { ASSERT(isUIThread()); RefPtr strongThis(this); WebThreadRun([strongThis, this] { m_model->setVideoFullscreenLayer(nil); dispatch_async(dispatch_get_main_queue(), [strongThis, this] { m_interface->cleanupFullscreen(); }); }); } void WebVideoFullscreenControllerContext::didCleanupFullscreen() { ASSERT(isUIThread()); m_interface->setWebVideoFullscreenModel(nullptr); m_interface->setWebVideoFullscreenChangeObserver(nullptr); m_interface = nullptr; RefPtr strongThis(this); WebThreadRun([strongThis, this] { m_model->setVideoFullscreenLayer(nil); m_model->setWebVideoFullscreenInterface(nullptr); m_model->setVideoElement(nullptr); m_model = nullptr; m_videoElement = nullptr; m_videoFullscreenLayer = nil; [m_controller didFinishFullscreen:this]; }); } void WebVideoFullscreenControllerContext::fullscreenMayReturnToInline() { ASSERT(isUIThread()); RefPtr strongThis(this); WebThreadRun([strongThis, this] { IntRect clientRect = m_videoElement->clientRect(); dispatch_async(dispatch_get_main_queue(), [strongThis, this, clientRect] { m_interface->preparedToReturnToInline(true, clientRect); }); }); } #pragma mark WebVideoFullscreenInterface void WebVideoFullscreenControllerContext::resetMediaState() { ASSERT(WebThreadIsCurrent() || isMainThread()); RefPtr strongThis(this); dispatch_async(dispatch_get_main_queue(), [strongThis, this] { if (m_interface) m_interface->resetMediaState(); }); } void WebVideoFullscreenControllerContext::setDuration(double duration) { ASSERT(WebThreadIsCurrent()); RefPtr strongThis(this); dispatch_async(dispatch_get_main_queue(), [strongThis, this, duration] { if (m_interface) m_interface->setDuration(duration); }); } void WebVideoFullscreenControllerContext::setCurrentTime(double currentTime, double anchorTime) { ASSERT(WebThreadIsCurrent()); RefPtr strongThis(this); dispatch_async(dispatch_get_main_queue(), [strongThis, this, currentTime, anchorTime] { if (m_interface) m_interface->setCurrentTime(currentTime, anchorTime); }); } void WebVideoFullscreenControllerContext::setBufferedTime(double bufferedTime) { ASSERT(WebThreadIsCurrent()); RefPtr strongThis(this); dispatch_async(dispatch_get_main_queue(), [strongThis, this, bufferedTime] { if (m_interface) m_interface->setBufferedTime(bufferedTime); }); } void WebVideoFullscreenControllerContext::setRate(bool isPlaying, float playbackRate) { ASSERT(WebThreadIsCurrent()); RefPtr strongThis(this); dispatch_async(dispatch_get_main_queue(), [strongThis, this, isPlaying, playbackRate] { if (m_interface) m_interface->setRate(isPlaying, playbackRate); }); } void WebVideoFullscreenControllerContext::setVideoDimensions(bool hasVideo, float width, float height) { ASSERT(WebThreadIsCurrent()); RefPtr strongThis(this); dispatch_async(dispatch_get_main_queue(), [strongThis, this, hasVideo, width, height] { if (m_interface) m_interface->setVideoDimensions(hasVideo, width, height); }); } void WebVideoFullscreenControllerContext::setSeekableRanges(const TimeRanges& timeRanges) { ASSERT(WebThreadIsCurrent()); RefPtr strongThis(this); const PlatformTimeRanges& platformTimeRanges = timeRanges.ranges(); dispatch_async(dispatch_get_main_queue(), [strongThis, this, platformTimeRanges] { if (m_interface) m_interface->setSeekableRanges(TimeRanges::create(platformTimeRanges)); }); } void WebVideoFullscreenControllerContext::setCanPlayFastReverse(bool canPlayFastReverse) { ASSERT(WebThreadIsCurrent()); RefPtr strongThis(this); dispatch_async(dispatch_get_main_queue(), [strongThis, this, canPlayFastReverse] { if (m_interface) m_interface->setCanPlayFastReverse(canPlayFastReverse); }); } void WebVideoFullscreenControllerContext::setAudioMediaSelectionOptions(const Vector& options, uint64_t selectedIndex) { ASSERT(WebThreadIsCurrent()); RefPtr strongThis(this); RetainPtr optionsArray = adoptNS([[NSMutableArray alloc] initWithCapacity:options.size()]); for (auto& name : options) [optionsArray addObject:name]; dispatch_async(dispatch_get_main_queue(), [strongThis, this, optionsArray, selectedIndex] { Vector options; for (NSString *name : optionsArray.get()) options.append(name); if (m_interface) m_interface->setAudioMediaSelectionOptions(options, selectedIndex); }); } void WebVideoFullscreenControllerContext::setLegibleMediaSelectionOptions(const Vector& options, uint64_t selectedIndex) { ASSERT(WebThreadIsCurrent()); RefPtr strongThis(this); RetainPtr optionsArray = adoptNS([[NSMutableArray alloc] initWithCapacity:options.size()]); for (auto& name : options) [optionsArray addObject:name]; dispatch_async(dispatch_get_main_queue(), [strongThis, this, optionsArray, selectedIndex] { Vector options; for (NSString *name : optionsArray.get()) options.append(name); if (m_interface) m_interface->setLegibleMediaSelectionOptions(options, selectedIndex); }); } void WebVideoFullscreenControllerContext::setExternalPlayback(bool enabled, ExternalPlaybackTargetType type, String localizedDeviceName) { ASSERT(WebThreadIsCurrent()); RefPtr strongThis(this); StringCapture capturedLocalizedDeviceName(localizedDeviceName); dispatch_async(dispatch_get_main_queue(), [strongThis, this, enabled, type, capturedLocalizedDeviceName] { if (m_interface) m_interface->setExternalPlayback(enabled, type, capturedLocalizedDeviceName.string()); }); } #pragma mark WebVideoFullscreenModel void WebVideoFullscreenControllerContext::play() { ASSERT(isUIThread()); RefPtr strongThis(this); WebThreadRun([strongThis, this] { if (m_model) m_model->play(); }); } void WebVideoFullscreenControllerContext::pause() { ASSERT(isUIThread()); RefPtr strongThis(this); WebThreadRun([strongThis, this] { if (m_model) m_model->pause(); }); } void WebVideoFullscreenControllerContext::togglePlayState() { ASSERT(isUIThread()); RefPtr strongThis(this); WebThreadRun([strongThis, this] { if (m_model) m_model->togglePlayState(); }); } void WebVideoFullscreenControllerContext::beginScrubbing() { ASSERT(isUIThread()); RefPtr strongThis(this); WebThreadRun([strongThis, this] { if (m_model) m_model->beginScrubbing(); }); } void WebVideoFullscreenControllerContext::endScrubbing() { ASSERT(isUIThread()); RefPtr strongThis(this); WebThreadRun([strongThis, this] { if (m_model) m_model->endScrubbing(); }); } void WebVideoFullscreenControllerContext::seekToTime(double time) { ASSERT(isUIThread()); RefPtr strongThis(this); WebThreadRun([strongThis, this, time] { if (m_model) m_model->seekToTime(time); }); } void WebVideoFullscreenControllerContext::fastSeek(double time) { ASSERT(isUIThread()); RefPtr strongThis(this); WebThreadRun([strongThis, this, time] { if (m_model) m_model->fastSeek(time); }); } void WebVideoFullscreenControllerContext::beginScanningForward() { ASSERT(isUIThread()); RefPtr strongThis(this); WebThreadRun([strongThis, this] { if (m_model) m_model->beginScanningForward(); }); } void WebVideoFullscreenControllerContext::beginScanningBackward() { ASSERT(isUIThread()); RefPtr strongThis(this); WebThreadRun([strongThis, this] { if (m_model) m_model->beginScanningBackward(); }); } void WebVideoFullscreenControllerContext::endScanning() { ASSERT(isUIThread()); RefPtr strongThis(this); WebThreadRun([strongThis, this] { if (m_model) m_model->endScanning(); }); } void WebVideoFullscreenControllerContext::requestExitFullscreen() { ASSERT(isUIThread()); RefPtr strongThis(this); WebThreadRun([strongThis, this] { if (m_model) m_model->requestExitFullscreen(); }); } void WebVideoFullscreenControllerContext::setVideoLayerFrame(FloatRect frame) { ASSERT(isUIThread()); m_frame = frame; RefPtr strongThis(this); WebThreadRun([strongThis, this, frame] { if (m_model) m_model->setVideoLayerFrame(frame); }); } void WebVideoFullscreenControllerContext::setVideoLayerGravity(WebVideoFullscreenModel::VideoGravity videoGravity) { ASSERT(isUIThread()); m_gravity = videoGravity; RefPtr strongThis(this); WebThreadRun([strongThis, this, videoGravity] { if (m_model) m_model->setVideoLayerGravity(videoGravity); }); } void WebVideoFullscreenControllerContext::selectAudioMediaOption(uint64_t index) { ASSERT(isUIThread()); RefPtr strongThis(this); WebThreadRun([strongThis, this, index] { if (m_model) m_model->selectAudioMediaOption(index); }); } void WebVideoFullscreenControllerContext::selectLegibleMediaOption(uint64_t index) { ASSERT(isUIThread()); RefPtr strongThis(this); WebThreadRun([strongThis, this, index] { if (m_model) m_model->selectLegibleMediaOption(index); }); } void WebVideoFullscreenControllerContext::fullscreenModeChanged(HTMLMediaElementEnums::VideoFullscreenMode mode) { ASSERT(isUIThread()); RefPtr strongThis(this); WebThreadRun([strongThis, this, mode] { if (m_model) m_model->fullscreenModeChanged(mode); }); } #pragma mark Other void WebVideoFullscreenControllerContext::setUpFullscreen(HTMLVideoElement& videoElement, UIView *view, HTMLMediaElementEnums::VideoFullscreenMode mode) { ASSERT(isMainThread()); RetainPtr viewRef = view; m_videoElement = &videoElement; m_interface = WebVideoFullscreenInterfaceAVKit::create(); m_interface->setWebVideoFullscreenChangeObserver(this); m_interface->setWebVideoFullscreenModel(this); RefPtr strongThis(this); WebThreadRun([strongThis, this, viewRef, mode] { m_model = WebVideoFullscreenModelVideoElement::create(); m_model->setWebVideoFullscreenInterface(this); m_model->setVideoElement(m_videoElement.get()); m_videoFullscreenLayer = [CALayer layer]; bool allowsPictureInPicture = m_videoElement->mediaSession().allowsPictureInPicture(*m_videoElement.get()); IntRect videoElementClientRect = m_videoElement->clientRect(); dispatch_async(dispatch_get_main_queue(), [strongThis, this, videoElementClientRect, viewRef, mode, allowsPictureInPicture] { m_interface->setupFullscreen(*m_videoFullscreenLayer.get(), videoElementClientRect, viewRef.get(), mode, allowsPictureInPicture); }); }); } void WebVideoFullscreenControllerContext::exitFullscreen() { ASSERT(WebThreadIsCurrent() || isMainThread()); IntRect screenRect = m_videoElement->screenRect(); RefPtr strongThis(this); dispatch_async(dispatch_get_main_queue(), [strongThis, this, screenRect] { ASSERT(isUIThread()); m_interface->exitFullscreen(screenRect); }); } void WebVideoFullscreenControllerContext::requestHideAndExitFullscreen() { ASSERT(isUIThread()); m_interface->requestHideAndExitFullscreen(); } @implementation WebVideoFullscreenController { RefPtr _context; RefPtr _videoElement; } - (instancetype)init { if (!(self = [super init])) return nil; return self; } - (void)setVideoElement:(HTMLVideoElement*)videoElement { _videoElement = videoElement; } - (HTMLVideoElement*)videoElement { return _videoElement.get(); } - (void)enterFullscreen:(UIView *)view mode:(HTMLMediaElementEnums::VideoFullscreenMode)mode { ASSERT(isMainThread()); _context = WebVideoFullscreenControllerContext::create(); _context->setController(self); _context->setUpFullscreen(*_videoElement.get(), view, mode); } - (void)exitFullscreen { ASSERT(WebThreadIsCurrent() || isMainThread()); _context->exitFullscreen(); } - (void)requestHideAndExitFullscreen { ASSERT(isUIThread()); if (_context) _context->requestHideAndExitFullscreen(); } - (void)didFinishFullscreen:(WebVideoFullscreenControllerContext*)context { ASSERT(WebThreadIsCurrent()); ASSERT_UNUSED(context, context == _context); [[self retain] autorelease]; // retain self before breaking a retain cycle. _context->setController(nil); _context = nullptr; _videoElement = nullptr; } @end #endif // __IPHONE_OS_VERSION_MIN_REQUIRED < 80000 #endif // PLATFORM(IOS)