• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 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 #include "core/html/MediaController.h"
28 
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"
39 
40 using namespace WebCore;
41 using namespace std;
42 
create(ExecutionContext * context)43 PassRefPtr<MediaController> MediaController::create(ExecutionContext* context)
44 {
45     return adoptRef(new MediaController(context));
46 }
47 
MediaController(ExecutionContext * context)48 MediaController::MediaController(ExecutionContext* context)
49     : m_paused(false)
50     , m_defaultPlaybackRate(1)
51     , m_volume(1)
52     , m_position(MediaPlayer::invalidTime())
53     , m_muted(false)
54     , m_readyState(HAVE_NOTHING)
55     , m_playbackState(WAITING)
56     , m_asyncEventTimer(this, &MediaController::asyncEventTimerFired)
57     , m_clearPositionTimer(this, &MediaController::clearPositionTimerFired)
58     , m_closedCaptionsVisible(false)
59     , m_clock(Clock::create())
60     , m_executionContext(context)
61     , m_timeupdateTimer(this, &MediaController::timeupdateTimerFired)
62     , m_previousTimeupdateTime(0)
63 {
64     ScriptWrappable::init(this);
65 }
66 
~MediaController()67 MediaController::~MediaController()
68 {
69 }
70 
addMediaElement(HTMLMediaElement * element)71 void MediaController::addMediaElement(HTMLMediaElement* element)
72 {
73     ASSERT(element);
74     ASSERT(!m_mediaElements.contains(element));
75 
76     m_mediaElements.append(element);
77     bringElementUpToSpeed(element);
78 }
79 
removeMediaElement(HTMLMediaElement * element)80 void MediaController::removeMediaElement(HTMLMediaElement* element)
81 {
82     ASSERT(element);
83     ASSERT(m_mediaElements.contains(element));
84     m_mediaElements.remove(m_mediaElements.find(element));
85 }
86 
containsMediaElement(HTMLMediaElement * element) const87 bool MediaController::containsMediaElement(HTMLMediaElement* element) const
88 {
89     return m_mediaElements.contains(element);
90 }
91 
buffered() const92 PassRefPtr<TimeRanges> MediaController::buffered() const
93 {
94     if (m_mediaElements.isEmpty())
95         return TimeRanges::create();
96 
97     // The buffered attribute must return a new static normalized TimeRanges object that represents
98     // the intersection of the ranges of the media resources of the slaved media elements that the
99     // user agent has buffered, at the time the attribute is evaluated.
100     RefPtr<TimeRanges> bufferedRanges = m_mediaElements.first()->buffered();
101     for (size_t index = 1; index < m_mediaElements.size(); ++index)
102         bufferedRanges->intersectWith(m_mediaElements[index]->buffered().get());
103     return bufferedRanges;
104 }
105 
seekable() const106 PassRefPtr<TimeRanges> MediaController::seekable() const
107 {
108     if (m_mediaElements.isEmpty())
109         return TimeRanges::create();
110 
111     // The seekable attribute must return a new static normalized TimeRanges object that represents
112     // the intersection of the ranges of the media resources of the slaved media elements that the
113     // user agent is able to seek to, at the time the attribute is evaluated.
114     RefPtr<TimeRanges> seekableRanges = m_mediaElements.first()->seekable();
115     for (size_t index = 1; index < m_mediaElements.size(); ++index)
116         seekableRanges->intersectWith(m_mediaElements[index]->seekable().get());
117     return seekableRanges;
118 }
119 
played()120 PassRefPtr<TimeRanges> MediaController::played()
121 {
122     if (m_mediaElements.isEmpty())
123         return TimeRanges::create();
124 
125     // The played attribute must return a new static normalized TimeRanges object that represents
126     // the union of the ranges of the media resources of the slaved media elements that the
127     // user agent has so far rendered, at the time the attribute is evaluated.
128     RefPtr<TimeRanges> playedRanges = m_mediaElements.first()->played();
129     for (size_t index = 1; index < m_mediaElements.size(); ++index)
130         playedRanges->unionWith(m_mediaElements[index]->played().get());
131     return playedRanges;
132 }
133 
duration() const134 double MediaController::duration() const
135 {
136     // FIXME: Investigate caching the maximum duration and only updating the cached value
137     // when the slaved media elements' durations change.
138     double maxDuration = 0;
139     for (size_t index = 0; index < m_mediaElements.size(); ++index) {
140         double duration = m_mediaElements[index]->duration();
141         if (std::isnan(duration))
142             continue;
143         maxDuration = max(maxDuration, duration);
144     }
145     return maxDuration;
146 }
147 
currentTime() const148 double MediaController::currentTime() const
149 {
150     if (m_mediaElements.isEmpty())
151         return 0;
152 
153     if (m_position == MediaPlayer::invalidTime()) {
154         // Some clocks may return times outside the range of [0..duration].
155         m_position = max(0.0, min(duration(), m_clock->currentTime()));
156         m_clearPositionTimer.startOneShot(0);
157     }
158 
159     return m_position;
160 }
161 
setCurrentTime(double time,ExceptionState & exceptionState)162 void MediaController::setCurrentTime(double time, ExceptionState& exceptionState)
163 {
164     // When the user agent is to seek the media controller to a particular new playback position,
165     // it must follow these steps:
166     // If the new playback position is less than zero, then set it to zero.
167     time = max(0.0, time);
168 
169     // If the new playback position is greater than the media controller duration, then set it
170     // to the media controller duration.
171     time = min(time, duration());
172 
173     // Set the media controller position to the new playback position.
174     m_clock->setCurrentTime(time);
175 
176     // Seek each slaved media element to the new playback position relative to the media element timeline.
177     for (size_t index = 0; index < m_mediaElements.size(); ++index)
178         m_mediaElements[index]->seek(time, exceptionState);
179 
180     scheduleTimeupdateEvent();
181 }
182 
unpause()183 void MediaController::unpause()
184 {
185     // When the unpause() method is invoked, if the MediaController is a paused media controller,
186     if (!m_paused)
187         return;
188 
189     // the user agent must change the MediaController into a playing media controller,
190     m_paused = false;
191     // queue a task to fire a simple event named play at the MediaController,
192     scheduleEvent(EventTypeNames::play);
193     // and then report the controller state of the MediaController.
194     reportControllerState();
195 }
196 
play()197 void MediaController::play()
198 {
199     // When the play() method is invoked, the user agent must invoke the play method of each
200     // slaved media element in turn,
201     for (size_t index = 0; index < m_mediaElements.size(); ++index)
202         m_mediaElements[index]->play();
203 
204     // and then invoke the unpause method of the MediaController.
205     unpause();
206 }
207 
pause()208 void MediaController::pause()
209 {
210     // When the pause() method is invoked, if the MediaController is a playing media controller,
211     if (m_paused)
212         return;
213 
214     // then the user agent must change the MediaController into a paused media controller,
215     m_paused = true;
216     // queue a task to fire a simple event named pause at the MediaController,
217     scheduleEvent(EventTypeNames::pause);
218     // and then report the controller state of the MediaController.
219     reportControllerState();
220 }
221 
setDefaultPlaybackRate(double rate)222 void MediaController::setDefaultPlaybackRate(double rate)
223 {
224     if (m_defaultPlaybackRate == rate)
225         return;
226 
227     // The defaultPlaybackRate attribute, on setting, must set the MediaController's media controller
228     // default playback rate to the new value,
229     m_defaultPlaybackRate = rate;
230 
231     // then queue a task to fire a simple event named ratechange at the MediaController.
232     scheduleEvent(EventTypeNames::ratechange);
233 }
234 
playbackRate() const235 double MediaController::playbackRate() const
236 {
237     return m_clock->playRate();
238 }
239 
setPlaybackRate(double rate)240 void MediaController::setPlaybackRate(double rate)
241 {
242     if (m_clock->playRate() == rate)
243         return;
244 
245     // The playbackRate attribute, on setting, must set the MediaController's media controller
246     // playback rate to the new value,
247     m_clock->setPlayRate(rate);
248 
249     for (size_t index = 0; index < m_mediaElements.size(); ++index)
250         m_mediaElements[index]->updatePlaybackRate();
251 
252     // then queue a task to fire a simple event named ratechange at the MediaController.
253     scheduleEvent(EventTypeNames::ratechange);
254 }
255 
setVolume(double level,ExceptionState & exceptionState)256 void MediaController::setVolume(double level, ExceptionState& exceptionState)
257 {
258     if (m_volume == level)
259         return;
260 
261     // If the new value is outside the range 0.0 to 1.0 inclusive, then, on setting, an
262     // IndexSizeError exception must be raised instead.
263     if (level < 0 || level > 1) {
264         exceptionState.throwDOMException(IndexSizeError, ExceptionMessages::failedToSet("volume", "MediaController", "The value provided (" + String::number(level) + ") is not in the range [0.0, 1.0]."));
265         return;
266     }
267 
268     // The volume attribute, on setting, if the new value is in the range 0.0 to 1.0 inclusive,
269     // must set the MediaController's media controller volume multiplier to the new value
270     m_volume = level;
271 
272     // and queue a task to fire a simple event named volumechange at the MediaController.
273     scheduleEvent(EventTypeNames::volumechange);
274 
275     for (size_t index = 0; index < m_mediaElements.size(); ++index)
276         m_mediaElements[index]->updateVolume();
277 }
278 
setMuted(bool flag)279 void MediaController::setMuted(bool flag)
280 {
281     if (m_muted == flag)
282         return;
283 
284     // The muted attribute, on setting, must set the MediaController's media controller mute override
285     // to the new value
286     m_muted = flag;
287 
288     // and queue a task to fire a simple event named volumechange at the MediaController.
289     scheduleEvent(EventTypeNames::volumechange);
290 
291     for (size_t index = 0; index < m_mediaElements.size(); ++index)
292         m_mediaElements[index]->updateVolume();
293 }
294 
playbackStateWaiting()295 static const AtomicString& playbackStateWaiting()
296 {
297     DEFINE_STATIC_LOCAL(AtomicString, waiting, ("waiting", AtomicString::ConstructFromLiteral));
298     return waiting;
299 }
300 
playbackStatePlaying()301 static const AtomicString& playbackStatePlaying()
302 {
303     DEFINE_STATIC_LOCAL(AtomicString, playing, ("playing", AtomicString::ConstructFromLiteral));
304     return playing;
305 }
306 
playbackStateEnded()307 static const AtomicString& playbackStateEnded()
308 {
309     DEFINE_STATIC_LOCAL(AtomicString, ended, ("ended", AtomicString::ConstructFromLiteral));
310     return ended;
311 }
312 
playbackState() const313 const AtomicString& MediaController::playbackState() const
314 {
315     switch (m_playbackState) {
316     case WAITING:
317         return playbackStateWaiting();
318     case PLAYING:
319         return playbackStatePlaying();
320     case ENDED:
321         return playbackStateEnded();
322     default:
323         ASSERT_NOT_REACHED();
324         return nullAtom;
325     }
326 }
327 
reportControllerState()328 void MediaController::reportControllerState()
329 {
330     updateReadyState();
331     updatePlaybackState();
332 }
333 
eventNameForReadyState(MediaControllerInterface::ReadyState state)334 static AtomicString eventNameForReadyState(MediaControllerInterface::ReadyState state)
335 {
336     switch (state) {
337     case MediaControllerInterface::HAVE_NOTHING:
338         return EventTypeNames::emptied;
339     case MediaControllerInterface::HAVE_METADATA:
340         return EventTypeNames::loadedmetadata;
341     case MediaControllerInterface::HAVE_CURRENT_DATA:
342         return EventTypeNames::loadeddata;
343     case MediaControllerInterface::HAVE_FUTURE_DATA:
344         return EventTypeNames::canplay;
345     case MediaControllerInterface::HAVE_ENOUGH_DATA:
346         return EventTypeNames::canplaythrough;
347     default:
348         ASSERT_NOT_REACHED();
349         return nullAtom;
350     }
351 }
352 
updateReadyState()353 void MediaController::updateReadyState()
354 {
355     ReadyState oldReadyState = m_readyState;
356     ReadyState newReadyState;
357 
358     if (m_mediaElements.isEmpty()) {
359         // If the MediaController has no slaved media elements, let new readiness state be 0.
360         newReadyState = HAVE_NOTHING;
361     } else {
362         // Otherwise, let it have the lowest value of the readyState IDL attributes of all of its
363         // slaved media elements.
364         newReadyState = m_mediaElements.first()->readyState();
365         for (size_t index = 1; index < m_mediaElements.size(); ++index)
366             newReadyState = min(newReadyState, m_mediaElements[index]->readyState());
367     }
368 
369     if (newReadyState == oldReadyState)
370         return;
371 
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));
377         return;
378     }
379 
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;
384     do {
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);
392 
393     // Let the MediaController's most recently reported readiness state be new readiness state.
394     m_readyState = newReadyState;
395 }
396 
updatePlaybackState()397 void MediaController::updatePlaybackState()
398 {
399     PlaybackState oldPlaybackState = m_playbackState;
400     PlaybackState newPlaybackState;
401 
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;
417     } else {
418         // Otherwise
419         // Let new playback state be playing.
420         newPlaybackState = PLAYING;
421     }
422 
423     // If the MediaController's most recently reported playback state is not equal to new playback state
424     if (newPlaybackState == oldPlaybackState)
425         return;
426 
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
434             m_paused = true;
435 
436             // and then fires a simple event named pause at the MediaController object.
437             scheduleEvent(EventTypeNames::pause);
438         }
439     }
440 
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) {
446     case WAITING:
447         eventName = EventTypeNames::waiting;
448         m_clock->stop();
449         m_timeupdateTimer.stop();
450         break;
451     case ENDED:
452         eventName = EventTypeNames::ended;
453         m_clock->stop();
454         m_timeupdateTimer.stop();
455         break;
456     case PLAYING:
457         eventName = EventTypeNames::playing;
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 
updateMediaElements()472 void MediaController::updateMediaElements()
473 {
474     for (size_t index = 0; index < m_mediaElements.size(); ++index)
475         m_mediaElements[index]->updatePlayState();
476 }
477 
bringElementUpToSpeed(HTMLMediaElement * element)478 void MediaController::bringElementUpToSpeed(HTMLMediaElement* element)
479 {
480     ASSERT(element);
481     ASSERT(m_mediaElements.contains(element));
482 
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(), IGNORE_EXCEPTION);
487 }
488 
isBlocked() const489 bool MediaController::isBlocked() const
490 {
491     // A MediaController is a blocked media controller if the MediaController is a paused media
492     // controller,
493     if (m_paused)
494         return true;
495 
496     if (m_mediaElements.isEmpty())
497         return false;
498 
499     bool allPaused = true;
500     for (size_t index = 0; index < m_mediaElements.size(); ++index) {
501         HTMLMediaElement* element = m_mediaElements[index];
502         //  or if any of its slaved media elements are blocked media elements,
503         if (element->isBlocked())
504             return true;
505 
506         // or if any of its slaved media elements whose autoplaying flag is true still have their
507         // paused attribute set to true,
508         if (element->isAutoplaying() && element->paused())
509             return true;
510 
511         if (!element->paused())
512             allPaused = false;
513     }
514 
515     // or if all of its slaved media elements have their paused attribute set to true.
516     return allPaused;
517 }
518 
hasEnded() const519 bool MediaController::hasEnded() const
520 {
521     // If the ... media controller playback rate is positive or zero
522     if (m_clock->playRate() < 0)
523         return false;
524 
525     // [and] all of the MediaController's slaved media elements have ended playback ... let new
526     // playback state be ended.
527     if (m_mediaElements.isEmpty())
528         return false;
529 
530     bool allHaveEnded = true;
531     for (size_t index = 0; index < m_mediaElements.size(); ++index) {
532         if (!m_mediaElements[index]->ended())
533             allHaveEnded = false;
534     }
535     return allHaveEnded;
536 }
537 
scheduleEvent(const AtomicString & eventName)538 void MediaController::scheduleEvent(const AtomicString& eventName)
539 {
540     m_pendingEvents.append(Event::createCancelable(eventName));
541     if (!m_asyncEventTimer.isActive())
542         m_asyncEventTimer.startOneShot(0);
543 }
544 
asyncEventTimerFired(Timer<MediaController> *)545 void MediaController::asyncEventTimerFired(Timer<MediaController>*)
546 {
547     Vector<RefPtr<Event> > pendingEvents;
548 
549     m_pendingEvents.swap(pendingEvents);
550     size_t count = pendingEvents.size();
551     for (size_t index = 0; index < count; ++index)
552         dispatchEvent(pendingEvents[index].release(), IGNORE_EXCEPTION);
553 }
554 
clearPositionTimerFired(Timer<MediaController> *)555 void MediaController::clearPositionTimerFired(Timer<MediaController>*)
556 {
557     m_position = MediaPlayer::invalidTime();
558 }
559 
hasAudio() const560 bool MediaController::hasAudio() const
561 {
562     for (size_t index = 0; index < m_mediaElements.size(); ++index) {
563         if (m_mediaElements[index]->hasAudio())
564             return true;
565     }
566     return false;
567 }
568 
hasVideo() const569 bool MediaController::hasVideo() const
570 {
571     for (size_t index = 0; index < m_mediaElements.size(); ++index) {
572         if (m_mediaElements[index]->hasVideo())
573             return true;
574     }
575     return false;
576 }
577 
hasClosedCaptions() const578 bool MediaController::hasClosedCaptions() const
579 {
580     for (size_t index = 0; index < m_mediaElements.size(); ++index) {
581         if (m_mediaElements[index]->hasClosedCaptions())
582             return true;
583     }
584     return false;
585 }
586 
setClosedCaptionsVisible(bool visible)587 void MediaController::setClosedCaptionsVisible(bool visible)
588 {
589     m_closedCaptionsVisible = visible;
590     for (size_t index = 0; index < m_mediaElements.size(); ++index)
591         m_mediaElements[index]->setClosedCaptionsVisible(visible);
592 }
593 
beginScrubbing()594 void MediaController::beginScrubbing()
595 {
596     for (size_t index = 0; index < m_mediaElements.size(); ++index)
597         m_mediaElements[index]->beginScrubbing();
598     if (m_playbackState == PLAYING)
599         m_clock->stop();
600 }
601 
endScrubbing()602 void MediaController::endScrubbing()
603 {
604     for (size_t index = 0; index < m_mediaElements.size(); ++index)
605         m_mediaElements[index]->endScrubbing();
606     if (m_playbackState == PLAYING)
607         m_clock->start();
608 }
609 
canPlay() const610 bool MediaController::canPlay() const
611 {
612     if (m_paused)
613         return true;
614 
615     for (size_t index = 0; index < m_mediaElements.size(); ++index) {
616         if (!m_mediaElements[index]->canPlay())
617             return false;
618     }
619     return true;
620 }
621 
hasCurrentSrc() const622 bool MediaController::hasCurrentSrc() const
623 {
624     for (size_t index = 0; index < m_mediaElements.size(); ++index) {
625         if (!m_mediaElements[index]->hasCurrentSrc())
626             return false;
627     }
628     return true;
629 }
630 
interfaceName() const631 const AtomicString& MediaController::interfaceName() const
632 {
633     return EventTargetNames::MediaController;
634 }
635 
636 // The spec says to fire periodic timeupdate events (those sent while playing) every
637 // "15 to 250ms", we choose the slowest frequency
638 static const double maxTimeupdateEventFrequency = 0.25;
639 
startTimeupdateTimer()640 void MediaController::startTimeupdateTimer()
641 {
642     if (m_timeupdateTimer.isActive())
643         return;
644 
645     m_timeupdateTimer.startRepeating(maxTimeupdateEventFrequency);
646 }
647 
timeupdateTimerFired(Timer<MediaController> *)648 void MediaController::timeupdateTimerFired(Timer<MediaController>*)
649 {
650     scheduleTimeupdateEvent();
651 }
652 
scheduleTimeupdateEvent()653 void MediaController::scheduleTimeupdateEvent()
654 {
655     double now = WTF::currentTime();
656     double timedelta = now - m_previousTimeupdateTime;
657 
658     if (timedelta < maxTimeupdateEventFrequency)
659         return;
660 
661     scheduleEvent(EventTypeNames::timeupdate);
662     m_previousTimeupdateTime = now;
663 }
664