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 COMPUTER, 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 COMPUTER, 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.
27 #include "core/html/MediaController.h"
29 #include "bindings/v8/ExceptionMessages.h"
30 #include "bindings/v8/ExceptionState.h"
31 #include "bindings/v8/ExceptionStatePlaceholder.h"
32 #include "core/dom/ExceptionCode.h"
33 #include "core/html/HTMLMediaElement.h"
34 #include "core/html/TimeRanges.h"
35 #include "platform/Clock.h"
36 #include "wtf/CurrentTime.h"
37 #include "wtf/StdLibExtras.h"
38 #include "wtf/text/AtomicString.h"
40 using namespace WebCore;
43 PassRefPtr<MediaController> MediaController::create(ExecutionContext* context)
45 return adoptRef(new MediaController(context));
48 MediaController::MediaController(ExecutionContext* context)
50 , m_defaultPlaybackRate(1)
52 , m_position(MediaPlayer::invalidTime())
54 , m_readyState(HTMLMediaElement::HAVE_NOTHING)
55 , m_playbackState(WAITING)
56 , m_asyncEventTimer(this, &MediaController::asyncEventTimerFired)
57 , m_clearPositionTimer(this, &MediaController::clearPositionTimerFired)
58 , m_clock(Clock::create())
59 , m_executionContext(context)
60 , m_timeupdateTimer(this, &MediaController::timeupdateTimerFired)
61 , m_previousTimeupdateTime(0)
63 ScriptWrappable::init(this);
66 MediaController::~MediaController()
70 void MediaController::addMediaElement(HTMLMediaElement* element)
73 ASSERT(!m_mediaElements.contains(element));
75 m_mediaElements.append(element);
76 bringElementUpToSpeed(element);
79 void MediaController::removeMediaElement(HTMLMediaElement* element)
82 ASSERT(m_mediaElements.contains(element));
83 m_mediaElements.remove(m_mediaElements.find(element));
86 bool MediaController::containsMediaElement(HTMLMediaElement* element) const
88 return m_mediaElements.contains(element);
91 PassRefPtr<TimeRanges> MediaController::buffered() const
93 if (m_mediaElements.isEmpty())
94 return TimeRanges::create();
96 // The buffered attribute must return a new static normalized TimeRanges object that represents
97 // the intersection of the ranges of the media resources of the slaved media elements that the
98 // user agent has buffered, at the time the attribute is evaluated.
99 RefPtr<TimeRanges> bufferedRanges = m_mediaElements.first()->buffered();
100 for (size_t index = 1; index < m_mediaElements.size(); ++index)
101 bufferedRanges->intersectWith(m_mediaElements[index]->buffered().get());
102 return bufferedRanges;
105 PassRefPtr<TimeRanges> MediaController::seekable() const
107 if (m_mediaElements.isEmpty())
108 return TimeRanges::create();
110 // The seekable attribute must return a new static normalized TimeRanges object that represents
111 // the intersection of the ranges of the media resources of the slaved media elements that the
112 // user agent is able to seek to, at the time the attribute is evaluated.
113 RefPtr<TimeRanges> seekableRanges = m_mediaElements.first()->seekable();
114 for (size_t index = 1; index < m_mediaElements.size(); ++index)
115 seekableRanges->intersectWith(m_mediaElements[index]->seekable().get());
116 return seekableRanges;
119 PassRefPtr<TimeRanges> MediaController::played()
121 if (m_mediaElements.isEmpty())
122 return TimeRanges::create();
124 // The played attribute must return a new static normalized TimeRanges object that represents
125 // the union of the ranges of the media resources of the slaved media elements that the
126 // user agent has so far rendered, at the time the attribute is evaluated.
127 RefPtr<TimeRanges> playedRanges = m_mediaElements.first()->played();
128 for (size_t index = 1; index < m_mediaElements.size(); ++index)
129 playedRanges->unionWith(m_mediaElements[index]->played().get());
133 double MediaController::duration() const
135 // FIXME: Investigate caching the maximum duration and only updating the cached value
136 // when the slaved media elements' durations change.
137 double maxDuration = 0;
138 for (size_t index = 0; index < m_mediaElements.size(); ++index) {
139 double duration = m_mediaElements[index]->duration();
140 if (std::isnan(duration))
142 maxDuration = max(maxDuration, duration);
147 double MediaController::currentTime() const
149 if (m_mediaElements.isEmpty())
152 if (m_position == MediaPlayer::invalidTime()) {
153 // Some clocks may return times outside the range of [0..duration].
154 m_position = max(0.0, min(duration(), m_clock->currentTime()));
155 m_clearPositionTimer.startOneShot(0, FROM_HERE);
161 void MediaController::setCurrentTime(double time, ExceptionState& exceptionState)
163 // When the user agent is to seek the media controller to a particular new playback position,
164 // it must follow these steps:
165 // If the new playback position is less than zero, then set it to zero.
166 time = max(0.0, time);
168 // If the new playback position is greater than the media controller duration, then set it
169 // to the media controller duration.
170 time = min(time, duration());
172 // Set the media controller position to the new playback position.
173 m_clock->setCurrentTime(time);
175 // Seek each slaved media element to the new playback position relative to the media element timeline.
176 for (size_t index = 0; index < m_mediaElements.size(); ++index)
177 m_mediaElements[index]->seek(time, exceptionState);
179 scheduleTimeupdateEvent();
182 void MediaController::unpause()
184 // When the unpause() method is invoked, if the MediaController is a paused media controller,
188 // the user agent must change the MediaController into a playing media controller,
190 // queue a task to fire a simple event named play at the MediaController,
191 scheduleEvent(EventTypeNames::play);
192 // and then report the controller state of the MediaController.
193 reportControllerState();
196 void MediaController::play()
198 // When the play() method is invoked, the user agent must invoke the play method of each
199 // slaved media element in turn,
200 for (size_t index = 0; index < m_mediaElements.size(); ++index)
201 m_mediaElements[index]->play();
203 // and then invoke the unpause method of the MediaController.
207 void MediaController::pause()
209 // When the pause() method is invoked, if the MediaController is a playing media controller,
213 // then the user agent must change the MediaController into a paused media controller,
215 // queue a task to fire a simple event named pause at the MediaController,
216 scheduleEvent(EventTypeNames::pause);
217 // and then report the controller state of the MediaController.
218 reportControllerState();
221 void MediaController::setDefaultPlaybackRate(double rate)
223 if (m_defaultPlaybackRate == rate)
226 // The defaultPlaybackRate attribute, on setting, must set the MediaController's media controller
227 // default playback rate to the new value,
228 m_defaultPlaybackRate = rate;
230 // then queue a task to fire a simple event named ratechange at the MediaController.
231 scheduleEvent(EventTypeNames::ratechange);
234 double MediaController::playbackRate() const
236 return m_clock->playRate();
239 void MediaController::setPlaybackRate(double rate)
241 if (m_clock->playRate() == rate)
244 // The playbackRate attribute, on setting, must set the MediaController's media controller
245 // playback rate to the new value,
246 m_clock->setPlayRate(rate);
248 for (size_t index = 0; index < m_mediaElements.size(); ++index)
249 m_mediaElements[index]->updatePlaybackRate();
251 // then queue a task to fire a simple event named ratechange at the MediaController.
252 scheduleEvent(EventTypeNames::ratechange);
255 void MediaController::setVolume(double level, ExceptionState& exceptionState)
257 if (m_volume == level)
260 // If the new value is outside the range 0.0 to 1.0 inclusive, then, on setting, an
261 // IndexSizeError exception must be raised instead.
262 if (level < 0 || level > 1) {
263 exceptionState.throwDOMException(IndexSizeError, ExceptionMessages::indexOutsideRange("volume", level, 0.0, ExceptionMessages::InclusiveBound, 1.0, ExceptionMessages::InclusiveBound));
267 // The volume attribute, on setting, if the new value is in the range 0.0 to 1.0 inclusive,
268 // must set the MediaController's media controller volume multiplier to the new value
271 // and queue a task to fire a simple event named volumechange at the MediaController.
272 scheduleEvent(EventTypeNames::volumechange);
274 for (size_t index = 0; index < m_mediaElements.size(); ++index)
275 m_mediaElements[index]->updateVolume();
278 void MediaController::setMuted(bool flag)
283 // The muted attribute, on setting, must set the MediaController's media controller mute override
287 // and queue a task to fire a simple event named volumechange at the MediaController.
288 scheduleEvent(EventTypeNames::volumechange);
290 for (size_t index = 0; index < m_mediaElements.size(); ++index)
291 m_mediaElements[index]->updateVolume();
294 static const AtomicString& playbackStateWaiting()
296 DEFINE_STATIC_LOCAL(AtomicString, waiting, ("waiting", AtomicString::ConstructFromLiteral));
300 static const AtomicString& playbackStatePlaying()
302 DEFINE_STATIC_LOCAL(AtomicString, playing, ("playing", AtomicString::ConstructFromLiteral));
306 static const AtomicString& playbackStateEnded()
308 DEFINE_STATIC_LOCAL(AtomicString, ended, ("ended", AtomicString::ConstructFromLiteral));
312 const AtomicString& MediaController::playbackState() const
314 switch (m_playbackState) {
316 return playbackStateWaiting();
318 return playbackStatePlaying();
320 return playbackStateEnded();
322 ASSERT_NOT_REACHED();
327 void MediaController::reportControllerState()
330 updatePlaybackState();
333 static const AtomicString& eventNameForReadyState(HTMLMediaElement::ReadyState state)
336 case HTMLMediaElement::HAVE_NOTHING:
337 return EventTypeNames::emptied;
338 case HTMLMediaElement::HAVE_METADATA:
339 return EventTypeNames::loadedmetadata;
340 case HTMLMediaElement::HAVE_CURRENT_DATA:
341 return EventTypeNames::loadeddata;
342 case HTMLMediaElement::HAVE_FUTURE_DATA:
343 return EventTypeNames::canplay;
344 case HTMLMediaElement::HAVE_ENOUGH_DATA:
345 return EventTypeNames::canplaythrough;
347 ASSERT_NOT_REACHED();
352 void MediaController::updateReadyState()
354 ReadyState oldReadyState = m_readyState;
355 ReadyState newReadyState;
357 if (m_mediaElements.isEmpty()) {
358 // If the MediaController has no slaved media elements, let new readiness state be 0.
359 newReadyState = HTMLMediaElement::HAVE_NOTHING;
361 // Otherwise, let it have the lowest value of the readyState IDL attributes of all of its
362 // slaved media elements.
363 newReadyState = m_mediaElements.first()->readyState();
364 for (size_t index = 1; index < m_mediaElements.size(); ++index)
365 newReadyState = min(newReadyState, m_mediaElements[index]->readyState());
368 if (newReadyState == oldReadyState)
371 // If the MediaController's most recently reported readiness state is greater than new readiness
372 // state then queue a task to fire a simple event at the MediaController object, whose name is the
373 // event name corresponding to the value of new readiness state given in the table below. [omitted]
374 if (oldReadyState > newReadyState) {
375 scheduleEvent(eventNameForReadyState(newReadyState));
379 // If the MediaController's most recently reported readiness state is less than the new readiness
380 // state, then run these substeps:
381 // 1. Let next state be the MediaController's most recently reported readiness state.
382 ReadyState nextState = oldReadyState;
384 // 2. Loop: Increment next state by one.
385 nextState = static_cast<ReadyState>(nextState + 1);
386 // 3. Queue a task to fire a simple event at the MediaController object, whose name is the
387 // event name corresponding to the value of next state given in the table below. [omitted]
388 scheduleEvent(eventNameForReadyState(nextState));
389 // If next state is less than new readiness state, then return to the step labeled loop
390 } while (nextState < newReadyState);
392 // Let the MediaController's most recently reported readiness state be new readiness state.
393 m_readyState = newReadyState;
396 void MediaController::updatePlaybackState()
398 PlaybackState oldPlaybackState = m_playbackState;
399 PlaybackState newPlaybackState;
401 // Initialize new playback state by setting it to the state given for the first matching
402 // condition from the following list:
403 if (m_mediaElements.isEmpty()) {
404 // If the MediaController has no slaved media elements
405 // Let new playback state be waiting.
406 newPlaybackState = WAITING;
407 } else if (hasEnded()) {
408 // If all of the MediaController's slaved media elements have ended playback and the media
409 // controller playback rate is positive or zero
410 // Let new playback state be ended.
411 newPlaybackState = ENDED;
412 } else if (isBlocked()) {
413 // If the MediaController is a blocked media controller
414 // Let new playback state be waiting.
415 newPlaybackState = WAITING;
418 // Let new playback state be playing.
419 newPlaybackState = PLAYING;
422 // If the MediaController's most recently reported playback state is not equal to new playback state
423 if (newPlaybackState == oldPlaybackState)
426 // and the new playback state is ended,
427 if (newPlaybackState == ENDED) {
428 // then queue a task that, if the MediaController object is a playing media controller, and
429 // all of the MediaController's slaved media elements have still ended playback, and the
430 // media controller playback rate is still positive or zero,
431 if (!m_paused && hasEnded()) {
432 // changes the MediaController object to a paused media controller
435 // and then fires a simple event named pause at the MediaController object.
436 scheduleEvent(EventTypeNames::pause);
440 // If the MediaController's most recently reported playback state is not equal to new playback state
441 // then queue a task to fire a simple event at the MediaController object, whose name is playing
442 // if new playback state is playing, ended if new playback state is ended, and waiting otherwise.
443 AtomicString eventName;
444 switch (newPlaybackState) {
446 eventName = EventTypeNames::waiting;
448 m_timeupdateTimer.stop();
451 eventName = EventTypeNames::ended;
453 m_timeupdateTimer.stop();
456 eventName = EventTypeNames::playing;
458 startTimeupdateTimer();
461 ASSERT_NOT_REACHED();
463 scheduleEvent(eventName);
465 // Let the MediaController's most recently reported playback state be new playback state.
466 m_playbackState = newPlaybackState;
468 updateMediaElements();
471 void MediaController::updateMediaElements()
473 for (size_t index = 0; index < m_mediaElements.size(); ++index)
474 m_mediaElements[index]->updatePlayState();
477 void MediaController::bringElementUpToSpeed(HTMLMediaElement* element)
480 ASSERT(m_mediaElements.contains(element));
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->seek(currentTime(), IGNORE_EXCEPTION);
488 bool MediaController::isRestrained() const
490 ASSERT(!m_mediaElements.isEmpty());
492 // A MediaController is a restrained media controller if the MediaController is a playing media
497 bool anyAutoplayingAndPaused = false;
498 bool allPaused = true;
499 for (size_t index = 0; index < m_mediaElements.size(); ++index) {
500 HTMLMediaElement* element = m_mediaElements[index];
502 // and none of its slaved media elements are blocked media elements,
503 if (element->isBlocked())
506 if (element->isAutoplaying() && element->paused())
507 anyAutoplayingAndPaused = true;
509 if (!element->paused())
513 // but either at least one of its slaved media elements whose autoplaying flag is true still has
514 // its paused attribute set to true, or, all of its slaved media elements have their paused
515 // attribute set to true.
516 return anyAutoplayingAndPaused || allPaused;
519 bool MediaController::isBlocked() const
521 ASSERT(!m_mediaElements.isEmpty());
523 // A MediaController is a blocked media controller if the MediaController is a paused media
528 bool allPaused = true;
529 for (size_t index = 0; index < m_mediaElements.size(); ++index) {
530 HTMLMediaElement* element = m_mediaElements[index];
532 // or if any of its slaved media elements are blocked media elements,
533 if (element->isBlocked())
536 // or if any of its slaved media elements whose autoplaying flag is true still have their
537 // paused attribute set to true,
538 if (element->isAutoplaying() && element->paused())
541 if (!element->paused())
545 // or if all of its slaved media elements have their paused attribute set to true.
549 bool MediaController::hasEnded() const
551 // If the ... media controller playback rate is positive or zero
552 if (m_clock->playRate() < 0)
555 // [and] all of the MediaController's slaved media elements have ended playback ... let new
556 // playback state be ended.
557 if (m_mediaElements.isEmpty())
560 bool allHaveEnded = true;
561 for (size_t index = 0; index < m_mediaElements.size(); ++index) {
562 if (!m_mediaElements[index]->ended())
563 allHaveEnded = false;
568 void MediaController::scheduleEvent(const AtomicString& eventName)
570 m_pendingEvents.append(Event::createCancelable(eventName));
571 if (!m_asyncEventTimer.isActive())
572 m_asyncEventTimer.startOneShot(0, FROM_HERE);
575 void MediaController::asyncEventTimerFired(Timer<MediaController>*)
577 Vector<RefPtr<Event> > pendingEvents;
579 m_pendingEvents.swap(pendingEvents);
580 size_t count = pendingEvents.size();
581 for (size_t index = 0; index < count; ++index)
582 dispatchEvent(pendingEvents[index].release(), IGNORE_EXCEPTION);
585 void MediaController::clearPositionTimerFired(Timer<MediaController>*)
587 m_position = MediaPlayer::invalidTime();
590 bool MediaController::hasAudio() const
592 for (size_t index = 0; index < m_mediaElements.size(); ++index) {
593 if (m_mediaElements[index]->hasAudio())
599 const AtomicString& MediaController::interfaceName() const
601 return EventTargetNames::MediaController;
604 // The spec says to fire periodic timeupdate events (those sent while playing) every
605 // "15 to 250ms", we choose the slowest frequency
606 static const double maxTimeupdateEventFrequency = 0.25;
608 void MediaController::startTimeupdateTimer()
610 if (m_timeupdateTimer.isActive())
613 m_timeupdateTimer.startRepeating(maxTimeupdateEventFrequency, FROM_HERE);
616 void MediaController::timeupdateTimerFired(Timer<MediaController>*)
618 scheduleTimeupdateEvent();
621 void MediaController::scheduleTimeupdateEvent()
623 double now = WTF::currentTime();
624 double timedelta = now - m_previousTimeupdateTime;
626 if (timedelta < maxTimeupdateEventFrequency)
629 scheduleEvent(EventTypeNames::timeupdate);
630 m_previousTimeupdateTime = now;