2 * Copyright (C) 2011 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.
29 #include "MediaController.h"
32 #include "HTMLMediaElement.h"
33 #include "TimeRanges.h"
34 #include <wtf/CurrentTime.h>
35 #include <wtf/StdLibExtras.h>
36 #include <wtf/text/AtomicString.h>
38 using namespace WebCore;
40 Ref<MediaController> MediaController::create(ScriptExecutionContext& context)
42 return adoptRef(*new MediaController(context));
45 MediaController::MediaController(ScriptExecutionContext& context)
47 , m_defaultPlaybackRate(1)
49 , m_position(MediaPlayer::invalidTime())
51 , m_readyState(HAVE_NOTHING)
52 , m_playbackState(WAITING)
53 , m_asyncEventTimer(*this, &MediaController::asyncEventTimerFired)
54 , m_clearPositionTimer(*this, &MediaController::clearPositionTimerFired)
55 , m_closedCaptionsVisible(false)
56 , m_clock(Clock::create())
57 , m_scriptExecutionContext(context)
58 , m_timeupdateTimer(*this, &MediaController::scheduleTimeupdateEvent)
59 , m_previousTimeupdateTime(0)
63 MediaController::~MediaController()
67 void MediaController::addMediaElement(HTMLMediaElement* element)
70 ASSERT(!m_mediaElements.contains(element));
72 m_mediaElements.append(element);
73 bringElementUpToSpeed(element);
76 void MediaController::removeMediaElement(HTMLMediaElement* element)
79 ASSERT(m_mediaElements.contains(element));
80 m_mediaElements.remove(m_mediaElements.find(element));
83 bool MediaController::containsMediaElement(HTMLMediaElement* element) const
85 return m_mediaElements.contains(element);
88 PassRefPtr<TimeRanges> MediaController::buffered() const
90 if (m_mediaElements.isEmpty())
91 return TimeRanges::create();
93 // The buffered attribute must return a new static normalized TimeRanges object that represents
94 // the intersection of the ranges of the media resources of the slaved media elements that the
95 // user agent has buffered, at the time the attribute is evaluated.
96 RefPtr<TimeRanges> bufferedRanges = m_mediaElements.first()->buffered();
97 for (size_t index = 1; index < m_mediaElements.size(); ++index)
98 bufferedRanges->intersectWith(*m_mediaElements[index]->buffered().get());
99 return bufferedRanges;
102 PassRefPtr<TimeRanges> MediaController::seekable() const
104 if (m_mediaElements.isEmpty())
105 return TimeRanges::create();
107 // The seekable attribute must return a new static normalized TimeRanges object that represents
108 // the intersection of the ranges of the media resources of the slaved media elements that the
109 // user agent is able to seek to, at the time the attribute is evaluated.
110 RefPtr<TimeRanges> seekableRanges = m_mediaElements.first()->seekable();
111 for (size_t index = 1; index < m_mediaElements.size(); ++index)
112 seekableRanges->intersectWith(*m_mediaElements[index]->seekable().get());
113 return seekableRanges;
116 PassRefPtr<TimeRanges> MediaController::played()
118 if (m_mediaElements.isEmpty())
119 return TimeRanges::create();
121 // The played attribute must return a new static normalized TimeRanges object that represents
122 // the union of the ranges of the media resources of the slaved media elements that the
123 // user agent has so far rendered, at the time the attribute is evaluated.
124 RefPtr<TimeRanges> playedRanges = m_mediaElements.first()->played();
125 for (size_t index = 1; index < m_mediaElements.size(); ++index)
126 playedRanges->unionWith(*m_mediaElements[index]->played().get());
130 double MediaController::duration() const
132 // FIXME: Investigate caching the maximum duration and only updating the cached value
133 // when the slaved media elements' durations change.
134 double maxDuration = 0;
135 for (size_t index = 0; index < m_mediaElements.size(); ++index) {
136 double duration = m_mediaElements[index]->duration();
137 if (std::isnan(duration))
139 maxDuration = std::max(maxDuration, duration);
144 double MediaController::currentTime() const
146 if (m_mediaElements.isEmpty())
149 if (m_position == MediaPlayer::invalidTime()) {
150 // Some clocks may return times outside the range of [0..duration].
151 m_position = std::max<double>(0, std::min(duration(), m_clock->currentTime()));
152 m_clearPositionTimer.startOneShot(0);
158 void MediaController::setCurrentTime(double time)
160 // When the user agent is to seek the media controller to a particular new playback position,
161 // it must follow these steps:
162 // If the new playback position is less than zero, then set it to zero.
163 time = std::max(0.0, time);
165 // If the new playback position is greater than the media controller duration, then set it
166 // to the media controller duration.
167 time = std::min(time, duration());
169 // Set the media controller position to the new playback position.
170 m_clock->setCurrentTime(time);
172 // Seek each slaved media element to the new playback position relative to the media element timeline.
173 for (size_t index = 0; index < m_mediaElements.size(); ++index)
174 m_mediaElements[index]->seek(MediaTime::createWithDouble(time));
176 scheduleTimeupdateEvent();
177 m_resetCurrentTimeInNextPlay = false;
180 void MediaController::unpause()
182 // When the unpause() method is invoked, if the MediaController is a paused media controller,
185 // the user agent must change the MediaController into a playing media controller,
187 // queue a task to fire a simple event named play at the MediaController,
188 scheduleEvent(eventNames().playEvent);
189 // and then report the controller state of the MediaController.
190 reportControllerState();
193 void MediaController::play()
195 // When the play() method is invoked, the user agent must invoke the play method of each
196 // slaved media element in turn,
197 for (size_t index = 0; index < m_mediaElements.size(); ++index)
198 m_mediaElements[index]->play();
200 // and then invoke the unpause method of the MediaController.
204 void MediaController::pause()
206 // When the pause() method is invoked, if the MediaController is a playing media controller,
210 // then the user agent must change the MediaController into a paused media controller,
212 // queue a task to fire a simple event named pause at the MediaController,
213 scheduleEvent(eventNames().pauseEvent);
214 // and then report the controller state of the MediaController.
215 reportControllerState();
218 void MediaController::setDefaultPlaybackRate(double rate)
220 if (m_defaultPlaybackRate == rate)
223 // The defaultPlaybackRate attribute, on setting, must set the MediaController's media controller
224 // default playback rate to the new value,
225 m_defaultPlaybackRate = rate;
227 // then queue a task to fire a simple event named ratechange at the MediaController.
228 scheduleEvent(eventNames().ratechangeEvent);
231 double MediaController::playbackRate() const
233 return m_clock->playRate();
236 void MediaController::setPlaybackRate(double rate)
238 if (m_clock->playRate() == rate)
241 // The playbackRate attribute, on setting, must set the MediaController's media controller
242 // playback rate to the new value,
243 m_clock->setPlayRate(rate);
245 for (size_t index = 0; index < m_mediaElements.size(); ++index)
246 m_mediaElements[index]->updatePlaybackRate();
248 // then queue a task to fire a simple event named ratechange at the MediaController.
249 scheduleEvent(eventNames().ratechangeEvent);
252 void MediaController::setVolume(double level, ExceptionCode& code)
254 if (m_volume == level)
257 // If the new value is outside the range 0.0 to 1.0 inclusive, then, on setting, an
258 // IndexSizeError exception must be raised instead.
259 if (level < 0 || level > 1) {
260 code = INDEX_SIZE_ERR;
264 // The volume attribute, on setting, if the new value is in the range 0.0 to 1.0 inclusive,
265 // must set the MediaController's media controller volume multiplier to the new value
268 // and queue a task to fire a simple event named volumechange at the MediaController.
269 scheduleEvent(eventNames().volumechangeEvent);
271 for (size_t index = 0; index < m_mediaElements.size(); ++index)
272 m_mediaElements[index]->updateVolume();
275 void MediaController::setMuted(bool flag)
280 // The muted attribute, on setting, must set the MediaController's media controller mute override
284 // and queue a task to fire a simple event named volumechange at the MediaController.
285 scheduleEvent(eventNames().volumechangeEvent);
287 for (size_t index = 0; index < m_mediaElements.size(); ++index)
288 m_mediaElements[index]->updateVolume();
291 static const AtomicString& playbackStateWaiting()
293 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, waiting, ("waiting", AtomicString::ConstructFromLiteral));
297 static const AtomicString& playbackStatePlaying()
299 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, playing, ("playing", AtomicString::ConstructFromLiteral));
303 static const AtomicString& playbackStateEnded()
305 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, ended, ("ended", AtomicString::ConstructFromLiteral));
309 const AtomicString& MediaController::playbackState() const
311 switch (m_playbackState) {
313 return playbackStateWaiting();
315 return playbackStatePlaying();
317 return playbackStateEnded();
319 ASSERT_NOT_REACHED();
324 void MediaController::reportControllerState()
327 updatePlaybackState();
330 static AtomicString eventNameForReadyState(MediaControllerInterface::ReadyState state)
333 case MediaControllerInterface::HAVE_NOTHING:
334 return eventNames().emptiedEvent;
335 case MediaControllerInterface::HAVE_METADATA:
336 return eventNames().loadedmetadataEvent;
337 case MediaControllerInterface::HAVE_CURRENT_DATA:
338 return eventNames().loadeddataEvent;
339 case MediaControllerInterface::HAVE_FUTURE_DATA:
340 return eventNames().canplayEvent;
341 case MediaControllerInterface::HAVE_ENOUGH_DATA:
342 return eventNames().canplaythroughEvent;
344 ASSERT_NOT_REACHED();
349 void MediaController::updateReadyState()
351 ReadyState oldReadyState = m_readyState;
352 ReadyState newReadyState;
354 if (m_mediaElements.isEmpty()) {
355 // If the MediaController has no slaved media elements, let new readiness state be 0.
356 newReadyState = HAVE_NOTHING;
358 // Otherwise, let it have the lowest value of the readyState IDL attributes of all of its
359 // slaved media elements.
360 newReadyState = m_mediaElements.first()->readyState();
361 for (size_t index = 1; index < m_mediaElements.size(); ++index)
362 newReadyState = std::min(newReadyState, m_mediaElements[index]->readyState());
365 if (newReadyState == oldReadyState)
368 // If the MediaController's most recently reported readiness state is greater than new readiness
369 // state then queue a task to fire a simple event at the MediaController object, whose name is the
370 // event name corresponding to the value of new readiness state given in the table below. [omitted]
371 if (oldReadyState > newReadyState) {
372 scheduleEvent(eventNameForReadyState(newReadyState));
376 // If the MediaController's most recently reported readiness state is less than the new readiness
377 // state, then run these substeps:
378 // 1. Let next state be the MediaController's most recently reported readiness state.
379 ReadyState nextState = oldReadyState;
381 // 2. Loop: Increment next state by one.
382 nextState = static_cast<ReadyState>(nextState + 1);
383 // 3. Queue a task to fire a simple event at the MediaController object, whose name is the
384 // event name corresponding to the value of next state given in the table below. [omitted]
385 scheduleEvent(eventNameForReadyState(nextState));
386 // If next state is less than new readiness state, then return to the step labeled loop
387 } while (nextState < newReadyState);
389 // Let the MediaController's most recently reported readiness state be new readiness state.
390 m_readyState = newReadyState;
393 void MediaController::updatePlaybackState()
395 PlaybackState oldPlaybackState = m_playbackState;
396 PlaybackState newPlaybackState;
398 // Initialize new playback state by setting it to the state given for the first matching
399 // condition from the following list:
400 if (m_mediaElements.isEmpty()) {
401 // If the MediaController has no slaved media elements
402 // Let new playback state be waiting.
403 newPlaybackState = WAITING;
404 } else if (hasEnded()) {
405 // If all of the MediaController's slaved media elements have ended playback and the media
406 // controller playback rate is positive or zero
407 // Let new playback state be ended.
408 newPlaybackState = ENDED;
409 } else if (isBlocked()) {
410 // If the MediaController is a blocked media controller
411 // Let new playback state be waiting.
412 newPlaybackState = WAITING;
415 // Let new playback state be playing.
416 newPlaybackState = PLAYING;
419 // If the MediaController's most recently reported playback state is not equal to new playback state
420 if (newPlaybackState == oldPlaybackState)
423 // and the new playback state is ended,
424 if (newPlaybackState == ENDED) {
425 // then queue a task that, if the MediaController object is a playing media controller, and
426 // all of the MediaController's slaved media elements have still ended playback, and the
427 // media controller playback rate is still positive or zero,
428 if (!m_paused && hasEnded()) {
429 // changes the MediaController object to a paused media controller
432 // and then fires a simple event named pause at the MediaController object.
433 scheduleEvent(eventNames().pauseEvent);
437 // If the MediaController's most recently reported playback state is not equal to new playback state
438 // then queue a task to fire a simple event at the MediaController object, whose name is playing
439 // if new playback state is playing, ended if new playback state is ended, and waiting otherwise.
440 AtomicString eventName;
441 switch (newPlaybackState) {
443 eventName = eventNames().waitingEvent;
445 m_timeupdateTimer.stop();
448 eventName = eventNames().endedEvent;
449 m_resetCurrentTimeInNextPlay = true;
451 m_timeupdateTimer.stop();
454 if (m_resetCurrentTimeInNextPlay) {
455 m_resetCurrentTimeInNextPlay = false;
456 m_clock->setCurrentTime(0);
458 eventName = eventNames().playingEvent;
460 startTimeupdateTimer();
463 ASSERT_NOT_REACHED();
465 scheduleEvent(eventName);
467 // Let the MediaController's most recently reported playback state be new playback state.
468 m_playbackState = newPlaybackState;
470 updateMediaElements();
473 void MediaController::updateMediaElements()
475 for (size_t index = 0; index < m_mediaElements.size(); ++index)
476 m_mediaElements[index]->updatePlayState();
479 void MediaController::bringElementUpToSpeed(HTMLMediaElement* element)
482 ASSERT(m_mediaElements.contains(element));
484 // When the user agent is to bring a media element up to speed with its new media controller,
485 // it must seek that media element to the MediaController's media controller position relative
486 // to the media element's timeline.
487 element->seekInternal(MediaTime::createWithDouble(currentTime()));
490 bool MediaController::isBlocked() const
492 // A MediaController is a blocked media controller if the MediaController is a paused media
497 if (m_mediaElements.isEmpty())
500 bool allPaused = true;
501 for (size_t index = 0; index < m_mediaElements.size(); ++index) {
502 HTMLMediaElement* element = m_mediaElements[index];
503 // or if any of its slaved media elements are blocked media elements,
504 if (element->isBlocked())
507 // or if any of its slaved media elements whose autoplaying flag is true still have their
508 // paused attribute set to true,
509 if (element->isAutoplaying() && element->paused())
512 if (!element->paused())
516 // or if all of its slaved media elements have their paused attribute set to true.
520 bool MediaController::hasEnded() const
522 // If the ... media controller playback rate is positive or zero
523 if (m_clock->playRate() < 0)
526 // [and] all of the MediaController's slaved media elements have ended playback ... let new
527 // playback state be ended.
528 if (m_mediaElements.isEmpty())
531 bool allHaveEnded = true;
532 for (size_t index = 0; index < m_mediaElements.size(); ++index) {
533 if (!m_mediaElements[index]->ended())
534 allHaveEnded = false;
539 void MediaController::scheduleEvent(const AtomicString& eventName)
541 m_pendingEvents.append(Event::create(eventName, false, true));
542 if (!m_asyncEventTimer.isActive())
543 m_asyncEventTimer.startOneShot(0);
546 void MediaController::asyncEventTimerFired()
548 Vector<RefPtr<Event>> pendingEvents;
550 m_pendingEvents.swap(pendingEvents);
551 size_t count = pendingEvents.size();
552 for (size_t index = 0; index < count; ++index)
553 dispatchEvent(pendingEvents[index].release(), IGNORE_EXCEPTION);
556 void MediaController::clearPositionTimerFired()
558 m_position = MediaPlayer::invalidTime();
561 bool MediaController::hasAudio() const
563 for (size_t index = 0; index < m_mediaElements.size(); ++index) {
564 if (m_mediaElements[index]->hasAudio())
570 bool MediaController::hasVideo() const
572 for (size_t index = 0; index < m_mediaElements.size(); ++index) {
573 if (m_mediaElements[index]->hasVideo())
579 bool MediaController::hasClosedCaptions() const
581 for (size_t index = 0; index < m_mediaElements.size(); ++index) {
582 if (m_mediaElements[index]->hasClosedCaptions())
588 void MediaController::setClosedCaptionsVisible(bool visible)
590 m_closedCaptionsVisible = visible;
591 for (size_t index = 0; index < m_mediaElements.size(); ++index)
592 m_mediaElements[index]->setClosedCaptionsVisible(visible);
595 bool MediaController::supportsScanning() const
597 for (size_t index = 0; index < m_mediaElements.size(); ++index) {
598 if (!m_mediaElements[index]->supportsScanning())
604 void MediaController::beginScrubbing()
606 for (size_t index = 0; index < m_mediaElements.size(); ++index)
607 m_mediaElements[index]->beginScrubbing();
608 if (m_playbackState == PLAYING)
612 void MediaController::endScrubbing()
614 for (size_t index = 0; index < m_mediaElements.size(); ++index)
615 m_mediaElements[index]->endScrubbing();
616 if (m_playbackState == PLAYING)
620 void MediaController::beginScanning(ScanDirection direction)
622 for (auto& mediaElement : m_mediaElements)
623 mediaElement->beginScanning(direction);
626 void MediaController::endScanning()
628 for (auto& mediaElement : m_mediaElements)
629 mediaElement->endScanning();
632 bool MediaController::canPlay() const
637 for (size_t index = 0; index < m_mediaElements.size(); ++index) {
638 if (!m_mediaElements[index]->canPlay())
644 bool MediaController::isLiveStream() const
646 for (size_t index = 0; index < m_mediaElements.size(); ++index) {
647 if (!m_mediaElements[index]->isLiveStream())
653 bool MediaController::hasCurrentSrc() const
655 for (size_t index = 0; index < m_mediaElements.size(); ++index) {
656 if (!m_mediaElements[index]->hasCurrentSrc())
662 void MediaController::returnToRealtime()
664 for (size_t index = 0; index < m_mediaElements.size(); ++index)
665 m_mediaElements[index]->returnToRealtime();
668 // The spec says to fire periodic timeupdate events (those sent while playing) every
669 // "15 to 250ms", we choose the slowest frequency
670 static const double maxTimeupdateEventFrequency = 0.25;
672 void MediaController::startTimeupdateTimer()
674 if (m_timeupdateTimer.isActive())
677 m_timeupdateTimer.startRepeating(maxTimeupdateEventFrequency);
680 void MediaController::scheduleTimeupdateEvent()
682 double now = monotonicallyIncreasingTime();
683 double timedelta = now - m_previousTimeupdateTime;
685 if (timedelta < maxTimeupdateEventFrequency)
688 scheduleEvent(eventNames().timeupdateEvent);
689 m_previousTimeupdateTime = now;