blob: 103161b1cb1b1b55f5d7dc7caa832aa3acc55e6b [file] [log] [blame]
jer.noble@apple.combe39c542011-11-14 17:55:16 +00001/*
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 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.
24 */
25
26#include "config.h"
27
28#if ENABLE(VIDEO)
29#include "MediaController.h"
30
31#include "Clock.h"
32#include "ExceptionCode.h"
33#include "HTMLMediaElement.h"
34#include "TimeRanges.h"
jamesr@google.comc38e6fb2012-08-11 00:32:19 +000035#include <wtf/CurrentTime.h>
jer.noble@apple.combe39c542011-11-14 17:55:16 +000036#include <wtf/StdLibExtras.h>
37#include <wtf/text/AtomicString.h>
38
39using namespace WebCore;
40using namespace std;
41
42PassRefPtr<MediaController> MediaController::create(ScriptExecutionContext* context)
43{
44 return adoptRef(new MediaController(context));
45}
46
47MediaController::MediaController(ScriptExecutionContext* context)
48 : m_paused(false)
49 , m_defaultPlaybackRate(1)
50 , m_volume(1)
jer.noble@apple.comee9d8ae2012-07-23 21:39:19 +000051 , m_position(MediaPlayer::invalidTime())
jer.noble@apple.combe39c542011-11-14 17:55:16 +000052 , m_muted(false)
53 , m_readyState(HAVE_NOTHING)
54 , m_playbackState(WAITING)
55 , m_asyncEventTimer(this, &MediaController::asyncEventTimerFired)
jer.noble@apple.comee9d8ae2012-07-23 21:39:19 +000056 , m_clearPositionTimer(this, &MediaController::clearPositionTimerFired)
jer.noble@apple.combe39c542011-11-14 17:55:16 +000057 , m_closedCaptionsVisible(false)
58 , m_clock(Clock::create())
59 , m_scriptExecutionContext(context)
jer.noble@apple.com29d33362012-08-10 23:50:46 +000060 , m_timeupdateTimer(this, &MediaController::timeupdateTimerFired)
61 , m_previousTimeupdateTime(0)
jer.noble@apple.combe39c542011-11-14 17:55:16 +000062{
63}
64
65MediaController::~MediaController()
66{
67}
68
69void MediaController::addMediaElement(HTMLMediaElement* element)
70{
71 ASSERT(element);
72 ASSERT(!m_mediaElements.contains(element));
73
74 m_mediaElements.append(element);
75 bringElementUpToSpeed(element);
76}
77
78void MediaController::removeMediaElement(HTMLMediaElement* element)
79{
80 ASSERT(element);
81 ASSERT(m_mediaElements.contains(element));
82 m_mediaElements.remove(m_mediaElements.find(element));
83}
84
85bool MediaController::containsMediaElement(HTMLMediaElement* element) const
86{
87 return m_mediaElements.contains(element);
88}
89
90PassRefPtr<TimeRanges> MediaController::buffered() const
91{
92 if (m_mediaElements.isEmpty())
93 return TimeRanges::create();
94
95 // The buffered attribute must return a new static normalized TimeRanges object that represents
96 // the intersection of the ranges of the media resources of the slaved media elements that the
97 // user agent has buffered, at the time the attribute is evaluated.
98 RefPtr<TimeRanges> bufferedRanges = m_mediaElements.first()->buffered();
99 for (size_t index = 1; index < m_mediaElements.size(); ++index)
100 bufferedRanges->intersectWith(m_mediaElements[index]->buffered().get());
101 return bufferedRanges;
102}
103
104PassRefPtr<TimeRanges> MediaController::seekable() const
105{
106 if (m_mediaElements.isEmpty())
107 return TimeRanges::create();
108
109 // The seekable attribute must return a new static normalized TimeRanges object that represents
110 // the intersection of the ranges of the media resources of the slaved media elements that the
111 // user agent is able to seek to, at the time the attribute is evaluated.
112 RefPtr<TimeRanges> seekableRanges = m_mediaElements.first()->seekable();
113 for (size_t index = 1; index < m_mediaElements.size(); ++index)
114 seekableRanges->intersectWith(m_mediaElements[index]->seekable().get());
115 return seekableRanges;
116}
117
118PassRefPtr<TimeRanges> MediaController::played()
119{
120 if (m_mediaElements.isEmpty())
121 return TimeRanges::create();
122
123 // The played attribute must return a new static normalized TimeRanges object that represents
124 // the union of the ranges of the media resources of the slaved media elements that the
125 // user agent has so far rendered, at the time the attribute is evaluated.
126 RefPtr<TimeRanges> playedRanges = m_mediaElements.first()->played();
127 for (size_t index = 1; index < m_mediaElements.size(); ++index)
128 playedRanges->unionWith(m_mediaElements[index]->played().get());
129 return playedRanges;
130}
131
132float MediaController::duration() const
133{
134 // FIXME: Investigate caching the maximum duration and only updating the cached value
135 // when the slaved media elements' durations change.
136 float maxDuration = 0;
137 for (size_t index = 0; index < m_mediaElements.size(); ++index) {
138 float duration = m_mediaElements[index]->duration();
zandobersek@gmail.com9182d472013-02-13 23:01:21 +0000139 if (std::isnan(duration))
jer.noble@apple.combe39c542011-11-14 17:55:16 +0000140 continue;
141 maxDuration = max(maxDuration, duration);
142 }
143 return maxDuration;
144}
145
146float MediaController::currentTime() const
147{
148 if (m_mediaElements.isEmpty())
149 return 0;
jer.noble@apple.com5022a392012-06-07 18:18:37 +0000150
jer.noble@apple.comee9d8ae2012-07-23 21:39:19 +0000151 if (m_position == MediaPlayer::invalidTime()) {
152 // Some clocks may return times outside the range of [0..duration].
153 m_position = max(0.0f, min(duration(), m_clock->currentTime()));
154 m_clearPositionTimer.startOneShot(0);
155 }
156
157 return m_position;
jer.noble@apple.combe39c542011-11-14 17:55:16 +0000158}
159
160void MediaController::setCurrentTime(float time, ExceptionCode& code)
161{
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 = max(0.0f, time);
166
167 // If the new playback position is greater than the media controller duration, then set it
168 // to the media controller duration.
169 time = min(time, duration());
170
171 // Set the media controller position to the new playback position.
172 m_clock->setCurrentTime(time);
173
174 // Seek each slaved media element to the new playback position relative to the media element timeline.
175 for (size_t index = 0; index < m_mediaElements.size(); ++index)
176 m_mediaElements[index]->seek(time, code);
jer.noble@apple.com29d33362012-08-10 23:50:46 +0000177
178 scheduleTimeupdateEvent();
jer.noble@apple.combe39c542011-11-14 17:55:16 +0000179}
180
jer.noble@apple.com53ef8c92012-12-01 00:13:34 +0000181void MediaController::unpause()
jer.noble@apple.combe39c542011-11-14 17:55:16 +0000182{
jer.noble@apple.com53ef8c92012-12-01 00:13:34 +0000183 // When the unpause() method is invoked, if the MediaController is a paused media controller,
jer.noble@apple.combe39c542011-11-14 17:55:16 +0000184 if (!m_paused)
185 return;
186
187 // the user agent must change the MediaController into a playing media controller,
188 m_paused = false;
189 // queue a task to fire a simple event named play at the MediaController,
190 scheduleEvent(eventNames().playEvent);
191 // and then report the controller state of the MediaController.
192 reportControllerState();
193}
194
jer.noble@apple.com53ef8c92012-12-01 00:13:34 +0000195void MediaController::play()
196{
197 // When the play() method is invoked, the user agent must invoke the play method of each
198 // slaved media element in turn,
199 for (size_t index = 0; index < m_mediaElements.size(); ++index)
200 m_mediaElements[index]->play();
201
202 // and then invoke the unpause method of the MediaController.
203 unpause();
204}
205
jer.noble@apple.combe39c542011-11-14 17:55:16 +0000206void MediaController::pause()
207{
208 // When the pause() method is invoked, if the MediaController is a playing media controller,
209 if (m_paused)
210 return;
211
212 // then the user agent must change the MediaController into a paused media controller,
213 m_paused = true;
214 // queue a task to fire a simple event named pause at the MediaController,
215 scheduleEvent(eventNames().pauseEvent);
216 // and then report the controller state of the MediaController.
217 reportControllerState();
218}
219
220void MediaController::setDefaultPlaybackRate(float rate)
221{
222 if (m_defaultPlaybackRate == rate)
223 return;
224
225 // The defaultPlaybackRate attribute, on setting, must set the MediaController's media controller
226 // default playback rate to the new value,
227 m_defaultPlaybackRate = rate;
228
229 // then queue a task to fire a simple event named ratechange at the MediaController.
230 scheduleEvent(eventNames().ratechangeEvent);
231}
232
233float MediaController::playbackRate() const
234{
235 return m_clock->playRate();
236}
237
238void MediaController::setPlaybackRate(float rate)
239{
240 if (m_clock->playRate() == rate)
241 return;
242
243 // The playbackRate attribute, on setting, must set the MediaController's media controller
244 // playback rate to the new value,
245 m_clock->setPlayRate(rate);
246
247 for (size_t index = 0; index < m_mediaElements.size(); ++index)
248 m_mediaElements[index]->updatePlaybackRate();
249
250 // then queue a task to fire a simple event named ratechange at the MediaController.
251 scheduleEvent(eventNames().ratechangeEvent);
252}
253
254void MediaController::setVolume(float level, ExceptionCode& code)
255{
256 if (m_volume == level)
257 return;
258
259 // If the new value is outside the range 0.0 to 1.0 inclusive, then, on setting, an
260 // IndexSizeError exception must be raised instead.
261 if (level < 0 || level > 1) {
262 code = INDEX_SIZE_ERR;
263 return;
264 }
265
266 // The volume attribute, on setting, if the new value is in the range 0.0 to 1.0 inclusive,
267 // must set the MediaController's media controller volume multiplier to the new value
268 m_volume = level;
269
270 // and queue a task to fire a simple event named volumechange at the MediaController.
271 scheduleEvent(eventNames().volumechangeEvent);
272
273 for (size_t index = 0; index < m_mediaElements.size(); ++index)
274 m_mediaElements[index]->updateVolume();
275}
276
277void MediaController::setMuted(bool flag)
278{
279 if (m_muted == flag)
280 return;
281
282 // The muted attribute, on setting, must set the MediaController's media controller mute override
283 // to the new value
284 m_muted = flag;
285
286 // and queue a task to fire a simple event named volumechange at the MediaController.
287 scheduleEvent(eventNames().volumechangeEvent);
288
289 for (size_t index = 0; index < m_mediaElements.size(); ++index)
290 m_mediaElements[index]->updateVolume();
291}
292
jer.noble@apple.com53ef8c92012-12-01 00:13:34 +0000293static const AtomicString& playbackStateWaiting()
294{
295 DEFINE_STATIC_LOCAL(AtomicString, waiting, ("waiting", AtomicString::ConstructFromLiteral));
296 return waiting;
297}
298
299static const AtomicString& playbackStatePlaying()
300{
301 DEFINE_STATIC_LOCAL(AtomicString, playing, ("playing", AtomicString::ConstructFromLiteral));
302 return playing;
303}
304
305static const AtomicString& playbackStateEnded()
306{
307 DEFINE_STATIC_LOCAL(AtomicString, ended, ("ended", AtomicString::ConstructFromLiteral));
308 return ended;
309}
310
311const AtomicString& MediaController::playbackState() const
312{
313 switch (m_playbackState) {
314 case WAITING:
315 return playbackStateWaiting();
316 case PLAYING:
317 return playbackStatePlaying();
318 case ENDED:
319 return playbackStateEnded();
jer.noble@apple.comcbc42ae2012-12-01 00:43:08 +0000320 default:
321 ASSERT_NOT_REACHED();
322 return nullAtom;
jer.noble@apple.com53ef8c92012-12-01 00:13:34 +0000323 }
324}
325
jer.noble@apple.combe39c542011-11-14 17:55:16 +0000326void MediaController::reportControllerState()
327{
328 updateReadyState();
329 updatePlaybackState();
330}
331
332static AtomicString eventNameForReadyState(MediaControllerInterface::ReadyState state)
333{
334 switch (state) {
335 case MediaControllerInterface::HAVE_NOTHING:
336 return eventNames().emptiedEvent;
337 case MediaControllerInterface::HAVE_METADATA:
338 return eventNames().loadedmetadataEvent;
339 case MediaControllerInterface::HAVE_CURRENT_DATA:
340 return eventNames().loadeddataEvent;
341 case MediaControllerInterface::HAVE_FUTURE_DATA:
342 return eventNames().canplayEvent;
343 case MediaControllerInterface::HAVE_ENOUGH_DATA:
344 return eventNames().canplaythroughEvent;
345 default:
346 ASSERT_NOT_REACHED();
347 return nullAtom;
348 }
349}
350
351void MediaController::updateReadyState()
352{
353 ReadyState oldReadyState = m_readyState;
354 ReadyState newReadyState;
355
356 if (m_mediaElements.isEmpty()) {
357 // If the MediaController has no slaved media elements, let new readiness state be 0.
358 newReadyState = HAVE_NOTHING;
359 } else {
360 // Otherwise, let it have the lowest value of the readyState IDL attributes of all of its
361 // slaved media elements.
362 newReadyState = m_mediaElements.first()->readyState();
363 for (size_t index = 1; index < m_mediaElements.size(); ++index)
364 newReadyState = min(newReadyState, m_mediaElements[index]->readyState());
365 }
366
367 if (newReadyState == oldReadyState)
368 return;
369
370 // If the MediaController's most recently reported readiness state is greater than new readiness
371 // state then queue a task to fire a simple event at the MediaController object, whose name is the
372 // event name corresponding to the value of new readiness state given in the table below. [omitted]
373 if (oldReadyState > newReadyState) {
374 scheduleEvent(eventNameForReadyState(newReadyState));
375 return;
376 }
377
378 // If the MediaController's most recently reported readiness state is less than the new readiness
379 // state, then run these substeps:
380 // 1. Let next state be the MediaController's most recently reported readiness state.
381 ReadyState nextState = oldReadyState;
382 do {
383 // 2. Loop: Increment next state by one.
384 nextState = static_cast<ReadyState>(nextState + 1);
385 // 3. Queue a task to fire a simple event at the MediaController object, whose name is the
386 // event name corresponding to the value of next state given in the table below. [omitted]
387 scheduleEvent(eventNameForReadyState(nextState));
388 // If next state is less than new readiness state, then return to the step labeled loop
389 } while (nextState < newReadyState);
390
391 // Let the MediaController's most recently reported readiness state be new readiness state.
392 m_readyState = newReadyState;
393}
394
395void MediaController::updatePlaybackState()
396{
397 PlaybackState oldPlaybackState = m_playbackState;
398 PlaybackState newPlaybackState;
399
400 // Initialize new playback state by setting it to the state given for the first matching
401 // condition from the following list:
402 if (m_mediaElements.isEmpty()) {
403 // If the MediaController has no slaved media elements
404 // Let new playback state be waiting.
405 newPlaybackState = WAITING;
406 } else if (hasEnded()) {
407 // If all of the MediaController's slaved media elements have ended playback and the media
408 // controller playback rate is positive or zero
409 // Let new playback state be ended.
410 newPlaybackState = ENDED;
411 } else if (isBlocked()) {
412 // If the MediaController is a blocked media controller
413 // Let new playback state be waiting.
414 newPlaybackState = WAITING;
415 } else {
416 // Otherwise
417 // Let new playback state be playing.
418 newPlaybackState = PLAYING;
419 }
420
421 // If the MediaController's most recently reported playback state is not equal to new playback state
422 if (newPlaybackState == oldPlaybackState)
423 return;
424
425 // and the new playback state is ended,
426 if (newPlaybackState == ENDED) {
427 // then queue a task that, if the MediaController object is a playing media controller, and
428 // all of the MediaController's slaved media elements have still ended playback, and the
429 // media controller playback rate is still positive or zero,
430 if (!m_paused && hasEnded()) {
431 // changes the MediaController object to a paused media controller
432 m_paused = true;
jer.noble@apple.combe39c542011-11-14 17:55:16 +0000433
434 // and then fires a simple event named pause at the MediaController object.
435 scheduleEvent(eventNames().pauseEvent);
436 }
437 }
438
439 // If the MediaController's most recently reported playback state is not equal to new playback state
440 // then queue a task to fire a simple event at the MediaController object, whose name is playing
441 // if new playback state is playing, ended if new playback state is ended, and waiting otherwise.
442 AtomicString eventName;
443 switch (newPlaybackState) {
444 case WAITING:
445 eventName = eventNames().waitingEvent;
jer.noble@apple.comd0d77172011-12-19 19:15:47 +0000446 m_clock->stop();
jer.noble@apple.com29d33362012-08-10 23:50:46 +0000447 m_timeupdateTimer.stop();
jer.noble@apple.combe39c542011-11-14 17:55:16 +0000448 break;
449 case ENDED:
450 eventName = eventNames().endedEvent;
jer.noble@apple.comd0d77172011-12-19 19:15:47 +0000451 m_clock->stop();
jer.noble@apple.com29d33362012-08-10 23:50:46 +0000452 m_timeupdateTimer.stop();
jer.noble@apple.combe39c542011-11-14 17:55:16 +0000453 break;
454 case PLAYING:
455 eventName = eventNames().playingEvent;
456 m_clock->start();
jer.noble@apple.com29d33362012-08-10 23:50:46 +0000457 startTimeupdateTimer();
jer.noble@apple.combe39c542011-11-14 17:55:16 +0000458 break;
459 default:
460 ASSERT_NOT_REACHED();
461 }
462 scheduleEvent(eventName);
463
464 // Let the MediaController's most recently reported playback state be new playback state.
465 m_playbackState = newPlaybackState;
466
467 updateMediaElements();
468}
469
470void MediaController::updateMediaElements()
471{
472 for (size_t index = 0; index < m_mediaElements.size(); ++index)
473 m_mediaElements[index]->updatePlayState();
474}
475
476void MediaController::bringElementUpToSpeed(HTMLMediaElement* element)
477{
478 ASSERT(element);
479 ASSERT(m_mediaElements.contains(element));
480
481 // When the user agent is to bring a media element up to speed with its new media controller,
482 // it must seek that media element to the MediaController's media controller position relative
483 // to the media element's timeline.
mkwst@chromium.org33cdf432013-02-08 14:21:48 +0000484 element->seek(currentTime(), IGNORE_EXCEPTION);
jer.noble@apple.combe39c542011-11-14 17:55:16 +0000485}
486
487bool MediaController::isBlocked() const
488{
489 // A MediaController is a blocked media controller if the MediaController is a paused media
490 // controller,
491 if (m_paused)
492 return true;
493
494 if (m_mediaElements.isEmpty())
495 return false;
496
497 bool allPaused = true;
498 for (size_t index = 0; index < m_mediaElements.size(); ++index) {
499 HTMLMediaElement* element = m_mediaElements[index];
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 (size_t index = 0; index < m_mediaElements.size(); ++index) {
530 if (!m_mediaElements[index]->ended())
531 allHaveEnded = false;
532 }
533 return allHaveEnded;
534}
535
536void MediaController::scheduleEvent(const AtomicString& eventName)
537{
538 m_pendingEvents.append(Event::create(eventName, false, true));
539 if (!m_asyncEventTimer.isActive())
540 m_asyncEventTimer.startOneShot(0);
541}
542
543void MediaController::asyncEventTimerFired(Timer<MediaController>*)
544{
545 Vector<RefPtr<Event> > pendingEvents;
mkwst@chromium.org33cdf432013-02-08 14:21:48 +0000546
jer.noble@apple.combe39c542011-11-14 17:55:16 +0000547 m_pendingEvents.swap(pendingEvents);
548 size_t count = pendingEvents.size();
549 for (size_t index = 0; index < count; ++index)
mkwst@chromium.org33cdf432013-02-08 14:21:48 +0000550 dispatchEvent(pendingEvents[index].release(), IGNORE_EXCEPTION);
jer.noble@apple.combe39c542011-11-14 17:55:16 +0000551}
552
jer.noble@apple.comee9d8ae2012-07-23 21:39:19 +0000553void MediaController::clearPositionTimerFired(Timer<MediaController>*)
554{
555 m_position = MediaPlayer::invalidTime();
556}
557
jer.noble@apple.combe39c542011-11-14 17:55:16 +0000558bool MediaController::hasAudio() const
559{
560 for (size_t index = 0; index < m_mediaElements.size(); ++index) {
561 if (m_mediaElements[index]->hasAudio())
562 return true;
563 }
564 return false;
565}
566
567bool MediaController::hasVideo() const
568{
569 for (size_t index = 0; index < m_mediaElements.size(); ++index) {
570 if (m_mediaElements[index]->hasVideo())
571 return true;
572 }
573 return false;
574}
575
576bool MediaController::hasClosedCaptions() const
577{
578 for (size_t index = 0; index < m_mediaElements.size(); ++index) {
579 if (m_mediaElements[index]->hasClosedCaptions())
580 return true;
581 }
582 return false;
583}
584
585void MediaController::setClosedCaptionsVisible(bool visible)
586{
587 m_closedCaptionsVisible = visible;
588 for (size_t index = 0; index < m_mediaElements.size(); ++index)
589 m_mediaElements[index]->setClosedCaptionsVisible(visible);
590}
591
592bool MediaController::supportsScanning() const
593{
594 for (size_t index = 0; index < m_mediaElements.size(); ++index) {
595 if (!m_mediaElements[index]->supportsScanning())
596 return false;
597 }
598 return true;
599}
600
601void MediaController::beginScrubbing()
602{
603 for (size_t index = 0; index < m_mediaElements.size(); ++index)
604 m_mediaElements[index]->beginScrubbing();
jer.noble@apple.comd0d77172011-12-19 19:15:47 +0000605 if (m_playbackState == PLAYING)
606 m_clock->stop();
jer.noble@apple.combe39c542011-11-14 17:55:16 +0000607}
608
609void MediaController::endScrubbing()
610{
611 for (size_t index = 0; index < m_mediaElements.size(); ++index)
612 m_mediaElements[index]->endScrubbing();
jer.noble@apple.comd0d77172011-12-19 19:15:47 +0000613 if (m_playbackState == PLAYING)
614 m_clock->start();
jer.noble@apple.combe39c542011-11-14 17:55:16 +0000615}
616
617bool MediaController::canPlay() const
618{
jer.noble@apple.comd0d77172011-12-19 19:15:47 +0000619 if (m_paused)
620 return true;
621
jer.noble@apple.combe39c542011-11-14 17:55:16 +0000622 for (size_t index = 0; index < m_mediaElements.size(); ++index) {
623 if (!m_mediaElements[index]->canPlay())
624 return false;
625 }
626 return true;
627}
628
629bool MediaController::isLiveStream() const
630{
631 for (size_t index = 0; index < m_mediaElements.size(); ++index) {
632 if (!m_mediaElements[index]->isLiveStream())
633 return false;
634 }
635 return true;
636}
637
638bool MediaController::hasCurrentSrc() const
639{
640 for (size_t index = 0; index < m_mediaElements.size(); ++index) {
641 if (!m_mediaElements[index]->hasCurrentSrc())
642 return false;
643 }
644 return true;
645}
646
647void MediaController::returnToRealtime()
648{
649 for (size_t index = 0; index < m_mediaElements.size(); ++index)
650 m_mediaElements[index]->returnToRealtime();
651}
652
653const AtomicString& MediaController::interfaceName() const
654{
655 return eventNames().interfaceForMediaController;
656}
657
jer.noble@apple.com29d33362012-08-10 23:50:46 +0000658// The spec says to fire periodic timeupdate events (those sent while playing) every
659// "15 to 250ms", we choose the slowest frequency
660static const double maxTimeupdateEventFrequency = 0.25;
661
662void MediaController::startTimeupdateTimer()
663{
664 if (m_timeupdateTimer.isActive())
665 return;
666
667 m_timeupdateTimer.startRepeating(maxTimeupdateEventFrequency);
668}
669
670void MediaController::timeupdateTimerFired(Timer<MediaController>*)
671{
672 scheduleTimeupdateEvent();
673}
674
675void MediaController::scheduleTimeupdateEvent()
676{
677 double now = WTF::currentTime();
678 double timedelta = now - m_previousTimeupdateTime;
679
680 if (timedelta < maxTimeupdateEventFrequency)
681 return;
682
683 scheduleEvent(eventNames().timeupdateEvent);
684 m_previousTimeupdateTime = now;
685}
686
jer.noble@apple.combe39c542011-11-14 17:55:16 +0000687#endif