WebVideoFullscreenControllerAVKit.mm   [plain text]


/*
 * Copyright (C) 2013 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#import "config.h"

#if PLATFORM(IOS)

#import "WebVideoFullscreenControllerAVKit.h"

#import "Logging.h"
#import "QuartzCoreSPI.h"
#import "SoftLinking.h"
#import "TimeRanges.h"
#import "WebPlaybackSessionInterfaceAVKit.h"
#import "WebPlaybackSessionModelMediaElement.h"
#import "WebVideoFullscreenChangeObserver.h"
#import "WebVideoFullscreenInterfaceAVKit.h"
#import "WebVideoFullscreenModelVideoElement.h"
#import <QuartzCore/CoreAnimation.h>
#import <WebCore/FrameView.h>
#import <WebCore/HTMLVideoElement.h>
#import <WebCore/RenderVideo.h>
#import <WebCore/WebCoreThreadRun.h>

SOFT_LINK_FRAMEWORK(UIKit)
SOFT_LINK_CLASS(UIKit, UIView)

using namespace WebCore;

#if !HAVE(AVKIT)

@implementation WebVideoFullscreenController
- (void)setVideoElement:(WebCore::HTMLVideoElement*)videoElement
{
    UNUSED_PARAM(videoElement);
}

- (WebCore::HTMLVideoElement*)videoElement
{
    return nullptr;
}

- (void)enterFullscreen:(UIView *)view mode:(WebCore::HTMLMediaElementEnums::VideoFullscreenMode)mode
{
    UNUSED_PARAM(view);
    UNUSED_PARAM(mode);
}

- (void)requestHideAndExitFullscreen
{
}

- (void)exitFullscreen
{
}
@end

#else

static IntRect elementRectInWindow(HTMLVideoElement* videoElement)
{
    if (!videoElement)
        return { };
    auto* renderer = videoElement->renderer();
    auto* view = videoElement->document().view();
    if (!renderer || !view)
        return { };
    return view->convertToContainingWindow(renderer->absoluteBoundingBoxRect());
}

class WebVideoFullscreenControllerContext;
class WebVideoFullscreenSessionModel;

@interface WebVideoFullscreenController (delegate)
-(void)didFinishFullscreen:(WebVideoFullscreenControllerContext*)context;
@end

class WebVideoFullscreenControllerContext final
    : private WebVideoFullscreenInterface
    , private WebVideoFullscreenModel
    , private WebVideoFullscreenChangeObserver
    , public ThreadSafeRefCounted<WebVideoFullscreenControllerContext> {

public:
    static Ref<WebVideoFullscreenControllerContext> create()
    {
        return adoptRef(*new WebVideoFullscreenControllerContext);
    }

    void setController(WebVideoFullscreenController* controller) { m_controller = controller; }
    void setUpFullscreen(HTMLVideoElement&, UIView *, HTMLMediaElementEnums::VideoFullscreenMode);
    void exitFullscreen();
    void requestHideAndExitFullscreen();
    void invalidate();

    void play();
    void pause();
    void togglePlayState();
    void beginScrubbing();
    void endScrubbing();
    void seekToTime(double);
    void fastSeek(double time);
    void beginScanningForward();
    void beginScanningBackward();
    void endScanning();
    void selectAudioMediaOption(uint64_t);
    void selectLegibleMediaOption(uint64_t);

private:
    WebVideoFullscreenControllerContext() { }

    // WebVideoFullscreenChangeObserver
    void didSetupFullscreen() override;
    void didEnterFullscreen() override { }
    void didExitFullscreen() override;
    void didCleanupFullscreen() override;
    void fullscreenMayReturnToInline() override;

    // WebVideoFullscreenInterface
    void resetMediaState() override;
    void setDuration(double) override;
    void setCurrentTime(double currentTime, double anchorTime) override;
    void setBufferedTime(double) override;
    void setRate(bool isPlaying, float playbackRate) override;
    void setVideoDimensions(bool hasVideo, float width, float height) override;
    void setSeekableRanges(const TimeRanges&) override;
    void setCanPlayFastReverse(bool) override;
    void setAudioMediaSelectionOptions(const Vector<String>& options, uint64_t selectedIndex) override;
    void setLegibleMediaSelectionOptions(const Vector<String>& options, uint64_t selectedIndex) override;
    void setExternalPlayback(bool enabled, ExternalPlaybackTargetType, String localizedDeviceName) override;
    void setWirelessVideoPlaybackDisabled(bool) override;

    // WebVideoFullscreenModel
    void requestFullscreenMode(HTMLMediaElementEnums::VideoFullscreenMode) override;
    void setVideoLayerFrame(FloatRect) override;
    void setVideoLayerGravity(WebVideoFullscreenModel::VideoGravity) override;
    void fullscreenModeChanged(HTMLMediaElementEnums::VideoFullscreenMode) override;
    bool isVisible() const override;

    RefPtr<WebVideoFullscreenInterfaceAVKit> m_interface;
    RefPtr<WebVideoFullscreenModelVideoElement> m_model;
    RefPtr<HTMLVideoElement> m_videoElement;
    RetainPtr<UIView> m_videoFullscreenView;
    RetainPtr<WebVideoFullscreenController> m_controller;
    RefPtr<WebVideoFullscreenSessionModel> m_sessionModel;
};


class WebVideoFullscreenSessionModel final: public RefCounted<WebVideoFullscreenSessionModel>, public WebCore::WebPlaybackSessionModel  {
public:
    static Ref<WebVideoFullscreenSessionModel> create(WebVideoFullscreenControllerContext& controller)
    {
        return adoptRef(*new WebVideoFullscreenSessionModel(controller));
    }
    virtual ~WebVideoFullscreenSessionModel() { }

    void invalidate() { m_controller = nullptr; }

    void setDuration(double duration) { m_duration = duration; }
    void setCurrentTime(double currentTime) { m_currentTime = currentTime; }
    void setBufferedTime(double bufferedTime) { m_bufferedTime = bufferedTime; }
    void setIsPlaying(bool isPlaying) { m_isPlaying = isPlaying; }
    void setPlaybackRate(float playbackRate) { m_playbackRate = playbackRate; }
    void setSeekableRanges(WebCore::TimeRanges& seekableRanges) { m_seekableRanges = seekableRanges; }
    void setCanPlayFastReverse(bool canPlayFastReverse) { m_canPlayFastReverse = canPlayFastReverse; }
    void setAudioMediaSelectionOptions(const Vector<WTF::String>& audioMediaSelectionOptions) { m_audioMediaSelectionOptions = audioMediaSelectionOptions; }
    void setAudioMediaSelectedIndex(uint64_t audioMediaSelectedIndex) { m_audioMediaSelectedIndex = audioMediaSelectedIndex; }
    void setLegibleMediaSelectionOptions(const Vector<WTF::String>& legibleMediaSelectionOptions) { m_legibleMediaSelectionOptions = legibleMediaSelectionOptions; }
    void setLegibleMediaSelectedIndex(uint64_t legibleMediaSelectedIndex) { m_legibleMediaSelectedIndex = legibleMediaSelectedIndex; }
    void setExternalPlaybackEnabled(bool externalPlaybackEnabled) { m_externalPlaybackEnabled = externalPlaybackEnabled; }
    void setWirelessVideoPlaybackDisabled(bool wirelessVideoPlaybackDisabled) { m_wirelessVideoPlaybackDisabled = wirelessVideoPlaybackDisabled; }

private:
    WebVideoFullscreenSessionModel(WebVideoFullscreenControllerContext& controller)
        : m_controller(&controller)
    {
    }

    void play() final;
    void pause() final;
    void togglePlayState() final;
    void beginScrubbing() final;
    void endScrubbing() final;
    void seekToTime(double) final;
    void fastSeek(double time) final;
    void beginScanningForward() final;
    void beginScanningBackward() final;
    void endScanning() final;
    void selectAudioMediaOption(uint64_t) final;
    void selectLegibleMediaOption(uint64_t) final;

    double duration() const final { return m_duration; }
    double currentTime() const final { return m_currentTime; }
    double bufferedTime() const final { return m_bufferedTime; }
    bool isPlaying() const final { return m_isPlaying; }
    float playbackRate() const final { return m_playbackRate; }
    Ref<WebCore::TimeRanges> seekableRanges() const final { return m_seekableRanges.copyRef(); }
    bool canPlayFastReverse() const final { return m_canPlayFastReverse; }
    Vector<WTF::String> audioMediaSelectionOptions() const final { return m_audioMediaSelectionOptions; }
    uint64_t audioMediaSelectedIndex() const final { return m_audioMediaSelectedIndex; }
    Vector<WTF::String> legibleMediaSelectionOptions() const final { return m_legibleMediaSelectionOptions; }
    uint64_t legibleMediaSelectedIndex() const final { return m_legibleMediaSelectedIndex; }
    bool externalPlaybackEnabled() const final { return m_externalPlaybackEnabled; }
    bool wirelessVideoPlaybackDisabled() const final { return m_wirelessVideoPlaybackDisabled; }

    WebVideoFullscreenControllerContext* m_controller;
    double m_duration { 0 };
    double m_currentTime { 0 };
    double m_bufferedTime { 0 };
    bool m_isPlaying { false };
    float m_playbackRate { 0 };
    Ref<WebCore::TimeRanges> m_seekableRanges { WebCore::TimeRanges::create() };
    bool m_canPlayFastReverse { false };
    Vector<WTF::String> m_audioMediaSelectionOptions;
    uint64_t m_audioMediaSelectedIndex { 0 };
    Vector<WTF::String> m_legibleMediaSelectionOptions;
    uint64_t m_legibleMediaSelectedIndex { 0 };
    bool m_externalPlaybackEnabled { false };
    bool m_wirelessVideoPlaybackDisabled { false };
};

#pragma mark WebVideoFullscreenChangeObserver

void WebVideoFullscreenControllerContext::didSetupFullscreen()
{
    ASSERT(isUIThread());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    RetainPtr<CALayer> videoFullscreenLayer = [m_videoFullscreenView layer];
    WebThreadRun([protectedThis, this, videoFullscreenLayer] {
        [videoFullscreenLayer setBackgroundColor:cachedCGColor(WebCore::Color::transparent)];
        m_model->setVideoFullscreenLayer(videoFullscreenLayer.get(), [protectedThis, this] {
            dispatch_async(dispatch_get_main_queue(), [protectedThis, this] {
                m_interface->enterFullscreen();
            });
        });
    });
}

void WebVideoFullscreenControllerContext::didExitFullscreen()
{
    ASSERT(isUIThread());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    WebThreadRun([protectedThis, this] {
        m_model->setVideoFullscreenLayer(nil, [protectedThis, this] {
            dispatch_async(dispatch_get_main_queue(), [protectedThis, this] {
                m_interface->cleanupFullscreen();
            });
        });
    });
}

void WebVideoFullscreenControllerContext::didCleanupFullscreen()
{
    ASSERT(isUIThread());
    m_interface->setWebVideoFullscreenModel(nullptr);
    m_interface->setWebVideoFullscreenChangeObserver(nullptr);
    m_interface = nullptr;
    m_videoFullscreenView = nil;
    m_sessionModel->invalidate();
    m_sessionModel = nullptr;

    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    WebThreadRun([protectedThis, this] {
        m_model->setVideoFullscreenLayer(nil);
        m_model->setWebVideoFullscreenInterface(nullptr);
        m_model->setVideoElement(nullptr);
        m_model->playbackSessionModel().setMediaElement(nullptr);
        m_model = nullptr;
        m_videoElement = nullptr;

        [m_controller didFinishFullscreen:this];
    });
}

void WebVideoFullscreenControllerContext::fullscreenMayReturnToInline()
{
    ASSERT(isUIThread());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    WebThreadRun([protectedThis, this] {
        IntRect clientRect = elementRectInWindow(m_videoElement.get());
        dispatch_async(dispatch_get_main_queue(), [protectedThis, this, clientRect] {
            m_interface->preparedToReturnToInline(true, clientRect);
        });
    });
}

#pragma mark WebVideoFullscreenInterface

void WebVideoFullscreenControllerContext::resetMediaState()
{
    ASSERT(WebThreadIsCurrent() || isMainThread());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    dispatch_async(dispatch_get_main_queue(), [protectedThis, this] {
        if (m_interface)
            m_interface->resetMediaState();
    });
}

void WebVideoFullscreenControllerContext::setDuration(double duration)
{
    ASSERT(WebThreadIsCurrent());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    dispatch_async(dispatch_get_main_queue(), [protectedThis, this, duration] {
        if (m_interface)
            m_interface->setDuration(duration);
    });
}

void WebVideoFullscreenControllerContext::setCurrentTime(double currentTime, double anchorTime)
{
    ASSERT(WebThreadIsCurrent());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    dispatch_async(dispatch_get_main_queue(), [protectedThis, this, currentTime, anchorTime] {
        if (m_interface)
            m_interface->setCurrentTime(currentTime, anchorTime);
    });
}

void WebVideoFullscreenControllerContext::setBufferedTime(double bufferedTime)
{
    ASSERT(WebThreadIsCurrent());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    dispatch_async(dispatch_get_main_queue(), [protectedThis, this, bufferedTime] {
        if (m_interface)
            m_interface->setBufferedTime(bufferedTime);
    });
}

void WebVideoFullscreenControllerContext::setRate(bool isPlaying, float playbackRate)
{
    ASSERT(WebThreadIsCurrent());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    dispatch_async(dispatch_get_main_queue(), [protectedThis, this, isPlaying, playbackRate] {
        if (m_interface)
            m_interface->setRate(isPlaying, playbackRate);
    });
}

void WebVideoFullscreenControllerContext::setVideoDimensions(bool hasVideo, float width, float height)
{
    if (WebThreadIsCurrent()) {
        RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
        dispatch_async(dispatch_get_main_queue(), [protectedThis, this, hasVideo, width, height] {
            if (m_interface)
                m_interface->setVideoDimensions(hasVideo, width, height);
        });
        return;
    }

    if (m_interface)
        m_interface->setVideoDimensions(hasVideo, width, height);
}

void WebVideoFullscreenControllerContext::setSeekableRanges(const TimeRanges& timeRanges)
{
    ASSERT(WebThreadIsCurrent());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    const PlatformTimeRanges& platformTimeRanges = timeRanges.ranges();
    dispatch_async(dispatch_get_main_queue(), [protectedThis, this, platformTimeRanges] {
        if (m_interface)
            m_interface->setSeekableRanges(TimeRanges::create(platformTimeRanges));
    });
}

void WebVideoFullscreenControllerContext::setCanPlayFastReverse(bool canPlayFastReverse)
{
    ASSERT(WebThreadIsCurrent());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    dispatch_async(dispatch_get_main_queue(), [protectedThis, this, canPlayFastReverse] {
        if (m_interface)
            m_interface->setCanPlayFastReverse(canPlayFastReverse);
    });
}

void WebVideoFullscreenControllerContext::setAudioMediaSelectionOptions(const Vector<String>& options, uint64_t selectedIndex)
{
    ASSERT(WebThreadIsCurrent());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);

    RetainPtr<NSMutableArray> optionsArray = adoptNS([[NSMutableArray alloc] initWithCapacity:options.size()]);
    for (auto& name : options)
        [optionsArray addObject:name];
    
    dispatch_async(dispatch_get_main_queue(), [protectedThis, this, optionsArray, selectedIndex] {
        Vector<String> options;
        for (NSString *name : optionsArray.get())
            options.append(name);
        
        if (m_interface)
            m_interface->setAudioMediaSelectionOptions(options, selectedIndex);
    });
}

void WebVideoFullscreenControllerContext::setLegibleMediaSelectionOptions(const Vector<String>& options, uint64_t selectedIndex)
{
    ASSERT(WebThreadIsCurrent());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    
    RetainPtr<NSMutableArray> optionsArray = adoptNS([[NSMutableArray alloc] initWithCapacity:options.size()]);
    for (auto& name : options)
        [optionsArray addObject:name];
    
    dispatch_async(dispatch_get_main_queue(), [protectedThis, this, optionsArray, selectedIndex] {
        Vector<String> options;
        for (NSString *name : optionsArray.get())
            options.append(name);
        
        if (m_interface)
            m_interface->setLegibleMediaSelectionOptions(options, selectedIndex);
    });
}

void WebVideoFullscreenControllerContext::setExternalPlayback(bool enabled, ExternalPlaybackTargetType type, String localizedDeviceName)
{
    ASSERT(WebThreadIsCurrent());
    callOnMainThread([protectedThis = makeRef(*this), this, enabled, type, localizedDeviceName = localizedDeviceName.isolatedCopy()] {
        if (m_interface)
            m_interface->setExternalPlayback(enabled, type, localizedDeviceName);
    });
}

void WebVideoFullscreenControllerContext::setWirelessVideoPlaybackDisabled(bool disabled)
{
    ASSERT(WebThreadIsCurrent());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    dispatch_async(dispatch_get_main_queue(), [protectedThis, this, disabled] {
        if (m_interface)
            m_interface->setWirelessVideoPlaybackDisabled(disabled);
    });
}

#pragma mark WebVideoFullscreenModel

void WebVideoFullscreenControllerContext::requestFullscreenMode(HTMLMediaElementEnums::VideoFullscreenMode mode)
{
    ASSERT(isUIThread());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    WebThreadRun([protectedThis, this, mode] {
        if (m_model)
            m_model->requestFullscreenMode(mode);
    });
}

void WebVideoFullscreenControllerContext::setVideoLayerFrame(FloatRect frame)
{
    ASSERT(isUIThread());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    RetainPtr<CALayer> videoFullscreenLayer = [m_videoFullscreenView layer];
    
    [videoFullscreenLayer setSublayerTransform:[videoFullscreenLayer transform]];

    dispatch_async(dispatch_get_main_queue(), ^{
        WebThreadRun([protectedThis, this, frame, videoFullscreenLayer] {
            [CATransaction begin];
            [CATransaction setDisableActions:YES];
            [CATransaction setAnimationDuration:0];
            
            [videoFullscreenLayer setSublayerTransform:CATransform3DIdentity];
            
            if (m_model)
                m_model->setVideoLayerFrame(frame);
            [CATransaction commit];
        });
    });
}

void WebVideoFullscreenControllerContext::setVideoLayerGravity(WebVideoFullscreenModel::VideoGravity videoGravity)
{
    ASSERT(isUIThread());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    WebThreadRun([protectedThis, this, videoGravity] {
        if (m_model)
            m_model->setVideoLayerGravity(videoGravity);
    });
}

void WebVideoFullscreenControllerContext::fullscreenModeChanged(HTMLMediaElementEnums::VideoFullscreenMode mode)
{
    ASSERT(isUIThread());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    WebThreadRun([protectedThis, this, mode] {
        if (m_model)
            m_model->fullscreenModeChanged(mode);
    });
}

bool WebVideoFullscreenControllerContext::isVisible() const
{
    ASSERT(isUIThread());
    return m_model ? m_model->isVisible() : false;
}

#pragma mark - WebPlaybackSessionModelContext

void WebVideoFullscreenControllerContext::play()
{
    ASSERT(isUIThread());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    WebThreadRun([protectedThis, this] {
        if (m_model)
            m_model->playbackSessionModel().play();
    });
}

void WebVideoFullscreenControllerContext::pause()
{
    ASSERT(isUIThread());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    WebThreadRun([protectedThis, this] {
        if (m_model)
            m_model->playbackSessionModel().pause();
    });
}

void WebVideoFullscreenControllerContext::togglePlayState()
{
    ASSERT(isUIThread());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    WebThreadRun([protectedThis, this] {
        if (m_model)
            m_model->playbackSessionModel().togglePlayState();
    });
}

void WebVideoFullscreenControllerContext::beginScrubbing()
{
    ASSERT(isUIThread());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    WebThreadRun([protectedThis, this] {
        if (m_model)
            m_model->playbackSessionModel().beginScrubbing();
    });
}

void WebVideoFullscreenControllerContext::endScrubbing()
{
    ASSERT(isUIThread());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    WebThreadRun([protectedThis, this] {
        if (m_model)
            m_model->playbackSessionModel().endScrubbing();
    });
}

void WebVideoFullscreenControllerContext::seekToTime(double time)
{
    ASSERT(isUIThread());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    WebThreadRun([protectedThis, this, time] {
        if (m_model)
            m_model->playbackSessionModel().seekToTime(time);
    });
}

void WebVideoFullscreenControllerContext::fastSeek(double time)
{
    ASSERT(isUIThread());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    WebThreadRun([protectedThis, this, time] {
        if (m_model)
            m_model->playbackSessionModel().fastSeek(time);
    });
}

void WebVideoFullscreenControllerContext::beginScanningForward()
{
    ASSERT(isUIThread());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    WebThreadRun([protectedThis, this] {
        if (m_model)
            m_model->playbackSessionModel().beginScanningForward();
    });
}

void WebVideoFullscreenControllerContext::beginScanningBackward()
{
    ASSERT(isUIThread());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    WebThreadRun([protectedThis, this] {
        if (m_model)
            m_model->playbackSessionModel().beginScanningBackward();
    });
}

void WebVideoFullscreenControllerContext::endScanning()
{
    ASSERT(isUIThread());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    WebThreadRun([protectedThis, this] {
        if (m_model)
            m_model->playbackSessionModel().endScanning();
    });
}

void WebVideoFullscreenControllerContext::selectAudioMediaOption(uint64_t index)
{
    ASSERT(isUIThread());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    WebThreadRun([protectedThis, this, index] {
        if (m_model)
            m_model->playbackSessionModel().selectAudioMediaOption(index);
    });
}

void WebVideoFullscreenControllerContext::selectLegibleMediaOption(uint64_t index)
{
    ASSERT(isUIThread());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    WebThreadRun([protectedThis, this, index] {
        if (m_model)
            m_model->playbackSessionModel().selectLegibleMediaOption(index);
    });
}

#pragma mark Other

void WebVideoFullscreenControllerContext::setUpFullscreen(HTMLVideoElement& videoElement, UIView *view, HTMLMediaElementEnums::VideoFullscreenMode mode)
{
    ASSERT(isMainThread());
    RetainPtr<UIView> viewRef = view;
    m_videoElement = &videoElement;

    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    dispatch_async(dispatch_get_main_queue(), [protectedThis, this, viewRef, mode] {
        ASSERT(isUIThread());

        Ref<WebPlaybackSessionInterfaceAVKit> sessionInterface = WebPlaybackSessionInterfaceAVKit::create();
        m_interface = WebVideoFullscreenInterfaceAVKit::create(sessionInterface.get());

        m_sessionModel = WebVideoFullscreenSessionModel::create(*this);
        sessionInterface->setWebPlaybackSessionModel(m_sessionModel.get());

        m_interface->setWebVideoFullscreenModel(this);
        m_interface->setWebVideoFullscreenChangeObserver(this);

        m_videoFullscreenView = adoptNS([[getUIViewClass() alloc] init]);
        
        RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
        WebThreadRun([protectedThis, this, viewRef, mode] {
            m_model = WebVideoFullscreenModelVideoElement::create(WebPlaybackSessionModelMediaElement::create().get());
            m_model->setWebVideoFullscreenInterface(this);
            m_model->playbackSessionModel().setMediaElement(m_videoElement.get());
            m_model->setVideoElement(m_videoElement.get());

            IntRect videoElementClientRect = elementRectInWindow(m_videoElement.get());
            FloatRect videoLayerFrame = FloatRect(FloatPoint(), videoElementClientRect.size());
            m_model->setVideoLayerFrame(videoLayerFrame);
            
            bool allowsPictureInPicture = m_videoElement->mediaSession().allowsPictureInPicture(*m_videoElement.get());
            dispatch_async(dispatch_get_main_queue(), [protectedThis, this, videoElementClientRect, viewRef, mode, allowsPictureInPicture] {
                m_interface->setupFullscreen(*m_videoFullscreenView.get(), videoElementClientRect, viewRef.get(), mode, allowsPictureInPicture);
            });
        });
    });
}

void WebVideoFullscreenControllerContext::exitFullscreen()
{
    ASSERT(WebThreadIsCurrent() || isMainThread());
    IntRect clientRect = elementRectInWindow(m_videoElement.get());
    RefPtr<WebVideoFullscreenControllerContext> protectedThis(this);
    dispatch_async(dispatch_get_main_queue(), [protectedThis, this, clientRect] {
        ASSERT(isUIThread());
        m_interface->exitFullscreen(clientRect);
    });
}

void WebVideoFullscreenControllerContext::requestHideAndExitFullscreen()
{
    ASSERT(isUIThread());
    m_interface->requestHideAndExitFullscreen();
}

#pragma mark WebPlaybackSessionModel

void WebVideoFullscreenSessionModel::play()
{
    if (m_controller)
        m_controller->play();
}

void WebVideoFullscreenSessionModel::pause()
{
    if (m_controller)
        m_controller->pause();
}

void WebVideoFullscreenSessionModel::togglePlayState()
{
    if (m_controller)
        m_controller->togglePlayState();
}

void WebVideoFullscreenSessionModel::beginScrubbing()
{
    if (m_controller)
        m_controller->beginScrubbing();
}

void WebVideoFullscreenSessionModel::endScrubbing()
{
    if (m_controller)
        m_controller->endScrubbing();
}

void WebVideoFullscreenSessionModel::seekToTime(double time)
{
    if (m_controller)
        m_controller->seekToTime(time);
}

void WebVideoFullscreenSessionModel::fastSeek(double time)
{
    if (m_controller)
        m_controller->fastSeek(time);
}

void WebVideoFullscreenSessionModel::beginScanningForward()
{
    if (m_controller)
        m_controller->beginScanningForward();
}

void WebVideoFullscreenSessionModel::beginScanningBackward()
{
    if (m_controller)
        m_controller->beginScanningBackward();
}

void WebVideoFullscreenSessionModel::endScanning()
{
    if (m_controller)
        m_controller->endScanning();
}

void WebVideoFullscreenSessionModel::selectAudioMediaOption(uint64_t optionId)
{
    if (m_controller)
        m_controller->selectAudioMediaOption(optionId);
}

void WebVideoFullscreenSessionModel::selectLegibleMediaOption(uint64_t optionId)
{
    if (m_controller)
        m_controller->selectLegibleMediaOption(optionId);
}


@implementation WebVideoFullscreenController {
    RefPtr<WebVideoFullscreenControllerContext> _context;
    RefPtr<HTMLVideoElement> _videoElement;
}

- (instancetype)init
{
    if (!(self = [super init]))
        return nil;
    
    return self;
}

- (void)setVideoElement:(HTMLVideoElement*)videoElement
{
    _videoElement = videoElement;
}

- (HTMLVideoElement*)videoElement
{
    return _videoElement.get();
}

- (void)enterFullscreen:(UIView *)view mode:(HTMLMediaElementEnums::VideoFullscreenMode)mode
{
    ASSERT(isMainThread());
    _context = WebVideoFullscreenControllerContext::create();
    _context->setController(self);
    _context->setUpFullscreen(*_videoElement.get(), view, mode);
}

- (void)exitFullscreen
{
    ASSERT(WebThreadIsCurrent() || isMainThread());
    _context->exitFullscreen();
}

- (void)requestHideAndExitFullscreen
{
    ASSERT(isUIThread());
    if (_context)
        _context->requestHideAndExitFullscreen();
}

- (void)didFinishFullscreen:(WebVideoFullscreenControllerContext*)context
{
    ASSERT(WebThreadIsCurrent());
    ASSERT_UNUSED(context, context == _context);
    [[self retain] autorelease]; // retain self before breaking a retain cycle.
    _context->setController(nil);
    _context = nullptr;
    _videoElement = nullptr;
}

@end

#endif // !HAVE(AVKIT)

#endif // PLATFORM(IOS)