[iOS] Don't update AVPlayerViewController currentTime while scrubbing
[WebKit-https.git] / Source / WebCore / platform / ios / WebAVPlayerController.mm
1 /*
2  * Copyright (C) 2014-2018 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 #import "WebAVPlayerController.h"
29
30 #if PLATFORM(IOS_FAMILY) && HAVE(AVKIT)
31
32 #import "Logging.h"
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>
40
41 #import <pal/cf/CoreMediaSoftLink.h>
42
43 SOFTLINK_AVKIT_FRAMEWORK()
44 SOFT_LINK_CLASS_OPTIONAL(AVKit, AVPlayerController)
45 SOFT_LINK_CLASS_OPTIONAL(AVKit, AVValueTiming)
46
47 using namespace WebCore;
48
49 static void * WebAVPlayerControllerSeekableTimeRangesObserverContext = &WebAVPlayerControllerSeekableTimeRangesObserverContext;
50 static void * WebAVPlayerControllerHasLiveStreamingContentObserverContext = &WebAVPlayerControllerHasLiveStreamingContentObserverContext;
51 static void * WebAVPlayerControllerIsPlayingOnSecondScreenObserverContext = &WebAVPlayerControllerIsPlayingOnSecondScreenObserverContext;
52
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;
56
57 @implementation WebAVPlayerController {
58     BOOL _liveStreamEventModePossible;
59     BOOL _isScrubbing;
60 }
61
62 - (instancetype)init
63 {
64     if (!getAVPlayerControllerClass())
65         return nil;
66
67     if (!(self = [super init]))
68         return self;
69
70     initAVPlayerController();
71     self.playerControllerProxy = [[allocAVPlayerControllerInstance() init] autorelease];
72     _liveStreamEventModePossible = YES;
73
74     [self addObserver:self forKeyPath:@"seekableTimeRanges" options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial) context:WebAVPlayerControllerSeekableTimeRangesObserverContext];
75     [self addObserver:self forKeyPath:@"hasLiveStreamingContent" options:NSKeyValueObservingOptionInitial context:WebAVPlayerControllerHasLiveStreamingContentObserverContext];
76     [self addObserver:self forKeyPath:@"playingOnSecondScreen" options:NSKeyValueObservingOptionNew context:WebAVPlayerControllerIsPlayingOnSecondScreenObserverContext];
77
78     return self;
79 }
80
81 - (void)dealloc
82 {
83     [self removeObserver:self forKeyPath:@"seekableTimeRanges" context:WebAVPlayerControllerSeekableTimeRangesObserverContext];
84     [self removeObserver:self forKeyPath:@"hasLiveStreamingContent" context:WebAVPlayerControllerHasLiveStreamingContentObserverContext];
85     [self removeObserver:self forKeyPath:@"playingOnSecondScreen" context:WebAVPlayerControllerIsPlayingOnSecondScreenObserverContext];
86
87     [_playerControllerProxy release];
88     [_loadedTimeRanges release];
89     [_seekableTimeRanges release];
90     [_timing release];
91     [_audioMediaSelectionOptions release];
92     [_legibleMediaSelectionOptions release];
93     [_currentAudioMediaSelectionOption release];
94     [_currentLegibleMediaSelectionOption release];
95     [_externalPlaybackAirPlayDeviceLocalizedName release];
96     [_minTiming release];
97     [_maxTiming release];
98     [super dealloc];
99 }
100
101 - (AVPlayer *)player
102 {
103     return nil;
104 }
105
106 - (id)forwardingTargetForSelector:(SEL)selector
107 {
108     UNUSED_PARAM(selector);
109     return self.playerControllerProxy;
110 }
111
112 - (void)play:(id)sender
113 {
114     UNUSED_PARAM(sender);
115     if (self.delegate)
116         self.delegate->play();
117 }
118
119 - (void)pause:(id)sender
120 {
121     UNUSED_PARAM(sender);
122     if (self.delegate)
123         self.delegate->pause();
124 }
125
126 - (void)togglePlayback:(id)sender
127 {
128     UNUSED_PARAM(sender);
129     if (self.delegate)
130         self.delegate->togglePlayState();
131 }
132
133 - (void)togglePlaybackEvenWhenInBackground:(id)sender
134 {
135     [self togglePlayback:sender];
136 }
137
138 - (BOOL)isPlaying
139 {
140     return [self rate];
141 }
142
143 - (void)setPlaying:(BOOL)playing
144 {
145     if (!self.delegate)
146         return;
147     if (playing)
148         self.delegate->play();
149     else
150         self.delegate->pause();
151 }
152
153 + (NSSet *)keyPathsForValuesAffectingPlaying
154 {
155     return [NSSet setWithObject:@"rate"];
156 }
157
158 - (void)beginScrubbing:(id)sender
159 {
160     UNUSED_PARAM(sender);
161     _isScrubbing = YES;
162     if (self.delegate)
163         self.delegate->beginScrubbing();
164 }
165
166 - (void)endScrubbing:(id)sender
167 {
168     UNUSED_PARAM(sender);
169     _isScrubbing = NO;
170     if (self.delegate)
171         self.delegate->endScrubbing();
172 }
173
174 - (void)seekToTime:(NSTimeInterval)time
175 {
176     if (self.delegate)
177         self.delegate->seekToTime(time);
178 }
179
180 - (void)seekToTime:(NSTimeInterval)time toleranceBefore:(NSTimeInterval)before toleranceAfter:(NSTimeInterval)after
181 {
182     self.delegate->seekToTime(time, before, after);
183 }
184
185 - (void)seekByTimeInterval:(NSTimeInterval)interval
186 {
187     [self seekByTimeInterval:interval toleranceBefore:0. toleranceAfter:0.];
188 }
189
190 - (void)seekByTimeInterval:(NSTimeInterval)interval toleranceBefore:(NSTimeInterval)before toleranceAfter:(NSTimeInterval)after
191 {
192     NSTimeInterval targetTime = [[self timing] currentValue] + interval;
193     [self seekToTime:targetTime toleranceBefore:before toleranceAfter:after];
194 }
195
196 - (NSTimeInterval)currentTimeWithinEndTimes
197 {
198     return self.timing.currentValue;
199 }
200
201 - (void)setCurrentTimeWithinEndTimes:(NSTimeInterval)currentTimeWithinEndTimes
202 {
203     [self seekToTime:currentTimeWithinEndTimes];
204 }
205
206 + (NSSet *)keyPathsForValuesAffectingCurrentTimeWithinEndTimes
207 {
208     return [NSSet setWithObject:@"timing"];
209 }
210
211 - (BOOL)hasLiveStreamingContent
212 {
213     if ([self status] == AVPlayerControllerStatusReadyToPlay)
214         return [self contentDuration] == std::numeric_limits<float>::infinity();
215     return NO;
216 }
217
218 + (NSSet *)keyPathsForValuesAffectingHasLiveStreamingContent
219 {
220     return [NSSet setWithObjects:@"contentDuration", @"status", nil];
221 }
222
223 - (NSTimeInterval)maxTime
224 {
225     NSTimeInterval maxTime = NAN;
226
227     NSTimeInterval duration = [self contentDuration];
228     if (!isnan(duration) && !isinf(duration))
229         maxTime = duration;
230     else if ([self hasSeekableLiveStreamingContent] && [self maxTiming])
231         maxTime = [[self maxTiming] currentValue];
232
233     return maxTime;
234 }
235
236 + (NSSet *)keyPathsForValuesAffectingMaxTime
237 {
238     return [NSSet setWithObjects:@"contentDuration", @"hasSeekableLiveStreamingContent", @"maxTiming", nil];
239 }
240
241 - (NSTimeInterval)minTime
242 {
243     NSTimeInterval minTime = 0.0;
244
245     if ([self hasSeekableLiveStreamingContent] && [self minTiming])
246         minTime = [[self minTiming] currentValue];
247
248     return minTime;
249 }
250
251 + (NSSet *)keyPathsForValuesAffectingMinTime
252 {
253     return [NSSet setWithObjects:@"hasSeekableLiveStreamingContent", @"minTiming", nil];
254 }
255
256 - (void)skipBackwardThirtySeconds:(id)sender
257 {
258     using namespace PAL;
259
260     UNUSED_PARAM(sender);
261     BOOL isTimeWithinSeekableTimeRanges = NO;
262     CMTime currentTime = CMTimeMakeWithSeconds([[self timing] currentValue], 1000);
263     CMTime thirtySecondsBeforeCurrentTime = CMTimeSubtract(currentTime, PAL::CMTimeMake(30, 1));
264
265     for (NSValue *seekableTimeRangeValue in [self seekableTimeRanges]) {
266         if (CMTimeRangeContainsTime([seekableTimeRangeValue CMTimeRangeValue], thirtySecondsBeforeCurrentTime)) {
267             isTimeWithinSeekableTimeRanges = YES;
268             break;
269         }
270     }
271
272     if (isTimeWithinSeekableTimeRanges)
273         [self seekToTime:CMTimeGetSeconds(thirtySecondsBeforeCurrentTime)];
274 }
275
276 - (void)gotoEndOfSeekableRanges:(id)sender
277 {
278     using namespace PAL;
279
280     UNUSED_PARAM(sender);
281     NSTimeInterval timeAtEndOfSeekableTimeRanges = NAN;
282
283     for (NSValue *seekableTimeRangeValue in [self seekableTimeRanges]) {
284         CMTimeRange seekableTimeRange = [seekableTimeRangeValue CMTimeRangeValue];
285         NSTimeInterval endOfSeekableTimeRange = CMTimeGetSeconds(CMTimeRangeGetEnd(seekableTimeRange));
286         if (isnan(timeAtEndOfSeekableTimeRanges) || endOfSeekableTimeRange > timeAtEndOfSeekableTimeRanges)
287             timeAtEndOfSeekableTimeRanges = endOfSeekableTimeRange;
288     }
289
290     if (!isnan(timeAtEndOfSeekableTimeRanges))
291         [self seekToTime:timeAtEndOfSeekableTimeRanges];
292 }
293
294 - (BOOL)isScrubbing
295 {
296     return _isScrubbing;
297 }
298
299 - (BOOL)canScanForward
300 {
301     return [self canPlay];
302 }
303
304 + (NSSet *)keyPathsForValuesAffectingCanScanForward
305 {
306     return [NSSet setWithObject:@"canPlay"];
307 }
308
309 - (void)beginScanningForward:(id)sender
310 {
311     UNUSED_PARAM(sender);
312     if (self.delegate)
313         self.delegate->beginScanningForward();
314 }
315
316 - (void)endScanningForward:(id)sender
317 {
318     UNUSED_PARAM(sender);
319     if (self.delegate)
320         self.delegate->endScanning();
321 }
322
323 - (void)beginScanningBackward:(id)sender
324 {
325     UNUSED_PARAM(sender);
326     if (self.delegate)
327         self.delegate->beginScanningBackward();
328 }
329
330 - (void)endScanningBackward:(id)sender
331 {
332     UNUSED_PARAM(sender);
333     if (self.delegate)
334         self.delegate->endScanning();
335 }
336
337 - (BOOL)canSeekToBeginning
338 {
339     using namespace PAL;
340
341     CMTime minimumTime = kCMTimeIndefinite;
342
343     for (NSValue *value in [self seekableTimeRanges])
344         minimumTime = CMTimeMinimum([value CMTimeRangeValue].start, minimumTime);
345
346     return CMTIME_IS_NUMERIC(minimumTime);
347 }
348
349 + (NSSet *)keyPathsForValuesAffectingCanSeekToBeginning
350 {
351     return [NSSet setWithObject:@"seekableTimeRanges"];
352 }
353
354 - (void)seekToBeginning:(id)sender
355 {
356     UNUSED_PARAM(sender);
357     if (self.delegate)
358         self.delegate->seekToTime(-INFINITY);
359 }
360
361 - (void)seekChapterBackward:(id)sender
362 {
363     [self seekToBeginning:sender];
364 }
365
366 - (BOOL)canSeekToEnd
367 {
368     using namespace PAL;
369
370     CMTime maximumTime = kCMTimeIndefinite;
371
372     for (NSValue *value in [self seekableTimeRanges])
373         maximumTime = CMTimeMaximum(CMTimeRangeGetEnd([value CMTimeRangeValue]), maximumTime);
374
375     return CMTIME_IS_NUMERIC(maximumTime);
376 }
377
378 + (NSSet *)keyPathsForValuesAffectingCanSeekToEnd
379 {
380     return [NSSet setWithObject:@"seekableTimeRanges"];
381 }
382
383 - (void)seekToEnd:(id)sender
384 {
385     UNUSED_PARAM(sender);
386     if (self.delegate)
387         self.delegate->seekToTime(INFINITY);
388 }
389
390 - (void)seekChapterForward:(id)sender
391 {
392     [self seekToEnd:sender];
393 }
394
395 - (BOOL)hasMediaSelectionOptions
396 {
397     return [self hasAudioMediaSelectionOptions] || [self hasLegibleMediaSelectionOptions];
398 }
399
400 + (NSSet *)keyPathsForValuesAffectingHasMediaSelectionOptions
401 {
402     return [NSSet setWithObjects:@"hasAudioMediaSelectionOptions", @"hasLegibleMediaSelectionOptions", nil];
403 }
404
405 - (BOOL)hasAudioMediaSelectionOptions
406 {
407     return [[self audioMediaSelectionOptions] count] > 1;
408 }
409
410 + (NSSet *)keyPathsForValuesAffectingHasAudioMediaSelectionOptions
411 {
412     return [NSSet setWithObject:@"audioMediaSelectionOptions"];
413 }
414
415 - (BOOL)hasLegibleMediaSelectionOptions
416 {
417     const NSUInteger numDefaultLegibleOptions = 2;
418     return [[self legibleMediaSelectionOptions] count] > numDefaultLegibleOptions;
419 }
420
421 + (NSSet *)keyPathsForValuesAffectingHasLegibleMediaSelectionOptions
422 {
423     return [NSSet setWithObject:@"legibleMediaSelectionOptions"];
424 }
425
426 - (WebAVMediaSelectionOption *)currentAudioMediaSelectionOption
427 {
428     return _currentAudioMediaSelectionOption;
429 }
430
431 - (void)setCurrentAudioMediaSelectionOption:(WebAVMediaSelectionOption *)option
432 {
433     if (option == _currentAudioMediaSelectionOption)
434         return;
435
436     [_currentAudioMediaSelectionOption release];
437     _currentAudioMediaSelectionOption = [option retain];
438
439     if (!self.delegate)
440         return;
441
442     NSInteger index = NSNotFound;
443
444     if (option && self.audioMediaSelectionOptions)
445         index = [self.audioMediaSelectionOptions indexOfObject:option];
446
447     if (index == NSNotFound)
448         return;
449
450     self.delegate->selectAudioMediaOption(index);
451 }
452
453 - (WebAVMediaSelectionOption *)currentLegibleMediaSelectionOption
454 {
455     return _currentLegibleMediaSelectionOption;
456 }
457
458 - (void)setCurrentLegibleMediaSelectionOption:(WebAVMediaSelectionOption *)option
459 {
460     if (option == _currentLegibleMediaSelectionOption)
461         return;
462
463     [_currentLegibleMediaSelectionOption release];
464     _currentLegibleMediaSelectionOption = [option retain];
465
466     if (!self.delegate)
467         return;
468
469     NSInteger index = NSNotFound;
470
471     if (option && self.legibleMediaSelectionOptions)
472         index = [self.legibleMediaSelectionOptions indexOfObject:option];
473
474     if (index == NSNotFound)
475         return;
476
477     self.delegate->selectLegibleMediaOption(index);
478 }
479
480 - (BOOL)isPlayingOnExternalScreen
481 {
482     return [self isExternalPlaybackActive] || [self isPlayingOnSecondScreen];
483 }
484
485 + (NSSet *)keyPathsForValuesAffectingPlayingOnExternalScreen
486 {
487     return [NSSet setWithObjects:@"externalPlaybackActive", @"playingOnSecondScreen", nil];
488 }
489
490 - (BOOL)isPictureInPictureInterrupted
491 {
492     return _pictureInPictureInterrupted;
493 }
494
495 - (void)setPictureInPictureInterrupted:(BOOL)pictureInPictureInterrupted
496 {
497     if (_pictureInPictureInterrupted != pictureInPictureInterrupted) {
498         _pictureInPictureInterrupted = pictureInPictureInterrupted;
499         if (pictureInPictureInterrupted)
500             [self setPlaying:NO];
501     }
502 }
503
504 - (BOOL)isMuted
505 {
506     return _muted;
507 }
508
509 - (void)setMuted:(BOOL)muted
510 {
511     if (_muted == muted)
512         return;
513     _muted = muted;
514
515     if (self.delegate)
516         self.delegate->setMuted(muted);
517 }
518
519 - (void)toggleMuted:(id)sender
520 {
521     UNUSED_PARAM(sender);
522     if (self.delegate)
523         self.delegate->toggleMuted();
524 }
525
526 - (double)volume
527 {
528     return self.delegate ? self.delegate->volume() : 0;
529 }
530
531 - (void)setVolume:(double)volume
532 {
533     if (self.delegate)
534         self.delegate->setVolume(volume);
535 }
536
537 - (void)volumeChanged:(double)volume
538 {
539     UNUSED_PARAM(volume);
540     [self willChangeValueForKey:@"volume"];
541     [self didChangeValueForKey:@"volume"];
542 }
543
544 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
545 {
546     using namespace PAL;
547
548     UNUSED_PARAM(object);
549     UNUSED_PARAM(keyPath);
550
551     if (WebAVPlayerControllerSeekableTimeRangesObserverContext == context) {
552         NSArray *oldArray = change[NSKeyValueChangeOldKey];
553         NSArray *newArray = change[NSKeyValueChangeNewKey];
554         if ([oldArray isKindOfClass:[NSArray class]] && [newArray isKindOfClass:[NSArray class]]) {
555             CMTimeRange oldSeekableTimeRange = [oldArray count] > 0 ? [[oldArray firstObject] CMTimeRangeValue] : kCMTimeRangeInvalid;
556             CMTimeRange newSeekableTimeRange = [newArray count] > 0 ? [[newArray firstObject] CMTimeRangeValue] : kCMTimeRangeInvalid;
557             if (!CMTimeRangeEqual(oldSeekableTimeRange, newSeekableTimeRange)) {
558                 if (CMTIMERANGE_IS_VALID(newSeekableTimeRange) && CMTIMERANGE_IS_VALID(oldSeekableTimeRange) && _liveStreamEventModePossible && !CMTimeRangeContainsTime(newSeekableTimeRange, oldSeekableTimeRange.start))
559                     _liveStreamEventModePossible = NO;
560
561                 [self updateMinMaxTiming];
562             }
563         }
564     } else if (WebAVPlayerControllerHasLiveStreamingContentObserverContext == context)
565         [self updateMinMaxTiming];
566     else if (WebAVPlayerControllerIsPlayingOnSecondScreenObserverContext == context) {
567         if (auto* delegate = self.delegate)
568             delegate->setPlayingOnSecondScreen(_playingOnSecondScreen);
569     }
570 }
571
572 - (void)updateMinMaxTiming
573 {
574     using namespace PAL;
575
576     AVValueTiming *newMinTiming = nil;
577     AVValueTiming *newMaxTiming = nil;
578
579     // For live streams value timings for the min and max times are needed.
580     if ([self hasLiveStreamingContent] && ([[self seekableTimeRanges] count] > 0)) {
581         CMTimeRange seekableTimeRange = [[[self seekableTimeRanges] firstObject] CMTimeRangeValue];
582         if (CMTIMERANGE_IS_VALID(seekableTimeRange)) {
583             NSTimeInterval oldMinTime = [[self minTiming] currentValue];
584             NSTimeInterval oldMaxTime = [[self maxTiming] currentValue];
585             NSTimeInterval newMinTime = CMTimeGetSeconds(seekableTimeRange.start);
586             NSTimeInterval newMaxTime = CMTimeGetSeconds(seekableTimeRange.start) + CMTimeGetSeconds(seekableTimeRange.duration) + (CACurrentMediaTime() - [self seekableTimeRangesLastModifiedTime]);
587             double newMinTimingRate = _liveStreamEventModePossible ? 0.0 : 1.0;
588             BOOL minTimingNeedsUpdate = YES;
589             BOOL maxTimingNeedsUpdate = YES;
590
591             if (isfinite([self liveUpdateInterval]) && [self liveUpdateInterval] > WebAVPlayerControllerLiveStreamMinimumTargetDuration) {
592                 // Only update the timing if the new time differs by one segment duration plus the hysteresis delta.
593                 minTimingNeedsUpdate = isnan(oldMinTime) || (fabs(oldMinTime - newMinTime) > [self liveUpdateInterval] + WebAVPlayerControllerLiveStreamSeekableTimeRangeDurationHysteresisDelta) || ([[self minTiming] rate] != newMinTimingRate);
594                 maxTimingNeedsUpdate = isnan(oldMaxTime) || (fabs(oldMaxTime - newMaxTime) > [self liveUpdateInterval] + WebAVPlayerControllerLiveStreamSeekableTimeRangeDurationHysteresisDelta);
595             }
596
597             if (minTimingNeedsUpdate || maxTimingNeedsUpdate) {
598                 newMinTiming = [getAVValueTimingClass() valueTimingWithAnchorValue:newMinTime anchorTimeStamp:[getAVValueTimingClass() currentTimeStamp] rate:newMinTimingRate];
599                 newMaxTiming = [getAVValueTimingClass() valueTimingWithAnchorValue:newMaxTime anchorTimeStamp:[getAVValueTimingClass() currentTimeStamp] rate:1.0];
600             } else {
601                 newMinTiming = [self minTiming];
602                 newMaxTiming = [self maxTiming];
603             }
604         }
605     }
606
607     if (!newMinTiming)
608         newMinTiming = [getAVValueTimingClass() valueTimingWithAnchorValue:[self minTime] anchorTimeStamp:NAN rate:0.0];
609
610     if (!newMaxTiming)
611         newMaxTiming = [getAVValueTimingClass() valueTimingWithAnchorValue:[self maxTime] anchorTimeStamp:NAN rate:0.0];
612
613     [self setMinTiming:newMinTiming];
614     [self setMaxTiming:newMaxTiming];
615 }
616
617 - (BOOL)hasSeekableLiveStreamingContent
618 {
619     BOOL hasSeekableLiveStreamingContent = NO;
620
621     if ([self hasLiveStreamingContent] && [self minTiming] && [self maxTiming] && isfinite([self liveUpdateInterval]) && [self liveUpdateInterval] > WebAVPlayerControllerLiveStreamMinimumTargetDuration && ([self seekableTimeRangesLastModifiedTime] != 0.0)) {
622         NSTimeInterval timeStamp = [getAVValueTimingClass() currentTimeStamp];
623         NSTimeInterval minTime = [[self minTiming] valueForTimeStamp:timeStamp];
624         NSTimeInterval maxTime = [[self maxTiming] valueForTimeStamp:timeStamp];
625         hasSeekableLiveStreamingContent = ((maxTime - minTime) > WebAVPlayerControllerLiveStreamSeekableTimeRangeMinimumDuration);
626     }
627
628     return hasSeekableLiveStreamingContent;
629 }
630
631 + (NSSet *)keyPathsForValuesAffectingHasSeekableLiveStreamingContent
632 {
633     return [NSSet setWithObjects:@"hasLiveStreamingContent", @"minTiming", @"maxTiming", @"seekableTimeRangesLastModifiedTime", nil];
634 }
635
636 @end
637
638 @implementation WebAVMediaSelectionOption
639
640 - (void)dealloc
641 {
642     [_localizedDisplayName release];
643     [super dealloc];
644 }
645
646 @end
647
648 #endif // PLATFORM(IOS_FAMILY)
649