2 * Copyright (C) 2013 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 #import "WebVideoFullscreenControllerAVKit.h"
33 #import "QuartzCoreSPI.h"
34 #import "SoftLinking.h"
35 #import "TimeRanges.h"
36 #import "WebVideoFullscreenInterfaceAVKit.h"
37 #import "WebVideoFullscreenModelVideoElement.h"
38 #import <QuartzCore/CoreAnimation.h>
39 #import <WebCore/FrameView.h>
40 #import <WebCore/HTMLVideoElement.h>
41 #import <WebCore/RenderElement.h>
42 #import <WebCore/WebCoreThreadRun.h>
44 SOFT_LINK_FRAMEWORK(UIKit)
45 SOFT_LINK_CLASS(UIKit, UIView)
47 using namespace WebCore;
49 #if __IPHONE_OS_VERSION_MIN_REQUIRED <= 80200 || !HAVE(AVKIT)
51 @implementation WebVideoFullscreenController
52 - (void)setVideoElement:(WebCore::HTMLVideoElement*)videoElement
54 UNUSED_PARAM(videoElement);
57 - (WebCore::HTMLVideoElement*)videoElement
62 - (void)enterFullscreen:(UIView *)view mode:(WebCore::HTMLMediaElementEnums::VideoFullscreenMode)mode
68 - (void)requestHideAndExitFullscreen
72 - (void)exitFullscreen
79 static IntRect elementRectInWindow(HTMLVideoElement* videoElement)
81 if (!videoElement || !videoElement->renderer() || !videoElement->document().view())
84 return videoElement->document().view()->convertToContainingWindow(videoElement->renderer()->absoluteBoundingBoxRect());
87 class WebVideoFullscreenControllerContext;
89 @interface WebVideoFullscreenController (delegate)
90 -(void)didFinishFullscreen:(WebVideoFullscreenControllerContext*)context;
93 class WebVideoFullscreenControllerContext final
94 : private WebVideoFullscreenInterface
95 , private WebVideoFullscreenModel
96 , private WebVideoFullscreenChangeObserver
97 , public ThreadSafeRefCounted<WebVideoFullscreenControllerContext> {
100 static Ref<WebVideoFullscreenControllerContext> create()
102 return adoptRef(*new WebVideoFullscreenControllerContext);
105 void setController(WebVideoFullscreenController* controller) { m_controller = controller; }
106 void setUpFullscreen(HTMLVideoElement&, UIView *, HTMLMediaElementEnums::VideoFullscreenMode);
107 void exitFullscreen();
108 void requestHideAndExitFullscreen();
112 WebVideoFullscreenControllerContext() { }
114 // WebVideoFullscreenChangeObserver
115 virtual void didSetupFullscreen() override;
116 virtual void didEnterFullscreen() override { }
117 virtual void didExitFullscreen() override;
118 virtual void didCleanupFullscreen() override;
119 virtual void fullscreenMayReturnToInline() override;
121 // WebVideoFullscreenInterface
122 virtual void resetMediaState() override;
123 virtual void setDuration(double) override;
124 virtual void setCurrentTime(double currentTime, double anchorTime) override;
125 virtual void setBufferedTime(double) override;
126 virtual void setRate(bool isPlaying, float playbackRate) override;
127 virtual void setVideoDimensions(bool hasVideo, float width, float height) override;
128 virtual void setSeekableRanges(const TimeRanges&) override;
129 virtual void setCanPlayFastReverse(bool) override;
130 virtual void setAudioMediaSelectionOptions(const Vector<String>& options, uint64_t selectedIndex) override;
131 virtual void setLegibleMediaSelectionOptions(const Vector<String>& options, uint64_t selectedIndex) override;
132 virtual void setExternalPlayback(bool enabled, ExternalPlaybackTargetType, String localizedDeviceName) override;
134 // WebVideoFullscreenModel
135 virtual void play() override;
136 virtual void pause() override;
137 virtual void togglePlayState() override;
138 virtual void beginScrubbing() override;
139 virtual void endScrubbing() override;
140 virtual void seekToTime(double time) override;
141 virtual void fastSeek(double time) override;
142 virtual void beginScanningForward() override;
143 virtual void beginScanningBackward() override;
144 virtual void endScanning() override;
145 virtual void requestExitFullscreen() override;
146 virtual void setVideoLayerFrame(FloatRect) override;
147 virtual void setVideoLayerGravity(WebVideoFullscreenModel::VideoGravity) override;
148 virtual void selectAudioMediaOption(uint64_t index) override;
149 virtual void selectLegibleMediaOption(uint64_t index) override;
150 virtual void fullscreenModeChanged(HTMLMediaElementEnums::VideoFullscreenMode) override;
152 RefPtr<WebVideoFullscreenInterfaceAVKit> m_interface;
153 RefPtr<WebVideoFullscreenModelVideoElement> m_model;
154 RefPtr<HTMLVideoElement> m_videoElement;
155 RetainPtr<UIView> m_videoFullscreenView;
156 RetainPtr<WebVideoFullscreenController> m_controller;
159 #pragma mark WebVideoFullscreenChangeObserver
161 void WebVideoFullscreenControllerContext::didSetupFullscreen()
163 ASSERT(isUIThread());
164 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
165 RetainPtr<CALayer> videoFullscreenLayer = [m_videoFullscreenView layer];
166 WebThreadRun([strongThis, this, videoFullscreenLayer] {
167 [videoFullscreenLayer setBackgroundColor:cachedCGColor(WebCore::Color::transparent, WebCore::ColorSpaceDeviceRGB)];
168 m_model->setVideoFullscreenLayer(videoFullscreenLayer.get());
169 dispatch_async(dispatch_get_main_queue(), [strongThis, this] {
170 m_interface->enterFullscreen();
175 void WebVideoFullscreenControllerContext::didExitFullscreen()
177 ASSERT(isUIThread());
178 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
179 WebThreadRun([strongThis, this] {
180 m_model->setVideoFullscreenLayer(nil);
181 dispatch_async(dispatch_get_main_queue(), [strongThis, this] {
182 m_interface->cleanupFullscreen();
187 void WebVideoFullscreenControllerContext::didCleanupFullscreen()
189 ASSERT(isUIThread());
190 m_interface->setWebVideoFullscreenModel(nullptr);
191 m_interface->setWebVideoFullscreenChangeObserver(nullptr);
192 m_interface = nullptr;
193 m_videoFullscreenView = nil;
195 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
196 WebThreadRun([strongThis, this] {
197 m_model->setVideoFullscreenLayer(nil);
198 m_model->setWebVideoFullscreenInterface(nullptr);
199 m_model->setVideoElement(nullptr);
201 m_videoElement = nullptr;
203 [m_controller didFinishFullscreen:this];
207 void WebVideoFullscreenControllerContext::fullscreenMayReturnToInline()
209 ASSERT(isUIThread());
210 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
211 WebThreadRun([strongThis, this] {
212 IntRect clientRect = elementRectInWindow(m_videoElement.get());
213 dispatch_async(dispatch_get_main_queue(), [strongThis, this, clientRect] {
214 m_interface->preparedToReturnToInline(true, clientRect);
219 #pragma mark WebVideoFullscreenInterface
221 void WebVideoFullscreenControllerContext::resetMediaState()
223 ASSERT(WebThreadIsCurrent() || isMainThread());
224 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
225 dispatch_async(dispatch_get_main_queue(), [strongThis, this] {
227 m_interface->resetMediaState();
231 void WebVideoFullscreenControllerContext::setDuration(double duration)
233 ASSERT(WebThreadIsCurrent());
234 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
235 dispatch_async(dispatch_get_main_queue(), [strongThis, this, duration] {
237 m_interface->setDuration(duration);
241 void WebVideoFullscreenControllerContext::setCurrentTime(double currentTime, double anchorTime)
243 ASSERT(WebThreadIsCurrent());
244 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
245 dispatch_async(dispatch_get_main_queue(), [strongThis, this, currentTime, anchorTime] {
247 m_interface->setCurrentTime(currentTime, anchorTime);
251 void WebVideoFullscreenControllerContext::setBufferedTime(double bufferedTime)
253 ASSERT(WebThreadIsCurrent());
254 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
255 dispatch_async(dispatch_get_main_queue(), [strongThis, this, bufferedTime] {
257 m_interface->setBufferedTime(bufferedTime);
261 void WebVideoFullscreenControllerContext::setRate(bool isPlaying, float playbackRate)
263 ASSERT(WebThreadIsCurrent());
264 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
265 dispatch_async(dispatch_get_main_queue(), [strongThis, this, isPlaying, playbackRate] {
267 m_interface->setRate(isPlaying, playbackRate);
271 void WebVideoFullscreenControllerContext::setVideoDimensions(bool hasVideo, float width, float height)
273 ASSERT(WebThreadIsCurrent());
274 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
275 dispatch_async(dispatch_get_main_queue(), [strongThis, this, hasVideo, width, height] {
277 m_interface->setVideoDimensions(hasVideo, width, height);
281 void WebVideoFullscreenControllerContext::setSeekableRanges(const TimeRanges& timeRanges)
283 ASSERT(WebThreadIsCurrent());
284 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
285 const PlatformTimeRanges& platformTimeRanges = timeRanges.ranges();
286 dispatch_async(dispatch_get_main_queue(), [strongThis, this, platformTimeRanges] {
288 m_interface->setSeekableRanges(TimeRanges::create(platformTimeRanges));
292 void WebVideoFullscreenControllerContext::setCanPlayFastReverse(bool canPlayFastReverse)
294 ASSERT(WebThreadIsCurrent());
295 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
296 dispatch_async(dispatch_get_main_queue(), [strongThis, this, canPlayFastReverse] {
298 m_interface->setCanPlayFastReverse(canPlayFastReverse);
302 void WebVideoFullscreenControllerContext::setAudioMediaSelectionOptions(const Vector<String>& options, uint64_t selectedIndex)
304 ASSERT(WebThreadIsCurrent());
305 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
307 RetainPtr<NSMutableArray> optionsArray = adoptNS([[NSMutableArray alloc] initWithCapacity:options.size()]);
308 for (auto& name : options)
309 [optionsArray addObject:name];
311 dispatch_async(dispatch_get_main_queue(), [strongThis, this, optionsArray, selectedIndex] {
312 Vector<String> options;
313 for (NSString *name : optionsArray.get())
314 options.append(name);
317 m_interface->setAudioMediaSelectionOptions(options, selectedIndex);
321 void WebVideoFullscreenControllerContext::setLegibleMediaSelectionOptions(const Vector<String>& options, uint64_t selectedIndex)
323 ASSERT(WebThreadIsCurrent());
324 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
326 RetainPtr<NSMutableArray> optionsArray = adoptNS([[NSMutableArray alloc] initWithCapacity:options.size()]);
327 for (auto& name : options)
328 [optionsArray addObject:name];
330 dispatch_async(dispatch_get_main_queue(), [strongThis, this, optionsArray, selectedIndex] {
331 Vector<String> options;
332 for (NSString *name : optionsArray.get())
333 options.append(name);
336 m_interface->setLegibleMediaSelectionOptions(options, selectedIndex);
340 void WebVideoFullscreenControllerContext::setExternalPlayback(bool enabled, ExternalPlaybackTargetType type, String localizedDeviceName)
342 ASSERT(WebThreadIsCurrent());
343 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
344 StringCapture capturedLocalizedDeviceName(localizedDeviceName);
345 dispatch_async(dispatch_get_main_queue(), [strongThis, this, enabled, type, capturedLocalizedDeviceName] {
347 m_interface->setExternalPlayback(enabled, type, capturedLocalizedDeviceName.string());
351 #pragma mark WebVideoFullscreenModel
353 void WebVideoFullscreenControllerContext::play()
355 ASSERT(isUIThread());
356 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
357 WebThreadRun([strongThis, this] {
363 void WebVideoFullscreenControllerContext::pause()
365 ASSERT(isUIThread());
366 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
367 WebThreadRun([strongThis, this] {
373 void WebVideoFullscreenControllerContext::togglePlayState()
375 ASSERT(isUIThread());
376 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
377 WebThreadRun([strongThis, this] {
379 m_model->togglePlayState();
383 void WebVideoFullscreenControllerContext::beginScrubbing()
385 ASSERT(isUIThread());
386 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
387 WebThreadRun([strongThis, this] {
389 m_model->beginScrubbing();
393 void WebVideoFullscreenControllerContext::endScrubbing()
395 ASSERT(isUIThread());
396 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
397 WebThreadRun([strongThis, this] {
399 m_model->endScrubbing();
403 void WebVideoFullscreenControllerContext::seekToTime(double time)
405 ASSERT(isUIThread());
406 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
407 WebThreadRun([strongThis, this, time] {
409 m_model->seekToTime(time);
413 void WebVideoFullscreenControllerContext::fastSeek(double time)
415 ASSERT(isUIThread());
416 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
417 WebThreadRun([strongThis, this, time] {
419 m_model->fastSeek(time);
423 void WebVideoFullscreenControllerContext::beginScanningForward()
425 ASSERT(isUIThread());
426 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
427 WebThreadRun([strongThis, this] {
429 m_model->beginScanningForward();
433 void WebVideoFullscreenControllerContext::beginScanningBackward()
435 ASSERT(isUIThread());
436 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
437 WebThreadRun([strongThis, this] {
439 m_model->beginScanningBackward();
443 void WebVideoFullscreenControllerContext::endScanning()
445 ASSERT(isUIThread());
446 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
447 WebThreadRun([strongThis, this] {
449 m_model->endScanning();
453 void WebVideoFullscreenControllerContext::requestExitFullscreen()
455 ASSERT(isUIThread());
456 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
457 WebThreadRun([strongThis, this] {
459 m_model->requestExitFullscreen();
463 void WebVideoFullscreenControllerContext::setVideoLayerFrame(FloatRect frame)
465 ASSERT(isUIThread());
466 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
467 RetainPtr<CALayer> videoFullscreenLayer = [m_videoFullscreenView layer];
469 [videoFullscreenLayer setSublayerTransform:[videoFullscreenLayer transform]];
471 dispatch_async(dispatch_get_main_queue(), ^{
472 WebThreadRun([strongThis, this, frame, videoFullscreenLayer] {
473 [CATransaction begin];
474 [CATransaction setDisableActions:YES];
475 [CATransaction setAnimationDuration:0];
477 [videoFullscreenLayer setSublayerTransform:CATransform3DIdentity];
480 m_model->setVideoLayerFrame(frame);
481 [CATransaction commit];
486 void WebVideoFullscreenControllerContext::setVideoLayerGravity(WebVideoFullscreenModel::VideoGravity videoGravity)
488 ASSERT(isUIThread());
489 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
490 WebThreadRun([strongThis, this, videoGravity] {
492 m_model->setVideoLayerGravity(videoGravity);
496 void WebVideoFullscreenControllerContext::selectAudioMediaOption(uint64_t index)
498 ASSERT(isUIThread());
499 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
500 WebThreadRun([strongThis, this, index] {
502 m_model->selectAudioMediaOption(index);
506 void WebVideoFullscreenControllerContext::selectLegibleMediaOption(uint64_t index)
508 ASSERT(isUIThread());
509 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
510 WebThreadRun([strongThis, this, index] {
512 m_model->selectLegibleMediaOption(index);
516 void WebVideoFullscreenControllerContext::fullscreenModeChanged(HTMLMediaElementEnums::VideoFullscreenMode mode)
518 ASSERT(isUIThread());
519 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
520 WebThreadRun([strongThis, this, mode] {
522 m_model->fullscreenModeChanged(mode);
528 void WebVideoFullscreenControllerContext::setUpFullscreen(HTMLVideoElement& videoElement, UIView *view, HTMLMediaElementEnums::VideoFullscreenMode mode)
530 ASSERT(isMainThread());
531 RetainPtr<UIView> viewRef = view;
532 m_videoElement = &videoElement;
534 m_interface = WebVideoFullscreenInterfaceAVKit::create();
535 m_interface->setWebVideoFullscreenChangeObserver(this);
536 m_interface->setWebVideoFullscreenModel(this);
537 m_videoFullscreenView = adoptNS([[getUIViewClass() alloc] init]);
539 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
540 WebThreadRun([strongThis, this, viewRef, mode] {
541 m_model = WebVideoFullscreenModelVideoElement::create();
542 m_model->setWebVideoFullscreenInterface(this);
543 m_model->setVideoElement(m_videoElement.get());
545 bool allowsPictureInPicture = m_videoElement->mediaSession().allowsPictureInPicture(*m_videoElement.get());
547 IntRect videoElementClientRect = elementRectInWindow(m_videoElement.get());
548 FloatRect videoLayerFrame = FloatRect(FloatPoint(), videoElementClientRect.size());
549 m_model->setVideoLayerFrame(videoLayerFrame);
551 dispatch_async(dispatch_get_main_queue(), [strongThis, this, videoElementClientRect, viewRef, mode, allowsPictureInPicture] {
552 m_interface->setupFullscreen(*m_videoFullscreenView.get(), videoElementClientRect, viewRef.get(), mode, allowsPictureInPicture);
557 void WebVideoFullscreenControllerContext::exitFullscreen()
559 ASSERT(WebThreadIsCurrent() || isMainThread());
560 IntRect clientRect = elementRectInWindow(m_videoElement.get());
561 RefPtr<WebVideoFullscreenControllerContext> strongThis(this);
562 dispatch_async(dispatch_get_main_queue(), [strongThis, this, clientRect] {
563 ASSERT(isUIThread());
564 m_interface->exitFullscreen(clientRect);
568 void WebVideoFullscreenControllerContext::requestHideAndExitFullscreen()
570 ASSERT(isUIThread());
571 m_interface->requestHideAndExitFullscreen();
574 @implementation WebVideoFullscreenController {
575 RefPtr<WebVideoFullscreenControllerContext> _context;
576 RefPtr<HTMLVideoElement> _videoElement;
581 if (!(self = [super init]))
587 - (void)setVideoElement:(HTMLVideoElement*)videoElement
589 _videoElement = videoElement;
592 - (HTMLVideoElement*)videoElement
594 return _videoElement.get();
597 - (void)enterFullscreen:(UIView *)view mode:(HTMLMediaElementEnums::VideoFullscreenMode)mode
599 ASSERT(isMainThread());
600 _context = WebVideoFullscreenControllerContext::create();
601 _context->setController(self);
602 _context->setUpFullscreen(*_videoElement.get(), view, mode);
605 - (void)exitFullscreen
607 ASSERT(WebThreadIsCurrent() || isMainThread());
608 _context->exitFullscreen();
611 - (void)requestHideAndExitFullscreen
613 ASSERT(isUIThread());
615 _context->requestHideAndExitFullscreen();
618 - (void)didFinishFullscreen:(WebVideoFullscreenControllerContext*)context
620 ASSERT(WebThreadIsCurrent());
621 ASSERT_UNUSED(context, context == _context);
622 [[self retain] autorelease]; // retain self before breaking a retain cycle.
623 _context->setController(nil);
625 _videoElement = nullptr;
630 #endif // __IPHONE_OS_VERSION_MIN_REQUIRED < 80000
632 #endif // PLATFORM(IOS)