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/core/v8/ExceptionMessages.h"
30 #include "bindings/core/v8/ExceptionState.h"
31 #include "bindings/core/v8/ExceptionStatePlaceholder.h"
32 #include "core/dom/ExceptionCode.h"
33 #include "core/dom/ExecutionContext.h"
34 #include "core/events/Event.h"
35 #include "core/events/GenericEventQueue.h"
36 #include "core/html/HTMLMediaElement.h"
37 #include "core/html/TimeRanges.h"
38 #include "platform/Clock.h"
39 #include "wtf/CurrentTime.h"
40 #include "wtf/StdLibExtras.h"
41 #include "wtf/text/AtomicString.h"
45 MediaController
* MediaController::create(ExecutionContext
* context
)
47 return new MediaController(context
);
50 MediaController::MediaController(ExecutionContext
* context
)
52 , m_defaultPlaybackRate(1)
54 , m_position(std::numeric_limits
<double>::quiet_NaN())
56 , m_readyState(HTMLMediaElement::HAVE_NOTHING
)
57 , m_playbackState(WAITING
)
58 , m_pendingEventsQueue(GenericEventQueue::create(this))
59 , m_clearPositionTimer(this, &MediaController::clearPositionTimerFired
)
60 , m_clock(Clock::create())
61 , m_executionContext(context
)
62 , m_timeupdateTimer(this, &MediaController::timeupdateTimerFired
)
63 , m_previousTimeupdateTime(0)
67 MediaController::~MediaController()
71 void MediaController::addMediaElement(HTMLMediaElement
* element
)
74 ASSERT(!m_mediaElements
.contains(element
));
76 m_mediaElements
.add(element
);
77 bringElementUpToSpeed(element
);
80 void MediaController::removeMediaElement(HTMLMediaElement
* element
)
83 ASSERT(m_mediaElements
.contains(element
));
84 m_mediaElements
.remove(m_mediaElements
.find(element
));
87 TimeRanges
* MediaController::buffered() const
89 if (m_mediaElements
.isEmpty())
90 return TimeRanges::create();
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 MediaElementSequence::const_iterator it
= m_mediaElements
.begin();
96 TimeRanges
* bufferedRanges
= (*it
)->buffered();
97 for (++it
; it
!= m_mediaElements
.end(); ++it
)
98 bufferedRanges
->intersectWith((*it
)->buffered());
99 return bufferedRanges
;
102 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 MediaElementSequence::const_iterator it
= m_mediaElements
.begin();
111 TimeRanges
* seekableRanges
= (*it
)->seekable();
112 for (++it
; it
!= m_mediaElements
.end(); ++it
)
113 seekableRanges
->intersectWith((*it
)->seekable());
114 return seekableRanges
;
117 TimeRanges
* MediaController::played()
119 if (m_mediaElements
.isEmpty())
120 return TimeRanges::create();
122 // The played attribute must return a new static normalized TimeRanges object that represents
123 // the union of the ranges of the media resources of the slaved media elements that the
124 // user agent has so far rendered, at the time the attribute is evaluated.
125 MediaElementSequence::const_iterator it
= m_mediaElements
.begin();
126 TimeRanges
* playedRanges
= (*it
)->played();
127 for (++it
; it
!= m_mediaElements
.end(); ++it
)
128 playedRanges
->unionWith((*it
)->played());
132 double MediaController::duration() const
134 // FIXME: Investigate caching the maximum duration and only updating the cached value
135 // when the slaved media elements' durations change.
136 double maxDuration
= 0;
137 for (const HTMLMediaElement
* element
: m_mediaElements
) {
138 double duration
= element
->duration();
139 if (std::isnan(duration
))
141 maxDuration
= std::max(maxDuration
, duration
);
146 double MediaController::currentTime() const
148 if (m_mediaElements
.isEmpty())
151 if (std::isnan(m_position
)) {
152 // Some clocks may return times outside the range of [0..duration].
153 m_position
= std::max(0.0, std::min(duration(), m_clock
->currentTime()));
154 m_clearPositionTimer
.startOneShot(0, FROM_HERE
);
160 void MediaController::setCurrentTime(double time
)
162 // When the user agent is to seek the media controller to a particular new playback position,
163 // it must follow these steps:
164 // If the new playback position is less than zero, then set it to zero.
165 time
= std::max(0.0, time
);
167 // If the new playback position is greater than the media controller duration, then set it
168 // to the media controller duration.
169 time
= std::min(time
, duration());
171 // 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 (HTMLMediaElement
* element
: m_mediaElements
)
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 (HTMLMediaElement
* element
: m_mediaElements
)
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 (HTMLMediaElement
* element
: m_mediaElements
)
249 element
->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 (HTMLMediaElement
* element
: m_mediaElements
)
275 element
->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 (HTMLMediaElement
* element
: m_mediaElements
)
291 element
->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 MediaElementSequence::const_iterator it
= m_mediaElements
.begin();
364 newReadyState
= (*it
)->readyState();
365 for (++it
; it
!= m_mediaElements
.end(); ++it
)
366 newReadyState
= std::min(newReadyState
, (*it
)->readyState());
369 if (newReadyState
== oldReadyState
)
372 // If the MediaController's most recently reported readiness state is greater than new readiness
373 // state then queue a task to fire a simple event at the MediaController object, whose name is the
374 // event name corresponding to the value of new readiness state given in the table below. [omitted]
375 if (oldReadyState
> newReadyState
) {
376 scheduleEvent(eventNameForReadyState(newReadyState
));
380 // If the MediaController's most recently reported readiness state is less than the new readiness
381 // state, then run these substeps:
382 // 1. Let next state be the MediaController's most recently reported readiness state.
383 ReadyState nextState
= oldReadyState
;
385 // 2. Loop: Increment next state by one.
386 nextState
= static_cast<ReadyState
>(nextState
+ 1);
387 // 3. Queue a task to fire a simple event at the MediaController object, whose name is the
388 // event name corresponding to the value of next state given in the table below. [omitted]
389 scheduleEvent(eventNameForReadyState(nextState
));
390 // If next state is less than new readiness state, then return to the step labeled loop
391 } while (nextState
< newReadyState
);
393 // Let the MediaController's most recently reported readiness state be new readiness state.
394 m_readyState
= newReadyState
;
397 void MediaController::updatePlaybackState()
399 PlaybackState oldPlaybackState
= m_playbackState
;
400 PlaybackState newPlaybackState
;
402 // Initialize new playback state by setting it to the state given for the first matching
403 // condition from the following list:
404 if (m_mediaElements
.isEmpty()) {
405 // If the MediaController has no slaved media elements
406 // Let new playback state be waiting.
407 newPlaybackState
= WAITING
;
408 } else if (hasEnded()) {
409 // If all of the MediaController's slaved media elements have ended playback and the media
410 // controller playback rate is positive or zero
411 // Let new playback state be ended.
412 newPlaybackState
= ENDED
;
413 } else if (isBlocked()) {
414 // If the MediaController is a blocked media controller
415 // Let new playback state be waiting.
416 newPlaybackState
= WAITING
;
419 // Let new playback state be playing.
420 newPlaybackState
= PLAYING
;
423 // If the MediaController's most recently reported playback state is not equal to new playback state
424 if (newPlaybackState
== oldPlaybackState
)
427 // and the new playback state is ended,
428 if (newPlaybackState
== ENDED
) {
429 // then queue a task that, if the MediaController object is a playing media controller, and
430 // all of the MediaController's slaved media elements have still ended playback, and the
431 // media controller playback rate is still positive or zero,
432 if (!m_paused
&& hasEnded()) {
433 // changes the MediaController object to a paused media controller
436 // and then fires a simple event named pause at the MediaController object.
437 scheduleEvent(EventTypeNames::pause
);
441 // If the MediaController's most recently reported playback state is not equal to new playback state
442 // then queue a task to fire a simple event at the MediaController object, whose name is playing
443 // if new playback state is playing, ended if new playback state is ended, and waiting otherwise.
444 AtomicString eventName
;
445 switch (newPlaybackState
) {
447 eventName
= EventTypeNames::waiting
;
449 m_timeupdateTimer
.stop();
452 eventName
= EventTypeNames::ended
;
454 m_timeupdateTimer
.stop();
457 eventName
= EventTypeNames::playing
;
459 startTimeupdateTimer();
462 ASSERT_NOT_REACHED();
464 scheduleEvent(eventName
);
466 // Let the MediaController's most recently reported playback state be new playback state.
467 m_playbackState
= newPlaybackState
;
469 updateMediaElements();
472 void MediaController::updateMediaElements()
474 for (HTMLMediaElement
* element
: m_mediaElements
)
475 element
->updatePlayState();
478 void MediaController::bringElementUpToSpeed(HTMLMediaElement
* element
)
481 ASSERT(m_mediaElements
.contains(element
));
483 // When the user agent is to bring a media element up to speed with its new media controller,
484 // it must seek that media element to the MediaController's media controller position relative
485 // to the media element's timeline.
486 element
->seek(currentTime());
488 // Update volume to take controller volume and mute into account.
489 element
->updateVolume();
492 bool MediaController::isRestrained() const
494 ASSERT(!m_mediaElements
.isEmpty());
496 // A MediaController is a restrained media controller if the MediaController is a playing media
501 bool anyAutoplayingAndPaused
= false;
502 bool allPaused
= true;
503 for (const HTMLMediaElement
* element
: m_mediaElements
) {
504 if (element
->isAutoplaying() && element
->paused())
505 anyAutoplayingAndPaused
= true;
507 if (!element
->paused())
511 // but either at least one of its slaved media elements whose autoplaying flag is true still has
512 // its paused attribute set to true, or, all of its slaved media elements have their paused
513 // attribute set to true.
514 return anyAutoplayingAndPaused
|| allPaused
;
517 bool MediaController::isBlocked() const
519 ASSERT(!m_mediaElements
.isEmpty());
521 // A MediaController is a blocked media controller if the MediaController is a paused media
526 bool allPaused
= true;
527 for (const HTMLMediaElement
* element
: m_mediaElements
) {
528 // or if any of its slaved media elements are blocked media elements,
529 if (element
->isBlocked())
532 // or if any of its slaved media elements whose autoplaying flag is true still have their
533 // paused attribute set to true,
534 if (element
->isAutoplaying() && element
->paused())
537 if (!element
->paused())
541 // or if all of its slaved media elements have their paused attribute set to true.
545 bool MediaController::hasEnded() const
547 // If the ... media controller playback rate is positive or zero
548 if (m_clock
->playRate() < 0)
551 // [and] all of the MediaController's slaved media elements have ended playback ... let new
552 // playback state be ended.
553 if (m_mediaElements
.isEmpty())
556 bool allHaveEnded
= true;
557 for (const HTMLMediaElement
* element
: m_mediaElements
) {
558 if (!element
->ended())
559 allHaveEnded
= false;
564 void MediaController::scheduleEvent(const AtomicString
& eventName
)
566 m_pendingEventsQueue
->enqueueEvent(Event::createCancelable(eventName
));
569 void MediaController::clearPositionTimerFired(Timer
<MediaController
>*)
571 m_position
= std::numeric_limits
<double>::quiet_NaN();
574 const AtomicString
& MediaController::interfaceName() const
576 return EventTargetNames::MediaController
;
579 // The spec says to fire periodic timeupdate events (those sent while playing) every
580 // "15 to 250ms", we choose the slowest frequency
581 static const double maxTimeupdateEventFrequency
= 0.25;
583 void MediaController::startTimeupdateTimer()
585 if (m_timeupdateTimer
.isActive())
588 m_timeupdateTimer
.startRepeating(maxTimeupdateEventFrequency
, FROM_HERE
);
591 void MediaController::timeupdateTimerFired(Timer
<MediaController
>*)
593 scheduleTimeupdateEvent();
596 void MediaController::scheduleTimeupdateEvent()
598 double now
= WTF::currentTime();
599 double timedelta
= now
- m_previousTimeupdateTime
;
601 if (timedelta
< maxTimeupdateEventFrequency
)
604 scheduleEvent(EventTypeNames::timeupdate
);
605 m_previousTimeupdateTime
= now
;
608 DEFINE_TRACE(MediaController
)
611 visitor
->trace(m_mediaElements
);
612 visitor
->trace(m_pendingEventsQueue
);
613 visitor
->trace(m_executionContext
);
615 RefCountedGarbageCollectedEventTargetWithInlineData
<MediaController
>::trace(visitor
);