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