1/*
2 * Copyright (C) 2011 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#include "config.h"
27
28#if ENABLE(VIDEO)
29#include "MediaController.h"
30
31#include "EventNames.h"
32#include "HTMLMediaElement.h"
33#include "TimeRanges.h"
34#include <pal/system/Clock.h>
35#include <wtf/IsoMallocInlines.h>
36#include <wtf/NeverDestroyed.h>
37#include <wtf/StdLibExtras.h>
38#include <wtf/text/AtomicString.h>
39
40namespace WebCore {
41
42WTF_MAKE_ISO_ALLOCATED_IMPL(MediaController);
43
44Ref<MediaController> MediaController::create(ScriptExecutionContext& context)
45{
46 return adoptRef(*new MediaController(context));
47}
48
49MediaController::MediaController(ScriptExecutionContext& context)
50 : m_paused(false)
51 , m_defaultPlaybackRate(1)
52 , m_volume(1)
53 , m_position(MediaPlayer::invalidTime())
54 , m_muted(false)
55 , m_readyState(HAVE_NOTHING)
56 , m_playbackState(WAITING)
57 , m_asyncEventTimer(*this, &MediaController::asyncEventTimerFired)
58 , m_clearPositionTimer(*this, &MediaController::clearPositionTimerFired)
59 , m_closedCaptionsVisible(false)
60 , m_clock(PAL::Clock::create())
61 , m_scriptExecutionContext(context)
62 , m_timeupdateTimer(*this, &MediaController::scheduleTimeupdateEvent)
63{
64}
65
66MediaController::~MediaController() = default;
67
68void MediaController::addMediaElement(HTMLMediaElement& element)
69{
70 ASSERT(!m_mediaElements.contains(&element));
71
72 m_mediaElements.append(&element);
73 bringElementUpToSpeed(element);
74}
75
76void MediaController::removeMediaElement(HTMLMediaElement& element)
77{
78 ASSERT(m_mediaElements.contains(&element));
79 m_mediaElements.remove(m_mediaElements.find(&element));
80}
81
82bool MediaController::containsMediaElement(HTMLMediaElement& element) const
83{
84 return m_mediaElements.contains(&element);
85}
86
87Ref<TimeRanges> MediaController::buffered() const
88{
89 if (m_mediaElements.isEmpty())
90 return TimeRanges::create();
91
92 // The buffered attribute must return a new static normalized TimeRanges object that represents
93 // the intersection of the ranges of the media resources of the slaved media elements that the
94 // user agent has buffered, at the time the attribute is evaluated.
95 Ref<TimeRanges> bufferedRanges = m_mediaElements.first()->buffered();
96 for (size_t index = 1; index < m_mediaElements.size(); ++index)
97 bufferedRanges->intersectWith(m_mediaElements[index]->buffered());
98 return bufferedRanges;
99}
100
101Ref<TimeRanges> MediaController::seekable() const
102{
103 if (m_mediaElements.isEmpty())
104 return TimeRanges::create();
105
106 // The seekable attribute must return a new static normalized TimeRanges object that represents
107 // the intersection of the ranges of the media resources of the slaved media elements that the
108 // user agent is able to seek to, at the time the attribute is evaluated.
109 Ref<TimeRanges> seekableRanges = m_mediaElements.first()->seekable();
110 for (size_t index = 1; index < m_mediaElements.size(); ++index)
111 seekableRanges->intersectWith(m_mediaElements[index]->seekable());
112 return seekableRanges;
113}
114
115Ref<TimeRanges> MediaController::played()
116{
117 if (m_mediaElements.isEmpty())
118 return TimeRanges::create();
119
120 // The played attribute must return a new static normalized TimeRanges object that represents
121 // the union of the ranges of the media resources of the slaved media elements that the
122 // user agent has so far rendered, at the time the attribute is evaluated.
123 Ref<TimeRanges> playedRanges = m_mediaElements.first()->played();
124 for (size_t index = 1; index < m_mediaElements.size(); ++index)
125 playedRanges->unionWith(m_mediaElements[index]->played());
126 return playedRanges;
127}
128
129double MediaController::duration() const
130{
131 // FIXME: Investigate caching the maximum duration and only updating the cached value
132 // when the slaved media elements' durations change.
133 double maxDuration = 0;
134 for (auto& mediaElement : m_mediaElements) {
135 double duration = mediaElement->duration();
136 if (std::isnan(duration))
137 continue;
138 maxDuration = std::max(maxDuration, duration);
139 }
140 return maxDuration;
141}
142
143double MediaController::currentTime() const
144{
145 if (m_mediaElements.isEmpty())
146 return 0;
147
148 if (m_position == MediaPlayer::invalidTime()) {
149 // Some clocks may return times outside the range of [0..duration].
150 m_position = std::max<double>(0, std::min(duration(), m_clock->currentTime()));
151 m_clearPositionTimer.startOneShot(0_s);
152 }
153
154 return m_position;
155}
156
157void MediaController::setCurrentTime(double time)
158{
159 // When the user agent is to seek the media controller to a particular new playback position,
160 // it must follow these steps:
161 // If the new playback position is less than zero, then set it to zero.
162 time = std::max(0.0, time);
163
164 // If the new playback position is greater than the media controller duration, then set it
165 // to the media controller duration.
166 time = std::min(time, duration());
167
168 // Set the media controller position to the new playback position.
169 m_clock->setCurrentTime(time);
170
171 // Seek each slaved media element to the new playback position relative to the media element timeline.
172 for (auto& mediaElement : m_mediaElements)
173 mediaElement->seek(MediaTime::createWithDouble(time));
174
175 scheduleTimeupdateEvent();
176 m_resetCurrentTimeInNextPlay = false;
177}
178
179void MediaController::unpause()
180{
181 // When the unpause() method is invoked, if the MediaController is a paused media controller,
182 if (!m_paused)
183 return;
184 // the user agent must change the MediaController into a playing media controller,
185 m_paused = false;
186 // queue a task to fire a simple event named play at the MediaController,
187 scheduleEvent(eventNames().playEvent);
188 // and then report the controller state of the MediaController.
189 reportControllerState();
190}
191
192void MediaController::play()
193{
194 // When the play() method is invoked, the user agent must invoke the play method of each
195 // slaved media element in turn,
196 for (auto& mediaElement : m_mediaElements)
197 mediaElement->play();
198
199 // and then invoke the unpause method of the MediaController.
200 unpause();
201}
202
203void MediaController::pause()
204{
205 // When the pause() method is invoked, if the MediaController is a playing media controller,
206 if (m_paused)
207 return;
208
209 // then the user agent must change the MediaController into a paused media controller,
210 m_paused = true;
211 // queue a task to fire a simple event named pause at the MediaController,
212 scheduleEvent(eventNames().pauseEvent);
213 // and then report the controller state of the MediaController.
214 reportControllerState();
215}
216
217void MediaController::setDefaultPlaybackRate(double rate)
218{
219 if (m_defaultPlaybackRate == rate)
220 return;
221
222 // The defaultPlaybackRate attribute, on setting, must set the MediaController's media controller
223 // default playback rate to the new value,
224 m_defaultPlaybackRate = rate;
225
226 // then queue a task to fire a simple event named ratechange at the MediaController.
227 scheduleEvent(eventNames().ratechangeEvent);
228}
229
230double MediaController::playbackRate() const
231{
232 return m_clock->playRate();
233}
234
235void MediaController::setPlaybackRate(double rate)
236{
237 if (m_clock->playRate() == rate)
238 return;
239
240 // The playbackRate attribute, on setting, must set the MediaController's media controller
241 // playback rate to the new value,
242 m_clock->setPlayRate(rate);
243
244 for (auto& mediaElement : m_mediaElements)
245 mediaElement->updatePlaybackRate();
246
247 // then queue a task to fire a simple event named ratechange at the MediaController.
248 scheduleEvent(eventNames().ratechangeEvent);
249}
250
251ExceptionOr<void> MediaController::setVolume(double level)
252{
253 if (m_volume == level)
254 return { };
255
256 // If the new value is outside the range 0.0 to 1.0 inclusive, then, on setting, an
257 // IndexSizeError exception must be raised instead.
258 if (!(level >= 0 && level <= 1))
259 return Exception { IndexSizeError };
260
261 // The volume attribute, on setting, if the new value is in the range 0.0 to 1.0 inclusive,
262 // must set the MediaController's media controller volume multiplier to the new value
263 m_volume = level;
264
265 // and queue a task to fire a simple event named volumechange at the MediaController.
266 scheduleEvent(eventNames().volumechangeEvent);
267
268 for (auto& mediaElement : m_mediaElements)
269 mediaElement->updateVolume();
270
271 return { };
272}
273
274void MediaController::setMuted(bool flag)
275{
276 if (m_muted == flag)
277 return;
278
279 // The muted attribute, on setting, must set the MediaController's media controller mute override
280 // to the new value
281 m_muted = flag;
282
283 // and queue a task to fire a simple event named volumechange at the MediaController.
284 scheduleEvent(eventNames().volumechangeEvent);
285
286 for (auto& mediaElement : m_mediaElements)
287 mediaElement->updateVolume();
288}
289
290static const AtomicString& playbackStateWaiting()
291{
292 static NeverDestroyed<AtomicString> waiting("waiting", AtomicString::ConstructFromLiteral);
293 return waiting;
294}
295
296static const AtomicString& playbackStatePlaying()
297{
298 static NeverDestroyed<AtomicString> playing("playing", AtomicString::ConstructFromLiteral);
299 return playing;
300}
301
302static const AtomicString& playbackStateEnded()
303{
304 static NeverDestroyed<AtomicString> ended("ended", AtomicString::ConstructFromLiteral);
305 return ended;
306}
307
308const AtomicString& MediaController::playbackState() const
309{
310 switch (m_playbackState) {
311 case WAITING:
312 return playbackStateWaiting();
313 case PLAYING:
314 return playbackStatePlaying();
315 case ENDED:
316 return playbackStateEnded();
317 default:
318 ASSERT_NOT_REACHED();
319 return nullAtom();
320 }
321}
322
323void MediaController::reportControllerState()
324{
325 updateReadyState();
326 updatePlaybackState();
327}
328
329static AtomicString eventNameForReadyState(MediaControllerInterface::ReadyState state)
330{
331 switch (state) {
332 case MediaControllerInterface::HAVE_NOTHING:
333 return eventNames().emptiedEvent;
334 case MediaControllerInterface::HAVE_METADATA:
335 return eventNames().loadedmetadataEvent;
336 case MediaControllerInterface::HAVE_CURRENT_DATA:
337 return eventNames().loadeddataEvent;
338 case MediaControllerInterface::HAVE_FUTURE_DATA:
339 return eventNames().canplayEvent;
340 case MediaControllerInterface::HAVE_ENOUGH_DATA:
341 return eventNames().canplaythroughEvent;
342 default:
343 ASSERT_NOT_REACHED();
344 return nullAtom();
345 }
346}
347
348void MediaController::updateReadyState()
349{
350 ReadyState oldReadyState = m_readyState;
351 ReadyState newReadyState;
352
353 if (m_mediaElements.isEmpty()) {
354 // If the MediaController has no slaved media elements, let new readiness state be 0.
355 newReadyState = HAVE_NOTHING;
356 } else {
357 // Otherwise, let it have the lowest value of the readyState IDL attributes of all of its
358 // slaved media elements.
359 newReadyState = m_mediaElements.first()->readyState();
360 for (size_t index = 1; index < m_mediaElements.size(); ++index)
361 newReadyState = std::min(newReadyState, m_mediaElements[index]->readyState());
362 }
363
364 if (newReadyState == oldReadyState)
365 return;
366
367 // If the MediaController's most recently reported readiness state is greater than new readiness
368 // state then queue a task to fire a simple event at the MediaController object, whose name is the
369 // event name corresponding to the value of new readiness state given in the table below. [omitted]
370 if (oldReadyState > newReadyState) {
371 scheduleEvent(eventNameForReadyState(newReadyState));
372 return;
373 }
374
375 // If the MediaController's most recently reported readiness state is less than the new readiness
376 // state, then run these substeps:
377 // 1. Let next state be the MediaController's most recently reported readiness state.
378 ReadyState nextState = oldReadyState;
379 do {
380 // 2. Loop: Increment next state by one.
381 nextState = static_cast<ReadyState>(nextState + 1);
382 // 3. Queue a task to fire a simple event at the MediaController object, whose name is the
383 // event name corresponding to the value of next state given in the table below. [omitted]
384 scheduleEvent(eventNameForReadyState(nextState));
385 // If next state is less than new readiness state, then return to the step labeled loop
386 } while (nextState < newReadyState);
387
388 // Let the MediaController's most recently reported readiness state be new readiness state.
389 m_readyState = newReadyState;
390}
391
392void MediaController::updatePlaybackState()
393{
394 PlaybackState oldPlaybackState = m_playbackState;
395 PlaybackState newPlaybackState;
396
397 // Initialize new playback state by setting it to the state given for the first matching
398 // condition from the following list:
399 if (m_mediaElements.isEmpty()) {
400 // If the MediaController has no slaved media elements
401 // Let new playback state be waiting.
402 newPlaybackState = WAITING;
403 } else if (hasEnded()) {
404 // If all of the MediaController's slaved media elements have ended playback and the media
405 // controller playback rate is positive or zero
406 // Let new playback state be ended.
407 newPlaybackState = ENDED;
408 } else if (isBlocked()) {
409 // If the MediaController is a blocked media controller
410 // Let new playback state be waiting.
411 newPlaybackState = WAITING;
412 } else {
413 // Otherwise
414 // Let new playback state be playing.
415 newPlaybackState = PLAYING;
416 }
417
418 // If the MediaController's most recently reported playback state is not equal to new playback state
419 if (newPlaybackState == oldPlaybackState)
420 return;
421
422 // and the new playback state is ended,
423 if (newPlaybackState == ENDED) {
424 // then queue a task that, if the MediaController object is a playing media controller, and
425 // all of the MediaController's slaved media elements have still ended playback, and the
426 // media controller playback rate is still positive or zero,
427 if (!m_paused && hasEnded()) {
428 // changes the MediaController object to a paused media controller
429 m_paused = true;
430
431 // and then fires a simple event named pause at the MediaController object.
432 scheduleEvent(eventNames().pauseEvent);
433 }
434 }
435
436 // If the MediaController's most recently reported playback state is not equal to new playback state
437 // then queue a task to fire a simple event at the MediaController object, whose name is playing
438 // if new playback state is playing, ended if new playback state is ended, and waiting otherwise.
439 AtomicString eventName;
440 switch (newPlaybackState) {
441 case WAITING:
442 eventName = eventNames().waitingEvent;
443 m_clock->stop();
444 m_timeupdateTimer.stop();
445 break;
446 case ENDED:
447 eventName = eventNames().endedEvent;
448 m_resetCurrentTimeInNextPlay = true;
449 m_clock->stop();
450 m_timeupdateTimer.stop();
451 break;
452 case PLAYING:
453 if (m_resetCurrentTimeInNextPlay) {
454 m_resetCurrentTimeInNextPlay = false;
455 m_clock->setCurrentTime(0);
456 }
457 eventName = eventNames().playingEvent;
458 m_clock->start();
459 startTimeupdateTimer();
460 break;
461 default:
462 ASSERT_NOT_REACHED();
463 }
464 scheduleEvent(eventName);
465
466 // Let the MediaController's most recently reported playback state be new playback state.
467 m_playbackState = newPlaybackState;
468
469 updateMediaElements();
470}
471
472void MediaController::updateMediaElements()
473{
474 for (auto& mediaElement : m_mediaElements)
475 mediaElement->updatePlayState();
476}
477
478void MediaController::bringElementUpToSpeed(HTMLMediaElement& element)
479{
480 ASSERT(m_mediaElements.contains(&element));
481
482 // When the user agent is to bring a media element up to speed with its new media controller,
483 // it must seek that media element to the MediaController's media controller position relative
484 // to the media element's timeline.
485 element.seekInternal(MediaTime::createWithDouble(currentTime()));
486}
487
488bool MediaController::isBlocked() const
489{
490 // A MediaController is a blocked media controller if the MediaController is a paused media
491 // controller,
492 if (m_paused)
493 return true;
494
495 if (m_mediaElements.isEmpty())
496 return false;
497
498 bool allPaused = true;
499 for (auto& element : m_mediaElements) {
500 // or if any of its slaved media elements are blocked media elements,
501 if (element->isBlocked())
502 return true;
503
504 // or if any of its slaved media elements whose autoplaying flag is true still have their
505 // paused attribute set to true,
506 if (element->isAutoplaying() && element->paused())
507 return true;
508
509 if (!element->paused())
510 allPaused = false;
511 }
512
513 // or if all of its slaved media elements have their paused attribute set to true.
514 return allPaused;
515}
516
517bool MediaController::hasEnded() const
518{
519 // If the ... media controller playback rate is positive or zero
520 if (m_clock->playRate() < 0)
521 return false;
522
523 // [and] all of the MediaController's slaved media elements have ended playback ... let new
524 // playback state be ended.
525 if (m_mediaElements.isEmpty())
526 return false;
527
528 bool allHaveEnded = true;
529 for (auto& mediaElement : m_mediaElements) {
530 if (!mediaElement->ended())
531 allHaveEnded = false;
532 }
533 return allHaveEnded;
534}
535
536void MediaController::scheduleEvent(const AtomicString& eventName)
537{
538 m_pendingEvents.append(Event::create(eventName, Event::CanBubble::No, Event::IsCancelable::Yes));
539 if (!m_asyncEventTimer.isActive())
540 m_asyncEventTimer.startOneShot(0_s);
541}
542
543void MediaController::asyncEventTimerFired()
544{
545 Vector<Ref<Event>> pendingEvents;
546
547 m_pendingEvents.swap(pendingEvents);
548 for (auto& pendingEvent : pendingEvents)
549 dispatchEvent(pendingEvent);
550}
551
552void MediaController::clearPositionTimerFired()
553{
554 m_position = MediaPlayer::invalidTime();
555}
556
557bool MediaController::hasAudio() const
558{
559 for (auto& mediaElement : m_mediaElements) {
560 if (mediaElement->hasAudio())
561 return true;
562 }
563 return false;
564}
565
566bool MediaController::hasVideo() const
567{
568 for (auto& mediaElement : m_mediaElements) {
569 if (mediaElement->hasVideo())
570 return true;
571 }
572 return false;
573}
574
575bool MediaController::hasClosedCaptions() const
576{
577 for (auto& mediaElement : m_mediaElements) {
578 if (mediaElement->hasClosedCaptions())
579 return true;
580 }
581 return false;
582}
583
584void MediaController::setClosedCaptionsVisible(bool visible)
585{
586 m_closedCaptionsVisible = visible;
587 for (auto& mediaElement : m_mediaElements)
588 mediaElement->setClosedCaptionsVisible(visible);
589}
590
591bool MediaController::supportsScanning() const
592{
593 for (auto& mediaElement : m_mediaElements) {
594 if (!mediaElement->supportsScanning())
595 return false;
596 }
597 return true;
598}
599
600void MediaController::beginScrubbing()
601{
602 for (auto& mediaElement : m_mediaElements)
603 mediaElement->beginScrubbing();
604 if (m_playbackState == PLAYING)
605 m_clock->stop();
606}
607
608void MediaController::endScrubbing()
609{
610 for (auto& mediaElement : m_mediaElements)
611 mediaElement->endScrubbing();
612 if (m_playbackState == PLAYING)
613 m_clock->start();
614}
615
616void MediaController::beginScanning(ScanDirection direction)
617{
618 for (auto& mediaElement : m_mediaElements)
619 mediaElement->beginScanning(direction);
620}
621
622void MediaController::endScanning()
623{
624 for (auto& mediaElement : m_mediaElements)
625 mediaElement->endScanning();
626}
627
628bool MediaController::canPlay() const
629{
630 if (m_paused)
631 return true;
632
633 for (auto& mediaElement : m_mediaElements) {
634 if (!mediaElement->canPlay())
635 return false;
636 }
637 return true;
638}
639
640bool MediaController::isLiveStream() const
641{
642 for (auto& mediaElement : m_mediaElements) {
643 if (!mediaElement->isLiveStream())
644 return false;
645 }
646 return true;
647}
648
649bool MediaController::hasCurrentSrc() const
650{
651 for (auto& mediaElement : m_mediaElements) {
652 if (!mediaElement->hasCurrentSrc())
653 return false;
654 }
655 return true;
656}
657
658void MediaController::returnToRealtime()
659{
660 for (auto& mediaElement : m_mediaElements)
661 mediaElement->returnToRealtime();
662}
663
664// The spec says to fire periodic timeupdate events (those sent while playing) every
665// "15 to 250ms", we choose the slowest frequency
666static const Seconds maxTimeupdateEventFrequency { 250_ms };
667
668void MediaController::startTimeupdateTimer()
669{
670 if (m_timeupdateTimer.isActive())
671 return;
672
673 m_timeupdateTimer.startRepeating(maxTimeupdateEventFrequency);
674}
675
676void MediaController::scheduleTimeupdateEvent()
677{
678 MonotonicTime now = MonotonicTime::now();
679 Seconds timedelta = now - m_previousTimeupdateTime;
680
681 if (timedelta < maxTimeupdateEventFrequency)
682 return;
683
684 scheduleEvent(eventNames().timeupdateEvent);
685 m_previousTimeupdateTime = now;
686}
687
688} // namespace WebCore
689
690#endif
691