Home | History | Annotate | Download | only in html
      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 
     43 PassRefPtr<MediaController> MediaController::create(ExecutionContext* context)
     44 {
     45     return adoptRef(new MediaController(context));
     46 }
     47 
     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 
     67 MediaController::~MediaController()
     68 {
     69 }
     70 
     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 
     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 
     87 bool MediaController::containsMediaElement(HTMLMediaElement* element) const
     88 {
     89     return m_mediaElements.contains(element);
     90 }
     91 
     92 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 
    106 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 
    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 
    134 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 
    148 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 
    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 
    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 
    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 
    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 
    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 
    235 double MediaController::playbackRate() const
    236 {
    237     return m_clock->playRate();
    238 }
    239 
    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 
    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 
    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 
    295 static const AtomicString& playbackStateWaiting()
    296 {
    297     DEFINE_STATIC_LOCAL(AtomicString, waiting, ("waiting", AtomicString::ConstructFromLiteral));
    298     return waiting;
    299 }
    300 
    301 static const AtomicString& playbackStatePlaying()
    302 {
    303     DEFINE_STATIC_LOCAL(AtomicString, playing, ("playing", AtomicString::ConstructFromLiteral));
    304     return playing;
    305 }
    306 
    307 static const AtomicString& playbackStateEnded()
    308 {
    309     DEFINE_STATIC_LOCAL(AtomicString, ended, ("ended", AtomicString::ConstructFromLiteral));
    310     return ended;
    311 }
    312 
    313 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 
    328 void MediaController::reportControllerState()
    329 {
    330     updateReadyState();
    331     updatePlaybackState();
    332 }
    333 
    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 
    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 
    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 
    472 void MediaController::updateMediaElements()
    473 {
    474     for (size_t index = 0; index < m_mediaElements.size(); ++index)
    475         m_mediaElements[index]->updatePlayState();
    476 }
    477 
    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 
    489 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 
    519 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 
    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 
    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 
    555 void MediaController::clearPositionTimerFired(Timer<MediaController>*)
    556 {
    557     m_position = MediaPlayer::invalidTime();
    558 }
    559 
    560 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 
    569 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 
    578 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 
    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 
    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 
    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 
    610 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 
    622 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 
    631 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 
    640 void MediaController::startTimeupdateTimer()
    641 {
    642     if (m_timeupdateTimer.isActive())
    643         return;
    644 
    645     m_timeupdateTimer.startRepeating(maxTimeupdateEventFrequency);
    646 }
    647 
    648 void MediaController::timeupdateTimerFired(Timer<MediaController>*)
    649 {
    650     scheduleTimeupdateEvent();
    651 }
    652 
    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