2 * Copyright (C) 2014-2018 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.
28 #import "WebAVPlayerController.h"
30 #if PLATFORM(IOS) && HAVE(AVKIT)
33 #import "PlaybackSessionInterfaceAVKit.h"
34 #import "PlaybackSessionModel.h"
35 #import "TimeRanges.h"
36 #import <AVFoundation/AVTime.h>
37 #import <pal/spi/cocoa/AVKitSPI.h>
38 #import <wtf/text/CString.h>
39 #import <wtf/text/WTFString.h>
41 #import <pal/cf/CoreMediaSoftLink.h>
43 SOFTLINK_AVKIT_FRAMEWORK()
44 SOFT_LINK_CLASS_OPTIONAL(AVKit, AVPlayerController)
45 SOFT_LINK_CLASS_OPTIONAL(AVKit, AVValueTiming)
47 using namespace WebCore;
49 static void * WebAVPlayerControllerSeekableTimeRangesObserverContext = &WebAVPlayerControllerSeekableTimeRangesObserverContext;
50 static void * WebAVPlayerControllerHasLiveStreamingContentObserverContext = &WebAVPlayerControllerHasLiveStreamingContentObserverContext;
51 static void * WebAVPlayerControllerIsPlayingOnSecondScreenObserverContext = &WebAVPlayerControllerIsPlayingOnSecondScreenObserverContext;
53 static double WebAVPlayerControllerLiveStreamSeekableTimeRangeDurationHysteresisDelta = 3.0; // Minimum delta of time required to change the duration of the seekable time range.
54 static double WebAVPlayerControllerLiveStreamMinimumTargetDuration = 1.0; // Minimum segment duration to be considered valid.
55 static double WebAVPlayerControllerLiveStreamSeekableTimeRangeMinimumDuration = 30.0;
57 @implementation WebAVPlayerController {
58 BOOL _liveStreamEventModePossible;
63 if (!getAVPlayerControllerClass())
66 if (!(self = [super init]))
69 initAVPlayerController();
70 self.playerControllerProxy = [[allocAVPlayerControllerInstance() init] autorelease];
71 _liveStreamEventModePossible = YES;
73 [self addObserver:self forKeyPath:@"seekableTimeRanges" options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial) context:WebAVPlayerControllerSeekableTimeRangesObserverContext];
74 [self addObserver:self forKeyPath:@"hasLiveStreamingContent" options:NSKeyValueObservingOptionInitial context:WebAVPlayerControllerHasLiveStreamingContentObserverContext];
75 [self addObserver:self forKeyPath:@"playingOnSecondScreen" options:NSKeyValueObservingOptionNew context:WebAVPlayerControllerIsPlayingOnSecondScreenObserverContext];
82 [self removeObserver:self forKeyPath:@"seekableTimeRanges" context:WebAVPlayerControllerSeekableTimeRangesObserverContext];
83 [self removeObserver:self forKeyPath:@"hasLiveStreamingContent" context:WebAVPlayerControllerHasLiveStreamingContentObserverContext];
84 [self removeObserver:self forKeyPath:@"playingOnSecondScreen" context:WebAVPlayerControllerIsPlayingOnSecondScreenObserverContext];
86 [_playerControllerProxy release];
87 [_loadedTimeRanges release];
88 [_seekableTimeRanges release];
90 [_audioMediaSelectionOptions release];
91 [_legibleMediaSelectionOptions release];
92 [_currentAudioMediaSelectionOption release];
93 [_currentLegibleMediaSelectionOption release];
94 [_externalPlaybackAirPlayDeviceLocalizedName release];
105 - (id)forwardingTargetForSelector:(SEL)selector
107 UNUSED_PARAM(selector);
108 return self.playerControllerProxy;
111 - (void)play:(id)sender
113 UNUSED_PARAM(sender);
115 self.delegate->play();
118 - (void)pause:(id)sender
120 UNUSED_PARAM(sender);
122 self.delegate->pause();
125 - (void)togglePlayback:(id)sender
127 UNUSED_PARAM(sender);
129 self.delegate->togglePlayState();
132 - (void)togglePlaybackEvenWhenInBackground:(id)sender
134 [self togglePlayback:sender];
142 - (void)setPlaying:(BOOL)playing
147 self.delegate->play();
149 self.delegate->pause();
152 + (NSSet *)keyPathsForValuesAffectingPlaying
154 return [NSSet setWithObject:@"rate"];
157 - (void)beginScrubbing:(id)sender
159 UNUSED_PARAM(sender);
161 self.delegate->beginScrubbing();
164 - (void)endScrubbing:(id)sender
166 UNUSED_PARAM(sender);
168 self.delegate->endScrubbing();
171 - (void)seekToTime:(NSTimeInterval)time
174 self.delegate->seekToTime(time);
177 - (void)seekToTime:(NSTimeInterval)time toleranceBefore:(NSTimeInterval)before toleranceAfter:(NSTimeInterval)after
179 self.delegate->seekToTime(time, before, after);
182 - (void)seekByTimeInterval:(NSTimeInterval)interval
184 [self seekByTimeInterval:interval toleranceBefore:0. toleranceAfter:0.];
187 - (void)seekByTimeInterval:(NSTimeInterval)interval toleranceBefore:(NSTimeInterval)before toleranceAfter:(NSTimeInterval)after
189 NSTimeInterval targetTime = [[self timing] currentValue] + interval;
190 [self seekToTime:targetTime toleranceBefore:before toleranceAfter:after];
193 - (NSTimeInterval)currentTimeWithinEndTimes
195 return self.timing.currentValue;
198 - (void)setCurrentTimeWithinEndTimes:(NSTimeInterval)currentTimeWithinEndTimes
200 [self seekToTime:currentTimeWithinEndTimes];
203 + (NSSet *)keyPathsForValuesAffectingCurrentTimeWithinEndTimes
205 return [NSSet setWithObject:@"timing"];
208 - (BOOL)hasLiveStreamingContent
210 if ([self status] == AVPlayerControllerStatusReadyToPlay)
211 return [self contentDuration] == std::numeric_limits<float>::infinity();
215 + (NSSet *)keyPathsForValuesAffectingHasLiveStreamingContent
217 return [NSSet setWithObjects:@"contentDuration", @"status", nil];
220 - (NSTimeInterval)maxTime
222 NSTimeInterval maxTime = NAN;
224 NSTimeInterval duration = [self contentDuration];
225 if (!isnan(duration) && !isinf(duration))
227 else if ([self hasSeekableLiveStreamingContent] && [self maxTiming])
228 maxTime = [[self maxTiming] currentValue];
233 + (NSSet *)keyPathsForValuesAffectingMaxTime
235 return [NSSet setWithObjects:@"contentDuration", @"hasSeekableLiveStreamingContent", @"maxTiming", nil];
238 - (NSTimeInterval)minTime
240 NSTimeInterval minTime = 0.0;
242 if ([self hasSeekableLiveStreamingContent] && [self minTiming])
243 minTime = [[self minTiming] currentValue];
248 + (NSSet *)keyPathsForValuesAffectingMinTime
250 return [NSSet setWithObjects:@"hasSeekableLiveStreamingContent", @"minTiming", nil];
253 - (void)skipBackwardThirtySeconds:(id)sender
257 UNUSED_PARAM(sender);
258 BOOL isTimeWithinSeekableTimeRanges = NO;
259 CMTime currentTime = CMTimeMakeWithSeconds([[self timing] currentValue], 1000);
260 CMTime thirtySecondsBeforeCurrentTime = CMTimeSubtract(currentTime, PAL::CMTimeMake(30, 1));
262 for (NSValue *seekableTimeRangeValue in [self seekableTimeRanges]) {
263 if (CMTimeRangeContainsTime([seekableTimeRangeValue CMTimeRangeValue], thirtySecondsBeforeCurrentTime)) {
264 isTimeWithinSeekableTimeRanges = YES;
269 if (isTimeWithinSeekableTimeRanges)
270 [self seekToTime:CMTimeGetSeconds(thirtySecondsBeforeCurrentTime)];
273 - (void)gotoEndOfSeekableRanges:(id)sender
277 UNUSED_PARAM(sender);
278 NSTimeInterval timeAtEndOfSeekableTimeRanges = NAN;
280 for (NSValue *seekableTimeRangeValue in [self seekableTimeRanges]) {
281 CMTimeRange seekableTimeRange = [seekableTimeRangeValue CMTimeRangeValue];
282 NSTimeInterval endOfSeekableTimeRange = CMTimeGetSeconds(CMTimeRangeGetEnd(seekableTimeRange));
283 if (isnan(timeAtEndOfSeekableTimeRanges) || endOfSeekableTimeRange > timeAtEndOfSeekableTimeRanges)
284 timeAtEndOfSeekableTimeRanges = endOfSeekableTimeRange;
287 if (!isnan(timeAtEndOfSeekableTimeRanges))
288 [self seekToTime:timeAtEndOfSeekableTimeRanges];
291 - (BOOL)canScanForward
293 return [self canPlay];
296 + (NSSet *)keyPathsForValuesAffectingCanScanForward
298 return [NSSet setWithObject:@"canPlay"];
301 - (void)beginScanningForward:(id)sender
303 UNUSED_PARAM(sender);
305 self.delegate->beginScanningForward();
308 - (void)endScanningForward:(id)sender
310 UNUSED_PARAM(sender);
312 self.delegate->endScanning();
315 - (void)beginScanningBackward:(id)sender
317 UNUSED_PARAM(sender);
319 self.delegate->beginScanningBackward();
322 - (void)endScanningBackward:(id)sender
324 UNUSED_PARAM(sender);
326 self.delegate->endScanning();
329 - (BOOL)canSeekToBeginning
333 CMTime minimumTime = kCMTimeIndefinite;
335 for (NSValue *value in [self seekableTimeRanges])
336 minimumTime = CMTimeMinimum([value CMTimeRangeValue].start, minimumTime);
338 return CMTIME_IS_NUMERIC(minimumTime);
341 + (NSSet *)keyPathsForValuesAffectingCanSeekToBeginning
343 return [NSSet setWithObject:@"seekableTimeRanges"];
346 - (void)seekToBeginning:(id)sender
348 UNUSED_PARAM(sender);
350 self.delegate->seekToTime(-INFINITY);
353 - (void)seekChapterBackward:(id)sender
355 [self seekToBeginning:sender];
362 CMTime maximumTime = kCMTimeIndefinite;
364 for (NSValue *value in [self seekableTimeRanges])
365 maximumTime = CMTimeMaximum(CMTimeRangeGetEnd([value CMTimeRangeValue]), maximumTime);
367 return CMTIME_IS_NUMERIC(maximumTime);
370 + (NSSet *)keyPathsForValuesAffectingCanSeekToEnd
372 return [NSSet setWithObject:@"seekableTimeRanges"];
375 - (void)seekToEnd:(id)sender
377 UNUSED_PARAM(sender);
379 self.delegate->seekToTime(INFINITY);
382 - (void)seekChapterForward:(id)sender
384 [self seekToEnd:sender];
387 - (BOOL)hasMediaSelectionOptions
389 return [self hasAudioMediaSelectionOptions] || [self hasLegibleMediaSelectionOptions];
392 + (NSSet *)keyPathsForValuesAffectingHasMediaSelectionOptions
394 return [NSSet setWithObjects:@"hasAudioMediaSelectionOptions", @"hasLegibleMediaSelectionOptions", nil];
397 - (BOOL)hasAudioMediaSelectionOptions
399 return [[self audioMediaSelectionOptions] count] > 1;
402 + (NSSet *)keyPathsForValuesAffectingHasAudioMediaSelectionOptions
404 return [NSSet setWithObject:@"audioMediaSelectionOptions"];
407 - (BOOL)hasLegibleMediaSelectionOptions
409 const NSUInteger numDefaultLegibleOptions = 2;
410 return [[self legibleMediaSelectionOptions] count] > numDefaultLegibleOptions;
413 + (NSSet *)keyPathsForValuesAffectingHasLegibleMediaSelectionOptions
415 return [NSSet setWithObject:@"legibleMediaSelectionOptions"];
418 - (WebAVMediaSelectionOption *)currentAudioMediaSelectionOption
420 return _currentAudioMediaSelectionOption;
423 - (void)setCurrentAudioMediaSelectionOption:(WebAVMediaSelectionOption *)option
425 if (option == _currentAudioMediaSelectionOption)
428 [_currentAudioMediaSelectionOption release];
429 _currentAudioMediaSelectionOption = [option retain];
434 NSInteger index = NSNotFound;
436 if (option && self.audioMediaSelectionOptions)
437 index = [self.audioMediaSelectionOptions indexOfObject:option];
439 if (index == NSNotFound)
442 self.delegate->selectAudioMediaOption(index);
445 - (WebAVMediaSelectionOption *)currentLegibleMediaSelectionOption
447 return _currentLegibleMediaSelectionOption;
450 - (void)setCurrentLegibleMediaSelectionOption:(WebAVMediaSelectionOption *)option
452 if (option == _currentLegibleMediaSelectionOption)
455 [_currentLegibleMediaSelectionOption release];
456 _currentLegibleMediaSelectionOption = [option retain];
461 NSInteger index = NSNotFound;
463 if (option && self.legibleMediaSelectionOptions)
464 index = [self.legibleMediaSelectionOptions indexOfObject:option];
466 if (index == NSNotFound)
469 self.delegate->selectLegibleMediaOption(index);
472 - (BOOL)isPlayingOnExternalScreen
474 return [self isExternalPlaybackActive] || [self isPlayingOnSecondScreen];
477 + (NSSet *)keyPathsForValuesAffectingPlayingOnExternalScreen
479 return [NSSet setWithObjects:@"externalPlaybackActive", @"playingOnSecondScreen", nil];
482 - (BOOL)isPictureInPictureInterrupted
484 return _pictureInPictureInterrupted;
487 - (void)setPictureInPictureInterrupted:(BOOL)pictureInPictureInterrupted
489 if (_pictureInPictureInterrupted != pictureInPictureInterrupted) {
490 _pictureInPictureInterrupted = pictureInPictureInterrupted;
491 if (pictureInPictureInterrupted)
492 [self setPlaying:NO];
501 - (void)setMuted:(BOOL)muted
508 self.delegate->setMuted(muted);
511 - (void)toggleMuted:(id)sender
513 UNUSED_PARAM(sender);
515 self.delegate->toggleMuted();
520 return self.delegate ? self.delegate->volume() : 0;
523 - (void)setVolume:(double)volume
526 self.delegate->setVolume(volume);
529 - (void)volumeChanged:(double)volume
531 UNUSED_PARAM(volume);
532 [self willChangeValueForKey:@"volume"];
533 [self didChangeValueForKey:@"volume"];
536 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
540 UNUSED_PARAM(object);
541 UNUSED_PARAM(keyPath);
543 if (WebAVPlayerControllerSeekableTimeRangesObserverContext == context) {
544 NSArray *oldArray = change[NSKeyValueChangeOldKey];
545 NSArray *newArray = change[NSKeyValueChangeNewKey];
546 if ([oldArray isKindOfClass:[NSArray class]] && [newArray isKindOfClass:[NSArray class]]) {
547 CMTimeRange oldSeekableTimeRange = [oldArray count] > 0 ? [[oldArray firstObject] CMTimeRangeValue] : kCMTimeRangeInvalid;
548 CMTimeRange newSeekableTimeRange = [newArray count] > 0 ? [[newArray firstObject] CMTimeRangeValue] : kCMTimeRangeInvalid;
549 if (!CMTimeRangeEqual(oldSeekableTimeRange, newSeekableTimeRange)) {
550 if (CMTIMERANGE_IS_VALID(newSeekableTimeRange) && CMTIMERANGE_IS_VALID(oldSeekableTimeRange) && _liveStreamEventModePossible && !CMTimeRangeContainsTime(newSeekableTimeRange, oldSeekableTimeRange.start))
551 _liveStreamEventModePossible = NO;
553 [self updateMinMaxTiming];
556 } else if (WebAVPlayerControllerHasLiveStreamingContentObserverContext == context)
557 [self updateMinMaxTiming];
558 else if (WebAVPlayerControllerIsPlayingOnSecondScreenObserverContext == context) {
559 if (auto* delegate = self.delegate)
560 delegate->setPlayingOnSecondScreen(_playingOnSecondScreen);
564 - (void)updateMinMaxTiming
568 AVValueTiming *newMinTiming = nil;
569 AVValueTiming *newMaxTiming = nil;
571 // For live streams value timings for the min and max times are needed.
572 if ([self hasLiveStreamingContent] && ([[self seekableTimeRanges] count] > 0)) {
573 CMTimeRange seekableTimeRange = [[[self seekableTimeRanges] firstObject] CMTimeRangeValue];
574 if (CMTIMERANGE_IS_VALID(seekableTimeRange)) {
575 NSTimeInterval oldMinTime = [[self minTiming] currentValue];
576 NSTimeInterval oldMaxTime = [[self maxTiming] currentValue];
577 NSTimeInterval newMinTime = CMTimeGetSeconds(seekableTimeRange.start);
578 NSTimeInterval newMaxTime = CMTimeGetSeconds(seekableTimeRange.start) + CMTimeGetSeconds(seekableTimeRange.duration) + (CACurrentMediaTime() - [self seekableTimeRangesLastModifiedTime]);
579 double newMinTimingRate = _liveStreamEventModePossible ? 0.0 : 1.0;
580 BOOL minTimingNeedsUpdate = YES;
581 BOOL maxTimingNeedsUpdate = YES;
583 if (isfinite([self liveUpdateInterval]) && [self liveUpdateInterval] > WebAVPlayerControllerLiveStreamMinimumTargetDuration) {
584 // Only update the timing if the new time differs by one segment duration plus the hysteresis delta.
585 minTimingNeedsUpdate = isnan(oldMinTime) || (fabs(oldMinTime - newMinTime) > [self liveUpdateInterval] + WebAVPlayerControllerLiveStreamSeekableTimeRangeDurationHysteresisDelta) || ([[self minTiming] rate] != newMinTimingRate);
586 maxTimingNeedsUpdate = isnan(oldMaxTime) || (fabs(oldMaxTime - newMaxTime) > [self liveUpdateInterval] + WebAVPlayerControllerLiveStreamSeekableTimeRangeDurationHysteresisDelta);
589 if (minTimingNeedsUpdate || maxTimingNeedsUpdate) {
590 newMinTiming = [getAVValueTimingClass() valueTimingWithAnchorValue:newMinTime anchorTimeStamp:[getAVValueTimingClass() currentTimeStamp] rate:newMinTimingRate];
591 newMaxTiming = [getAVValueTimingClass() valueTimingWithAnchorValue:newMaxTime anchorTimeStamp:[getAVValueTimingClass() currentTimeStamp] rate:1.0];
593 newMinTiming = [self minTiming];
594 newMaxTiming = [self maxTiming];
600 newMinTiming = [getAVValueTimingClass() valueTimingWithAnchorValue:[self minTime] anchorTimeStamp:NAN rate:0.0];
603 newMaxTiming = [getAVValueTimingClass() valueTimingWithAnchorValue:[self maxTime] anchorTimeStamp:NAN rate:0.0];
605 [self setMinTiming:newMinTiming];
606 [self setMaxTiming:newMaxTiming];
609 - (BOOL)hasSeekableLiveStreamingContent
611 BOOL hasSeekableLiveStreamingContent = NO;
613 if ([self hasLiveStreamingContent] && [self minTiming] && [self maxTiming] && isfinite([self liveUpdateInterval]) && [self liveUpdateInterval] > WebAVPlayerControllerLiveStreamMinimumTargetDuration && ([self seekableTimeRangesLastModifiedTime] != 0.0)) {
614 NSTimeInterval timeStamp = [getAVValueTimingClass() currentTimeStamp];
615 NSTimeInterval minTime = [[self minTiming] valueForTimeStamp:timeStamp];
616 NSTimeInterval maxTime = [[self maxTiming] valueForTimeStamp:timeStamp];
617 hasSeekableLiveStreamingContent = ((maxTime - minTime) > WebAVPlayerControllerLiveStreamSeekableTimeRangeMinimumDuration);
620 return hasSeekableLiveStreamingContent;
623 + (NSSet *)keyPathsForValuesAffectingHasSeekableLiveStreamingContent
625 return [NSSet setWithObjects:@"hasLiveStreamingContent", @"minTiming", @"maxTiming", @"seekableTimeRangesLastModifiedTime", nil];
630 @implementation WebAVMediaSelectionOption
634 [_localizedDisplayName release];
640 #endif // PLATFORM(IOS)