1/*
2 * Copyright (C) 2014 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
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.
12 *
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.
24 */
25
26
27#import "config.h"
28
29#if PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000
30
31#import "WebVideoFullscreenInterfaceAVKit.h"
32
33#import "Logging.h"
34#import "GeometryUtilities.h"
35#import "WebVideoFullscreenModel.h"
36#import <AVFoundation/AVTime.h>
37#import <AVKit/AVKit.h>
38#import <AVKit/AVPlayerController.h>
39#import <AVKit/AVPlayerViewController_Private.h>
40#import <AVKit/AVPlayerViewController_WebKitOnly.h>
41#import <AVKit/AVValueTiming.h>
42#import <AVKit/AVVideoLayer.h>
43#import <CoreMedia/CMTime.h>
44#import <UIKit/UIKit.h>
45#import <WebCore/RuntimeApplicationChecksIOS.h>
46#import <WebCore/SoftLinking.h>
47#import <WebCore/TimeRanges.h>
48#import <WebCore/WebCoreThreadRun.h>
49#import <wtf/RetainPtr.h>
50#import <wtf/text/CString.h>
51
52using namespace WebCore;
53
54SOFT_LINK_FRAMEWORK(AVFoundation)
55SOFT_LINK_CLASS(AVFoundation, AVPlayerLayer)
56
57SOFT_LINK_FRAMEWORK(AVKit)
58SOFT_LINK_CLASS(AVKit, AVPlayerController)
59SOFT_LINK_CLASS(AVKit, AVPlayerViewController)
60SOFT_LINK_CLASS(AVKit, AVValueTiming)
61
62SOFT_LINK_FRAMEWORK(UIKit)
63SOFT_LINK_CLASS(UIKit, UIApplication)
64SOFT_LINK_CLASS(UIKit, UIScreen)
65SOFT_LINK_CLASS(UIKit, UIWindow)
66SOFT_LINK_CLASS(UIKit, UIView)
67SOFT_LINK_CLASS(UIKit, UIViewController)
68SOFT_LINK_CLASS(UIKit, UIColor)
69
70SOFT_LINK_FRAMEWORK(CoreMedia)
71SOFT_LINK(CoreMedia, CMTimeMakeWithSeconds, CMTime, (Float64 seconds, int32_t preferredTimeScale), (seconds, preferredTimeScale))
72SOFT_LINK(CoreMedia, CMTimeGetSeconds, Float64, (CMTime time), (time))
73SOFT_LINK(CoreMedia, CMTimeMake, CMTime, (int64_t value, int32_t timescale), (value, timescale))
74SOFT_LINK(CoreMedia, CMTimeRangeContainsTime, Boolean, (CMTimeRange range, CMTime time), (range, time))
75SOFT_LINK(CoreMedia, CMTimeRangeGetEnd, CMTime, (CMTimeRange range), (range))
76SOFT_LINK(CoreMedia, CMTimeRangeMake, CMTimeRange, (CMTime start, CMTime duration), (start, duration))
77SOFT_LINK(CoreMedia, CMTimeSubtract, CMTime, (CMTime minuend, CMTime subtrahend), (minuend, subtrahend))
78SOFT_LINK(CoreMedia, CMTimeMaximum, CMTime, (CMTime time1, CMTime time2), (time1, time2))
79SOFT_LINK(CoreMedia, CMTimeMinimum, CMTime, (CMTime time1, CMTime time2), (time1, time2))
80SOFT_LINK_CONSTANT(CoreMedia, kCMTimeIndefinite, CMTime)
81
82#define kCMTimeIndefinite getkCMTimeIndefinite()
83
84@class WebAVMediaSelectionOption;
85
86@interface WebAVPlayerController : NSObject <AVPlayerViewControllerDelegate>
87{
88    WebAVMediaSelectionOption *_currentAudioMediaSelectionOption;
89    WebAVMediaSelectionOption *_currentLegibleMediaSelectionOption;
90}
91
92@property(retain) AVPlayerController* playerControllerProxy;
93@property(assign) WebVideoFullscreenModel* delegate;
94
95@property (readonly) BOOL canScanForward;
96@property BOOL canScanBackward;
97@property (readonly) BOOL canSeekToBeginning;
98@property (readonly) BOOL canSeekToEnd;
99
100@property BOOL canPlay;
101@property(getter=isPlaying) BOOL playing;
102@property BOOL canPause;
103@property BOOL canTogglePlayback;
104@property double rate;
105@property BOOL canSeek;
106@property NSTimeInterval contentDuration;
107@property NSSize contentDimensions;
108@property BOOL hasEnabledAudio;
109@property BOOL hasEnabledVideo;
110@property NSTimeInterval minTime;
111@property NSTimeInterval maxTime;
112@property NSTimeInterval contentDurationWithinEndTimes;
113@property(retain) NSArray *loadedTimeRanges;
114@property AVPlayerControllerStatus status;
115@property(retain) AVValueTiming *timing;
116@property(retain) NSArray *seekableTimeRanges;
117
118@property (readonly) BOOL hasMediaSelectionOptions;
119@property (readonly) BOOL hasAudioMediaSelectionOptions;
120@property (retain) NSArray *audioMediaSelectionOptions;
121@property (retain) WebAVMediaSelectionOption *currentAudioMediaSelectionOption;
122@property (readonly) BOOL hasLegibleMediaSelectionOptions;
123@property (retain) NSArray *legibleMediaSelectionOptions;
124@property (retain) WebAVMediaSelectionOption *currentLegibleMediaSelectionOption;
125
126@property (readonly, getter=isPlayingOnExternalScreen) BOOL playingOnExternalScreen;
127@property (getter=isExternalPlaybackActive) BOOL externalPlaybackActive;
128@property AVPlayerControllerExternalPlaybackType externalPlaybackType;
129@property (retain) NSString *externalPlaybackAirPlayDeviceLocalizedName;
130
131- (BOOL)playerViewController:(AVPlayerViewController *)playerViewController shouldExitFullScreenWithReason:(AVPlayerViewControllerExitFullScreenReason)reason;
132@end
133
134@implementation WebAVPlayerController
135
136- (instancetype)init
137{
138    if (!(self = [super init]))
139        return self;
140
141    initAVPlayerController();
142    self.playerControllerProxy = [[[getAVPlayerControllerClass() alloc] init] autorelease];
143    return self;
144}
145
146- (void)dealloc
147{
148    [_playerControllerProxy release];
149    [_loadedTimeRanges release];
150    [_seekableTimeRanges release];
151    [_timing release];
152    [_audioMediaSelectionOptions release];
153    [_legibleMediaSelectionOptions release];
154    [_currentAudioMediaSelectionOption release];
155    [_currentLegibleMediaSelectionOption release];
156    [super dealloc];
157}
158
159- (id)forwardingTargetForSelector:(SEL)selector
160{
161    UNUSED_PARAM(selector);
162    return self.playerControllerProxy;
163}
164
165- (BOOL)playerViewController:(AVPlayerViewController *)playerViewController shouldExitFullScreenWithReason:(AVPlayerViewControllerExitFullScreenReason)reason
166{
167    UNUSED_PARAM(playerViewController);
168    UNUSED_PARAM(reason);
169    ASSERT(self.delegate);
170    if (reason == AVPlayerViewControllerExitFullScreenReasonDoneButtonTapped || reason == AVPlayerViewControllerExitFullScreenReasonRemoteControlStopEventReceived)
171        self.delegate->pause();
172    self.delegate->requestExitFullscreen();
173    return NO;
174}
175
176- (void)play:(id)sender
177{
178    UNUSED_PARAM(sender);
179    ASSERT(self.delegate);
180    self.delegate->play();
181}
182
183- (void)pause:(id)sender
184{
185    UNUSED_PARAM(sender);
186    ASSERT(self.delegate);
187    self.delegate->pause();
188}
189
190- (void)togglePlayback:(id)sender
191{
192    UNUSED_PARAM(sender);
193    ASSERT(self.delegate);
194    self.delegate->togglePlayState();
195}
196
197- (BOOL)isPlaying
198{
199    return [self rate] != 0;
200}
201
202- (void)setPlaying:(BOOL)playing
203{
204    ASSERT(self.delegate);
205    if (playing)
206        self.delegate->play();
207    else
208        self.delegate->pause();
209    }
210
211+ (NSSet *)keyPathsForValuesAffectingPlaying
212{
213    return [NSSet setWithObject:@"rate"];
214}
215
216- (void)beginScrubbing:(id)sender
217{
218    UNUSED_PARAM(sender);
219    ASSERT(self.delegate);
220    self.delegate->beginScrubbing();
221}
222
223- (void)endScrubbing:(id)sender
224{
225    UNUSED_PARAM(sender);
226    ASSERT(self.delegate);
227    self.delegate->endScrubbing();
228}
229
230- (void)seekToTime:(NSTimeInterval)time
231{
232    ASSERT(self.delegate);
233    self.delegate->fastSeek(time);
234}
235
236- (BOOL)hasLiveStreamingContent
237{
238    if ([self status] == AVPlayerControllerStatusReadyToPlay)
239        return [self contentDuration] == std::numeric_limits<float>::infinity();
240    return NO;
241}
242
243+ (NSSet *)keyPathsForValuesAffectingHasLiveStreamingContent
244{
245    return [NSSet setWithObjects:@"contentDuration", @"status", nil];
246}
247
248- (void)skipBackwardThirtySeconds:(id)sender
249{
250    UNUSED_PARAM(sender);
251    BOOL isTimeWithinSeekableTimeRanges = NO;
252    CMTime currentTime = CMTimeMakeWithSeconds([[self timing] currentValue], 1000);
253    CMTime thirtySecondsBeforeCurrentTime = CMTimeSubtract(currentTime, CMTimeMake(30, 1));
254
255    for (NSValue *seekableTimeRangeValue in [self seekableTimeRanges]) {
256        if (CMTimeRangeContainsTime([seekableTimeRangeValue CMTimeRangeValue], thirtySecondsBeforeCurrentTime)) {
257            isTimeWithinSeekableTimeRanges = YES;
258            break;
259        }
260    }
261
262    if (isTimeWithinSeekableTimeRanges)
263        [self seekToTime:CMTimeGetSeconds(thirtySecondsBeforeCurrentTime)];
264}
265
266- (void)gotoEndOfSeekableRanges:(id)sender
267{
268    UNUSED_PARAM(sender);
269    NSTimeInterval timeAtEndOfSeekableTimeRanges = NAN;
270
271    for (NSValue *seekableTimeRangeValue in [self seekableTimeRanges]) {
272        CMTimeRange seekableTimeRange = [seekableTimeRangeValue CMTimeRangeValue];
273        NSTimeInterval endOfSeekableTimeRange = CMTimeGetSeconds(CMTimeRangeGetEnd(seekableTimeRange));
274        if (isnan(timeAtEndOfSeekableTimeRanges) || endOfSeekableTimeRange > timeAtEndOfSeekableTimeRanges)
275            timeAtEndOfSeekableTimeRanges = endOfSeekableTimeRange;
276    }
277
278    if (!isnan(timeAtEndOfSeekableTimeRanges))
279        [self seekToTime:timeAtEndOfSeekableTimeRanges];
280}
281
282- (BOOL)canScanForward
283{
284    return [self canPlay];
285}
286
287+ (NSSet *)keyPathsForValuesAffectingCanScanForward
288{
289    return [NSSet setWithObject:@"canPlay"];
290}
291
292- (void)beginScanningForward:(id)sender
293{
294    UNUSED_PARAM(sender);
295    ASSERT(self.delegate);
296    self.delegate->beginScanningForward();
297}
298
299- (void)endScanningForward:(id)sender
300{
301    UNUSED_PARAM(sender);
302    ASSERT(self.delegate);
303    self.delegate->endScanning();
304}
305
306- (void)beginScanningBackward:(id)sender
307{
308    UNUSED_PARAM(sender);
309    ASSERT(self.delegate);
310    self.delegate->beginScanningBackward();
311}
312
313- (void)endScanningBackward:(id)sender
314{
315    UNUSED_PARAM(sender);
316    ASSERT(self.delegate);
317    self.delegate->endScanning();
318}
319
320- (BOOL)canSeekToBeginning
321{
322    CMTime minimumTime = kCMTimeIndefinite;
323
324    for (NSValue *value in [self seekableTimeRanges])
325        minimumTime = CMTimeMinimum([value CMTimeRangeValue].start, minimumTime);
326
327    return CMTIME_IS_NUMERIC(minimumTime);
328}
329
330+ (NSSet *)keyPathsForValuesAffectingCanSeekToBeginning
331{
332    return [NSSet setWithObject:@"seekableTimeRanges"];
333}
334
335- (void)seekToBeginning:(id)sender
336{
337    UNUSED_PARAM(sender);
338    ASSERT(self.delegate);
339
340    self.delegate->seekToTime(-INFINITY);
341}
342
343- (void)seekChapterBackward:(id)sender
344{
345    [self seekToBeginning:sender];
346}
347
348- (BOOL)canSeekToEnd
349{
350    CMTime maximumTime = kCMTimeIndefinite;
351
352    for (NSValue *value in [self seekableTimeRanges])
353        maximumTime = CMTimeMaximum(CMTimeRangeGetEnd([value CMTimeRangeValue]), maximumTime);
354
355    return CMTIME_IS_NUMERIC(maximumTime);
356}
357
358+ (NSSet *)keyPathsForValuesAffectingCanSeekToEnd
359{
360    return [NSSet setWithObject:@"seekableTimeRanges"];
361}
362
363- (void)seekToEnd:(id)sender
364{
365    UNUSED_PARAM(sender);
366    ASSERT(self.delegate);
367
368    self.delegate->seekToTime(INFINITY);
369}
370
371- (void)seekChapterForward:(id)sender
372{
373    [self seekToEnd:sender];
374}
375
376- (BOOL)hasMediaSelectionOptions
377{
378    return [self hasAudioMediaSelectionOptions] || [self hasLegibleMediaSelectionOptions];
379}
380
381+ (NSSet *)keyPathsForValuesAffectingHasMediaSelectionOptions
382{
383    return [NSSet setWithObjects:@"hasAudioMediaSelectionOptions", @"hasLegibleMediaSelectionOptions", nil];
384}
385
386- (BOOL)hasAudioMediaSelectionOptions
387{
388    return [[self audioMediaSelectionOptions] count] > 0;
389}
390
391+ (NSSet *)keyPathsForValuesAffectingHasAudioMediaSelectionOptions
392{
393    return [NSSet setWithObject:@"audioMediaSelectionOptions"];
394}
395
396- (BOOL)hasLegibleMediaSelectionOptions
397{
398    return [[self legibleMediaSelectionOptions] count] > 0;
399}
400
401+ (NSSet *)keyPathsForValuesAffectingHasLegibleMediaSelectionOptions
402{
403    return [NSSet setWithObject:@"legibleMediaSelectionOptions"];
404}
405
406- (WebAVMediaSelectionOption *)currentAudioMediaSelectionOption
407{
408    return _currentAudioMediaSelectionOption;
409}
410
411- (void)setCurrentAudioMediaSelectionOption:(WebAVMediaSelectionOption *)option
412{
413    if (option == _currentAudioMediaSelectionOption)
414        return;
415
416    [_currentAudioMediaSelectionOption release];
417    _currentAudioMediaSelectionOption = [option retain];
418
419    ASSERT(self.delegate);
420
421    NSInteger index = NSNotFound;
422
423    if (option && self.audioMediaSelectionOptions)
424        index = [self.audioMediaSelectionOptions indexOfObject:option];
425
426    self.delegate->selectAudioMediaOption(index != NSNotFound ? index : UINT64_MAX);
427}
428
429- (WebAVMediaSelectionOption *)currentLegibleMediaSelectionOption
430{
431    return _currentLegibleMediaSelectionOption;
432}
433
434- (void)setCurrentLegibleMediaSelectionOption:(WebAVMediaSelectionOption *)option
435{
436    if (option == _currentLegibleMediaSelectionOption)
437        return;
438
439    [_currentLegibleMediaSelectionOption release];
440    _currentLegibleMediaSelectionOption = [option retain];
441
442    ASSERT(self.delegate);
443
444    NSInteger index = NSNotFound;
445
446    if (option && self.legibleMediaSelectionOptions)
447        index = [self.legibleMediaSelectionOptions indexOfObject:option];
448
449    self.delegate->selectLegibleMediaOption(index != NSNotFound ? index : UINT64_MAX);
450}
451
452- (BOOL)isPlayingOnExternalScreen
453{
454    return [self isExternalPlaybackActive];
455}
456
457+ (NSSet *)keyPathsForValuesAffectingPlayingOnExternalScreen
458{
459    return [NSSet setWithObjects:@"externalPlaybackActive", nil];
460}
461
462@end
463
464@interface WebAVMediaSelectionOption : NSObject
465@property (retain) NSString *localizedDisplayName;
466@end
467
468@implementation WebAVMediaSelectionOption
469@end
470
471@interface WebAVVideoLayer : CALayer <AVVideoLayer>
472+(WebAVVideoLayer *)videoLayer;
473@property (nonatomic) AVVideoLayerGravity videoLayerGravity;
474@property (nonatomic, getter = isReadyForDisplay) BOOL readyForDisplay;
475@property (nonatomic) CGRect videoRect;
476- (void)setPlayerViewController:(AVPlayerViewController *)playerViewController;
477- (void)setPlayerController:(AVPlayerController *)playerController;
478@end
479
480@implementation WebAVVideoLayer
481{
482    RetainPtr<WebAVPlayerController> _avPlayerController;
483    RetainPtr<AVPlayerViewController> _avPlayerViewController;
484    AVVideoLayerGravity _videoLayerGravity;
485}
486
487+(WebAVVideoLayer *)videoLayer
488{
489    return [[[WebAVVideoLayer alloc] init] autorelease];
490}
491
492- (instancetype)init
493{
494    self = [super init];
495    if (self) {
496        [self setMasksToBounds:YES];
497        [self setVideoLayerGravity:AVVideoLayerGravityResizeAspect];
498    }
499    return self;
500}
501
502- (void)setPlayerController:(AVPlayerController *)playerController
503{
504    ASSERT(!playerController || [playerController isKindOfClass:[WebAVPlayerController class]]);
505    _avPlayerController = (WebAVPlayerController *)playerController;
506}
507
508- (void)setPlayerViewController:(AVPlayerViewController *)playerViewController
509{
510    _avPlayerViewController = playerViewController;
511}
512
513- (void)setBounds:(CGRect)bounds
514{
515    [super setBounds:bounds];
516
517    if (![_avPlayerController delegate] || !_avPlayerViewController)
518        return;
519
520    UIView* rootView = [[_avPlayerViewController view] window];
521    if (!rootView)
522        return;
523
524    FloatRect rootBounds = [rootView bounds];
525    [_avPlayerController delegate]->setVideoLayerFrame(rootBounds);
526
527    FloatRect sourceBounds = largestRectWithAspectRatioInsideRect(CGRectGetWidth(bounds) / CGRectGetHeight(bounds), rootBounds);
528    CATransform3D transform = CATransform3DMakeScale(bounds.size.width / sourceBounds.width(), bounds.size.height / sourceBounds.height(), 1);
529    transform = CATransform3DTranslate(transform, bounds.origin.x - sourceBounds.x(), bounds.origin.y - sourceBounds.y(), 0);
530    [self setSublayerTransform:transform];
531}
532
533- (void)setVideoLayerGravity:(AVVideoLayerGravity)videoLayerGravity
534{
535    _videoLayerGravity = videoLayerGravity;
536
537    if (![_avPlayerController delegate])
538        return;
539
540    WebCore::WebVideoFullscreenModel::VideoGravity gravity = WebCore::WebVideoFullscreenModel::VideoGravityResizeAspect;
541    if (videoLayerGravity == AVVideoLayerGravityResize)
542        gravity = WebCore::WebVideoFullscreenModel::VideoGravityResize;
543    if (videoLayerGravity == AVVideoLayerGravityResizeAspect)
544        gravity = WebCore::WebVideoFullscreenModel::VideoGravityResizeAspect;
545    else if (videoLayerGravity == AVVideoLayerGravityResizeAspectFill)
546        gravity = WebCore::WebVideoFullscreenModel::VideoGravityResizeAspectFill;
547    else
548        ASSERT_NOT_REACHED();
549
550    [_avPlayerController delegate]->setVideoLayerGravity(gravity);
551}
552
553- (AVVideoLayerGravity)videoLayerGravity
554{
555    return _videoLayerGravity;
556}
557
558@end
559
560WebVideoFullscreenInterfaceAVKit::WebVideoFullscreenInterfaceAVKit()
561    : m_videoFullscreenModel(nullptr)
562{
563}
564
565WebAVPlayerController *WebVideoFullscreenInterfaceAVKit::playerController()
566{
567    if (!m_playerController)
568    {
569        m_playerController = adoptNS([[WebAVPlayerController alloc] init]);
570        if (m_videoFullscreenModel)
571            [m_playerController setDelegate:m_videoFullscreenModel];
572    }
573    return m_playerController.get();
574}
575
576
577void WebVideoFullscreenInterfaceAVKit::setWebVideoFullscreenModel(WebVideoFullscreenModel* model)
578{
579    m_videoFullscreenModel = model;
580    [m_playerController setDelegate:m_videoFullscreenModel];
581}
582
583void WebVideoFullscreenInterfaceAVKit::setWebVideoFullscreenChangeObserver(WebVideoFullscreenChangeObserver* observer)
584{
585    m_fullscreenChangeObserver = observer;
586}
587
588void WebVideoFullscreenInterfaceAVKit::setDuration(double duration)
589{
590    WebAVPlayerController* playerController = this->playerController();
591
592    __block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
593
594    dispatch_async(dispatch_get_main_queue(), ^{
595        // FIXME: https://bugs.webkit.org/show_bug.cgi?id=127017 use correct values instead of duration for all these
596        playerController.contentDuration = duration;
597        playerController.maxTime = duration;
598        playerController.contentDurationWithinEndTimes = duration;
599        playerController.loadedTimeRanges = @[@0, @(duration)];
600
601        // FIXME: we take this as an indication that playback is ready.
602        playerController.canPlay = YES;
603        playerController.canPause = YES;
604        playerController.canTogglePlayback = YES;
605        playerController.hasEnabledAudio = YES;
606        playerController.canSeek = YES;
607        playerController.minTime = 0;
608        playerController.status = AVPlayerControllerStatusReadyToPlay;
609
610        protect = nullptr;
611    });
612}
613
614void WebVideoFullscreenInterfaceAVKit::setCurrentTime(double currentTime, double anchorTime)
615{
616    __block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
617
618    dispatch_async(dispatch_get_main_queue(), ^{
619        NSTimeInterval anchorTimeStamp = ![playerController() rate] ? NAN : anchorTime;
620        AVValueTiming *timing = [getAVValueTimingClass() valueTimingWithAnchorValue:currentTime
621            anchorTimeStamp:anchorTimeStamp rate:0];
622        playerController().timing = timing;
623
624        protect = nullptr;
625    });
626}
627
628void WebVideoFullscreenInterfaceAVKit::setRate(bool isPlaying, float playbackRate)
629{
630    __block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
631
632    dispatch_async(dispatch_get_main_queue(), ^{
633        playerController().rate = isPlaying ? playbackRate : 0.;
634
635        protect = nullptr;
636    });
637}
638
639void WebVideoFullscreenInterfaceAVKit::setVideoDimensions(bool hasVideo, float width, float height)
640{
641    __block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
642
643    dispatch_async(dispatch_get_main_queue(), ^{
644        playerController().hasEnabledVideo = hasVideo;
645        playerController().contentDimensions = CGSizeMake(width, height);
646
647        protect = nullptr;
648    });
649}
650
651void WebVideoFullscreenInterfaceAVKit::setSeekableRanges(const TimeRanges& timeRanges)
652{
653    NSMutableArray* seekableRanges = [NSMutableArray array];
654    ExceptionCode exceptionCode;
655
656    for (unsigned i = 0; i < timeRanges.length(); i++) {
657        double start = timeRanges.start(i, exceptionCode);
658        double end = timeRanges.end(i, exceptionCode);
659
660        CMTimeRange range = CMTimeRangeMake(CMTimeMakeWithSeconds(start, 1000), CMTimeMakeWithSeconds(end-start, 1000));
661        [seekableRanges addObject:[NSValue valueWithCMTimeRange:range]];
662    }
663
664    __block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
665
666    dispatch_async(dispatch_get_main_queue(), ^{
667        playerController().seekableTimeRanges = seekableRanges;
668
669        protect = nullptr;
670    });
671}
672
673void WebVideoFullscreenInterfaceAVKit::setCanPlayFastReverse(bool canPlayFastReverse)
674{
675    playerController().canScanBackward = canPlayFastReverse;
676}
677
678static NSMutableArray *mediaSelectionOptions(const Vector<String>& options)
679{
680    NSMutableArray *webOptions = [NSMutableArray arrayWithCapacity:options.size()];
681    for (auto& name : options) {
682        RetainPtr<WebAVMediaSelectionOption> webOption = adoptNS([[WebAVMediaSelectionOption alloc] init]);
683        [webOption setLocalizedDisplayName:name];
684        [webOptions addObject:webOption.get()];
685    }
686    return webOptions;
687}
688
689void WebVideoFullscreenInterfaceAVKit::setAudioMediaSelectionOptions(const Vector<String>& options, uint64_t selectedIndex)
690{
691    NSMutableArray *webOptions = mediaSelectionOptions(options);
692    __block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
693
694    dispatch_async(dispatch_get_main_queue(), ^{
695        playerController().audioMediaSelectionOptions = webOptions;
696        if (selectedIndex < webOptions.count)
697            playerController().currentAudioMediaSelectionOption = webOptions[(size_t)selectedIndex];
698
699        protect = nullptr;
700    });
701}
702
703void WebVideoFullscreenInterfaceAVKit::setLegibleMediaSelectionOptions(const Vector<String>& options, uint64_t selectedIndex)
704{
705    NSMutableArray *webOptions = mediaSelectionOptions(options);
706    __block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
707
708    dispatch_async(dispatch_get_main_queue(), ^{
709        playerController().legibleMediaSelectionOptions = webOptions;
710        if (selectedIndex < webOptions.count)
711            playerController().currentLegibleMediaSelectionOption = webOptions[(size_t)selectedIndex];
712
713        protect = nullptr;
714    });
715}
716
717void WebVideoFullscreenInterfaceAVKit::setExternalPlayback(bool enabled, ExternalPlaybackTargetType targetType, String localizedDeviceName)
718{
719    AVPlayerControllerExternalPlaybackType externalPlaybackType = AVPlayerControllerExternalPlaybackTypeNone;
720    if (targetType == TargetTypeAirPlay)
721        externalPlaybackType = AVPlayerControllerExternalPlaybackTypeAirPlay;
722    else if (targetType == TargetTypeTVOut)
723        externalPlaybackType = AVPlayerControllerExternalPlaybackTypeTVOut;
724
725    __block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
726
727    dispatch_async(dispatch_get_main_queue(), ^{
728        playerController().externalPlaybackAirPlayDeviceLocalizedName = localizedDeviceName;
729        playerController().externalPlaybackType = externalPlaybackType;
730        playerController().externalPlaybackActive = enabled;
731        [m_videoLayerContainer.get() setHidden:enabled];
732
733        protect = nullptr;
734    });
735}
736
737void WebVideoFullscreenInterfaceAVKit::setupFullscreen(PlatformLayer& videoLayer, WebCore::IntRect initialRect, UIView* parentView)
738{
739    __block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
740
741    m_videoLayer = &videoLayer;
742
743    dispatch_async(dispatch_get_main_queue(), ^{
744
745        [CATransaction begin];
746        [CATransaction setDisableActions:YES];
747        m_parentView = parentView;
748
749        if (!applicationIsAdSheet()) {
750            m_window = adoptNS([[getUIWindowClass() alloc] initWithFrame:[[getUIScreenClass() mainScreen] bounds]]);
751            [m_window setBackgroundColor:[getUIColorClass() clearColor]];
752            m_viewController = adoptNS([[getUIViewControllerClass() alloc] init]);
753            [[m_viewController view] setFrame:[m_window bounds]];
754            [m_window setRootViewController:m_viewController.get()];
755            [m_window makeKeyAndVisible];
756        }
757
758        [m_videoLayer removeFromSuperlayer];
759
760        m_videoLayerContainer = [WebAVVideoLayer videoLayer];
761        [m_videoLayerContainer setHidden:playerController().externalPlaybackActive];
762        [m_videoLayerContainer addSublayer:m_videoLayer.get()];
763
764        CGSize videoSize = playerController().contentDimensions;
765        CGRect videoRect = CGRectMake(0, 0, videoSize.width, videoSize.height);
766        [m_videoLayerContainer setVideoRect:videoRect];
767
768        m_playerViewController = adoptNS([[getAVPlayerViewControllerClass() alloc] initWithVideoLayer:m_videoLayerContainer.get()]);
769        [m_playerViewController setShowsPlaybackControls:NO];
770        [m_playerViewController setPlayerController:(AVPlayerController *)playerController()];
771        [m_playerViewController setDelegate:playerController()];
772        [m_videoLayerContainer setPlayerViewController:m_playerViewController.get()];
773
774        if (m_viewController) {
775            [m_viewController addChildViewController:m_playerViewController.get()];
776            [[m_viewController view] addSubview:[m_playerViewController view]];
777            [m_playerViewController view].frame = [parentView convertRect:initialRect toView:nil];
778        } else {
779            [parentView addSubview:[m_playerViewController view]];
780            [m_playerViewController view].frame = initialRect;
781        }
782
783        [[m_playerViewController view] setBackgroundColor:[getUIColorClass() clearColor]];
784        [[m_playerViewController view] setNeedsLayout];
785        [[m_playerViewController view] layoutIfNeeded];
786
787        [CATransaction commit];
788
789        dispatch_async(dispatch_get_main_queue(), ^{
790            if (m_fullscreenChangeObserver)
791                m_fullscreenChangeObserver->didSetupFullscreen();
792
793            protect = nullptr;
794        });
795    });
796}
797
798void WebVideoFullscreenInterfaceAVKit::enterFullscreen()
799{
800    __block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
801
802    dispatch_async(dispatch_get_main_queue(), ^{
803        [m_videoLayerContainer setBackgroundColor:[[getUIColorClass() blackColor] CGColor]];
804        [m_playerViewController enterFullScreenWithCompletionHandler:^(BOOL, NSError*)
805        {
806            [m_playerViewController setShowsPlaybackControls:YES];
807            if (m_fullscreenChangeObserver)
808                m_fullscreenChangeObserver->didEnterFullscreen();
809            protect = nullptr;
810        }];
811    });
812}
813
814void WebVideoFullscreenInterfaceAVKit::exitFullscreen(WebCore::IntRect finalRect)
815{
816    __block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
817
818    m_playerController = nil;
819
820    dispatch_async(dispatch_get_main_queue(), ^{
821        [m_playerViewController setShowsPlaybackControls:NO];
822        if (m_viewController)
823            [m_playerViewController view].frame = [m_parentView convertRect:finalRect toView:nil];
824        else
825            [m_playerViewController view].frame = finalRect;
826
827        if ([m_videoLayerContainer videoLayerGravity] != AVVideoLayerGravityResizeAspect)
828            [m_videoLayerContainer setVideoLayerGravity:AVVideoLayerGravityResizeAspect];
829        [[m_playerViewController view] layoutIfNeeded];
830        [m_playerViewController exitFullScreenWithCompletionHandler:^(BOOL, NSError*) {
831            [m_videoLayerContainer setBackgroundColor:[[getUIColorClass() clearColor] CGColor]];
832            [[m_playerViewController view] setBackgroundColor:[getUIColorClass() clearColor]];
833            if (m_fullscreenChangeObserver)
834                m_fullscreenChangeObserver->didExitFullscreen();
835            protect = nullptr;
836        }];
837    });
838}
839
840@interface UIApplication ()
841-(void)_setStatusBarOrientation:(UIInterfaceOrientation)o;
842@end
843
844@interface UIWindow ()
845-(UIInterfaceOrientation)interfaceOrientation;
846@end
847
848void WebVideoFullscreenInterfaceAVKit::cleanupFullscreen()
849{
850    // Retain this to extend object life until async block completes.
851    __block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
852
853    dispatch_async(dispatch_get_main_queue(), ^{
854        if (m_window) {
855            [m_window setHidden:YES];
856            [m_window setRootViewController:nil];
857            [[getUIApplicationClass() sharedApplication] _setStatusBarOrientation:[[m_parentView window] interfaceOrientation]];
858        }
859        [m_playerViewController setDelegate:nil];
860        [[m_playerViewController view] removeFromSuperview];
861        if (m_viewController)
862            [m_playerViewController removeFromParentViewController];
863        [m_playerViewController setPlayerController:nil];
864        m_playerViewController = nil;
865        [m_videoLayer removeFromSuperlayer];
866        m_videoLayer = nil;
867        [m_videoLayerContainer removeFromSuperlayer];
868        [m_videoLayerContainer setPlayerViewController:nil];
869        m_videoLayerContainer = nil;
870        [[m_viewController view] removeFromSuperview];
871        m_viewController = nil;
872        m_window = nil;
873        m_parentView = nil;
874
875        if (m_fullscreenChangeObserver)
876            m_fullscreenChangeObserver->didCleanupFullscreen();
877        protect = nullptr;
878    });
879}
880
881void WebVideoFullscreenInterfaceAVKit::invalidate()
882{
883    [m_window setHidden:YES];
884    [m_window setRootViewController:nil];
885    [m_playerViewController exitFullScreenAnimated:NO completionHandler:nil];
886    m_playerController = nil;
887    [m_playerViewController setDelegate:nil];
888    [[m_playerViewController view] removeFromSuperview];
889    if (m_viewController)
890        [m_playerViewController removeFromParentViewController];
891    [m_playerViewController setPlayerController:nil];
892    m_playerViewController = nil;
893    [m_videoLayer removeFromSuperlayer];
894    m_videoLayer = nil;
895    [m_videoLayerContainer removeFromSuperlayer];
896    [m_videoLayerContainer setPlayerViewController:nil];
897    m_videoLayerContainer = nil;
898    [[m_viewController view] removeFromSuperview];
899    m_viewController = nil;
900    m_window = nil;
901    m_parentView = nil;
902}
903
904void WebVideoFullscreenInterfaceAVKit::requestHideAndExitFullscreen()
905{
906    __block RefPtr<WebVideoFullscreenInterfaceAVKit> protect(this);
907
908    dispatch_async(dispatch_get_main_queue(), ^{
909        [m_window setHidden:YES];
910        [m_playerViewController exitFullScreenAnimated:NO completionHandler:^(BOOL, NSError*) {
911            protect = nullptr;
912        }];
913    });
914
915    if (m_videoFullscreenModel)
916        m_videoFullscreenModel->requestExitFullscreen();
917}
918
919
920#endif
921