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