Home | History | Annotate | Download | only in html
      1 /*
      2  * Copyright (C) 2007, 2008, 2009, 2010 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 "HTMLMediaElement.h"
     30 
     31 #include "Chrome.h"
     32 #include "ChromeClient.h"
     33 #include "ClientRect.h"
     34 #include "ClientRectList.h"
     35 #include "CSSHelper.h"
     36 #include "CSSPropertyNames.h"
     37 #include "CSSValueKeywords.h"
     38 #include "ContentType.h"
     39 #include "DocLoader.h"
     40 #include "Event.h"
     41 #include "EventNames.h"
     42 #include "ExceptionCode.h"
     43 #include "Frame.h"
     44 #include "FrameLoader.h"
     45 #include "FrameLoaderClient.h"
     46 #include "FrameView.h"
     47 #include "HTMLDocument.h"
     48 #include "HTMLNames.h"
     49 #include "HTMLSourceElement.h"
     50 #include "HTMLVideoElement.h"
     51 #include "MIMETypeRegistry.h"
     52 #include "MappedAttribute.h"
     53 #include "MediaDocument.h"
     54 #include "MediaError.h"
     55 #include "MediaList.h"
     56 #include "MediaPlayer.h"
     57 #include "MediaQueryEvaluator.h"
     58 #include "Page.h"
     59 #include "RenderVideo.h"
     60 #include "RenderView.h"
     61 #include "ScriptEventListener.h"
     62 #include "TimeRanges.h"
     63 #include <limits>
     64 #include <wtf/CurrentTime.h>
     65 #include <wtf/MathExtras.h>
     66 
     67 #if USE(ACCELERATED_COMPOSITING)
     68 #include "RenderView.h"
     69 #include "RenderLayerCompositor.h"
     70 #endif
     71 
     72 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
     73 #include "RenderPartObject.h"
     74 #include "Widget.h"
     75 #endif
     76 
     77 using namespace std;
     78 
     79 namespace WebCore {
     80 
     81 using namespace HTMLNames;
     82 
     83 HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document* doc)
     84     : HTMLElement(tagName, doc)
     85     , m_loadTimer(this, &HTMLMediaElement::loadTimerFired)
     86     , m_asyncEventTimer(this, &HTMLMediaElement::asyncEventTimerFired)
     87     , m_progressEventTimer(this, &HTMLMediaElement::progressEventTimerFired)
     88     , m_playbackProgressTimer(this, &HTMLMediaElement::playbackProgressTimerFired)
     89     , m_playedTimeRanges()
     90     , m_playbackRate(1.0f)
     91     , m_defaultPlaybackRate(1.0f)
     92     , m_webkitPreservesPitch(true)
     93     , m_networkState(NETWORK_EMPTY)
     94     , m_readyState(HAVE_NOTHING)
     95     , m_volume(1.0f)
     96     , m_lastSeekTime(0)
     97     , m_previousProgress(0)
     98     , m_previousProgressTime(numeric_limits<double>::max())
     99     , m_lastTimeUpdateEventWallTime(0)
    100     , m_lastTimeUpdateEventMovieTime(numeric_limits<float>::max())
    101     , m_loadState(WaitingForSource)
    102     , m_currentSourceNode(0)
    103     , m_player(0)
    104     , m_restrictions(NoRestrictions)
    105     , m_playing(false)
    106     , m_processingMediaPlayerCallback(0)
    107     , m_processingLoad(false)
    108     , m_delayingTheLoadEvent(false)
    109     , m_haveFiredLoadedData(false)
    110     , m_inActiveDocument(true)
    111     , m_autoplaying(true)
    112     , m_muted(false)
    113     , m_paused(true)
    114     , m_seeking(false)
    115     , m_sentStalledEvent(false)
    116     , m_sentEndEvent(false)
    117     , m_pausedInternal(false)
    118     , m_sendProgressEvents(true)
    119     , m_isFullscreen(false)
    120     , m_closedCaptionsVisible(false)
    121 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
    122     , m_needWidgetUpdate(false)
    123 #endif
    124 {
    125     document()->registerForDocumentActivationCallbacks(this);
    126     document()->registerForMediaVolumeCallbacks(this);
    127 }
    128 
    129 HTMLMediaElement::~HTMLMediaElement()
    130 {
    131     document()->unregisterForDocumentActivationCallbacks(this);
    132     document()->unregisterForMediaVolumeCallbacks(this);
    133 }
    134 
    135 void HTMLMediaElement::willMoveToNewOwnerDocument()
    136 {
    137     document()->unregisterForDocumentActivationCallbacks(this);
    138     document()->unregisterForMediaVolumeCallbacks(this);
    139     HTMLElement::willMoveToNewOwnerDocument();
    140 }
    141 
    142 void HTMLMediaElement::didMoveToNewOwnerDocument()
    143 {
    144     document()->registerForDocumentActivationCallbacks(this);
    145     document()->registerForMediaVolumeCallbacks(this);
    146     HTMLElement::didMoveToNewOwnerDocument();
    147 }
    148 
    149 
    150 bool HTMLMediaElement::checkDTD(const Node* newChild)
    151 {
    152     return newChild->hasTagName(sourceTag) || HTMLElement::checkDTD(newChild);
    153 }
    154 
    155 void HTMLMediaElement::attributeChanged(Attribute* attr, bool preserveDecls)
    156 {
    157     HTMLElement::attributeChanged(attr, preserveDecls);
    158 
    159     const QualifiedName& attrName = attr->name();
    160     if (attrName == srcAttr) {
    161         // don't have a src or any <source> children, trigger load
    162         if (inDocument() && m_loadState == WaitingForSource)
    163             scheduleLoad();
    164     }
    165 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO)
    166     else if (attrName == controlsAttr) {
    167         if (!isVideo() && attached() && (controls() != (renderer() != 0))) {
    168             detach();
    169             attach();
    170         }
    171         if (renderer())
    172             renderer()->updateFromElement();
    173     }
    174 #endif
    175 }
    176 
    177 void HTMLMediaElement::parseMappedAttribute(MappedAttribute* attr)
    178 {
    179     const QualifiedName& attrName = attr->name();
    180 
    181     if (attrName == autobufferAttr) {
    182         if (m_player)
    183             m_player->setAutobuffer(!attr->isNull());
    184     } else if (attrName == onabortAttr)
    185         setAttributeEventListener(eventNames().abortEvent, createAttributeEventListener(this, attr));
    186     else if (attrName == onbeforeloadAttr)
    187         setAttributeEventListener(eventNames().beforeloadEvent, createAttributeEventListener(this, attr));
    188     else if (attrName == oncanplayAttr)
    189         setAttributeEventListener(eventNames().canplayEvent, createAttributeEventListener(this, attr));
    190     else if (attrName == oncanplaythroughAttr)
    191         setAttributeEventListener(eventNames().canplaythroughEvent, createAttributeEventListener(this, attr));
    192     else if (attrName == ondurationchangeAttr)
    193         setAttributeEventListener(eventNames().durationchangeEvent, createAttributeEventListener(this, attr));
    194     else if (attrName == onemptiedAttr)
    195         setAttributeEventListener(eventNames().emptiedEvent, createAttributeEventListener(this, attr));
    196     else if (attrName == onendedAttr)
    197         setAttributeEventListener(eventNames().endedEvent, createAttributeEventListener(this, attr));
    198     else if (attrName == onerrorAttr)
    199         setAttributeEventListener(eventNames().errorEvent, createAttributeEventListener(this, attr));
    200     else if (attrName == onloadAttr)
    201         setAttributeEventListener(eventNames().loadEvent, createAttributeEventListener(this, attr));
    202     else if (attrName == onloadeddataAttr)
    203         setAttributeEventListener(eventNames().loadeddataEvent, createAttributeEventListener(this, attr));
    204     else if (attrName == onloadedmetadataAttr)
    205         setAttributeEventListener(eventNames().loadedmetadataEvent, createAttributeEventListener(this, attr));
    206     else if (attrName == onloadstartAttr)
    207         setAttributeEventListener(eventNames().loadstartEvent, createAttributeEventListener(this, attr));
    208     else if (attrName == onpauseAttr)
    209         setAttributeEventListener(eventNames().pauseEvent, createAttributeEventListener(this, attr));
    210     else if (attrName == onplayAttr)
    211         setAttributeEventListener(eventNames().playEvent, createAttributeEventListener(this, attr));
    212     else if (attrName == onplayingAttr)
    213         setAttributeEventListener(eventNames().playingEvent, createAttributeEventListener(this, attr));
    214     else if (attrName == onprogressAttr)
    215         setAttributeEventListener(eventNames().progressEvent, createAttributeEventListener(this, attr));
    216     else if (attrName == onratechangeAttr)
    217         setAttributeEventListener(eventNames().ratechangeEvent, createAttributeEventListener(this, attr));
    218     else if (attrName == onseekedAttr)
    219         setAttributeEventListener(eventNames().seekedEvent, createAttributeEventListener(this, attr));
    220     else if (attrName == onseekingAttr)
    221         setAttributeEventListener(eventNames().seekingEvent, createAttributeEventListener(this, attr));
    222     else if (attrName == onstalledAttr)
    223         setAttributeEventListener(eventNames().stalledEvent, createAttributeEventListener(this, attr));
    224     else if (attrName == onsuspendAttr)
    225         setAttributeEventListener(eventNames().suspendEvent, createAttributeEventListener(this, attr));
    226     else if (attrName == ontimeupdateAttr)
    227         setAttributeEventListener(eventNames().timeupdateEvent, createAttributeEventListener(this, attr));
    228     else if (attrName == onvolumechangeAttr)
    229         setAttributeEventListener(eventNames().volumechangeEvent, createAttributeEventListener(this, attr));
    230     else if (attrName == onwaitingAttr)
    231         setAttributeEventListener(eventNames().waitingEvent, createAttributeEventListener(this, attr));
    232     else if (attrName == onwebkitbeginfullscreenAttr)
    233         setAttributeEventListener(eventNames().webkitbeginfullscreenEvent, createAttributeEventListener(this, attr));
    234     else if (attrName == onwebkitendfullscreenAttr)
    235         setAttributeEventListener(eventNames().webkitendfullscreenEvent, createAttributeEventListener(this, attr));
    236     else
    237         HTMLElement::parseMappedAttribute(attr);
    238 }
    239 
    240 bool HTMLMediaElement::rendererIsNeeded(RenderStyle* style)
    241 {
    242 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
    243     UNUSED_PARAM(style);
    244     Frame* frame = document()->frame();
    245     if (!frame)
    246         return false;
    247 
    248     return true;
    249 #else
    250     return controls() ? HTMLElement::rendererIsNeeded(style) : false;
    251 #endif
    252 }
    253 
    254 RenderObject* HTMLMediaElement::createRenderer(RenderArena* arena, RenderStyle*)
    255 {
    256 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
    257     return new (arena) RenderEmbeddedObject(this);
    258 #else
    259     return new (arena) RenderMedia(this);
    260 #endif
    261 }
    262 
    263 void HTMLMediaElement::insertedIntoDocument()
    264 {
    265     HTMLElement::insertedIntoDocument();
    266     if (!src().isEmpty() && m_networkState == NETWORK_EMPTY)
    267         scheduleLoad();
    268 }
    269 
    270 void HTMLMediaElement::removedFromDocument()
    271 {
    272     if (m_networkState > NETWORK_EMPTY)
    273         pause(processingUserGesture());
    274     if (m_isFullscreen)
    275         exitFullscreen();
    276     HTMLElement::removedFromDocument();
    277 }
    278 
    279 void HTMLMediaElement::attach()
    280 {
    281     ASSERT(!attached());
    282 
    283 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
    284     m_needWidgetUpdate = true;
    285 #endif
    286 
    287     HTMLElement::attach();
    288 
    289     if (renderer())
    290         renderer()->updateFromElement();
    291 }
    292 
    293 void HTMLMediaElement::recalcStyle(StyleChange change)
    294 {
    295     HTMLElement::recalcStyle(change);
    296 
    297     if (renderer())
    298         renderer()->updateFromElement();
    299 }
    300 
    301 void HTMLMediaElement::scheduleLoad()
    302 {
    303     if (m_loadTimer.isActive())
    304         return;
    305     prepareForLoad();
    306     m_loadTimer.startOneShot(0);
    307 }
    308 
    309 void HTMLMediaElement::scheduleNextSourceChild()
    310 {
    311     // Schedule the timer to try the next <source> element WITHOUT resetting state ala prepareForLoad.
    312     m_loadTimer.startOneShot(0);
    313 }
    314 
    315 void HTMLMediaElement::scheduleEvent(const AtomicString& eventName)
    316 {
    317     m_pendingEvents.append(Event::create(eventName, false, true));
    318     if (!m_asyncEventTimer.isActive())
    319         m_asyncEventTimer.startOneShot(0);
    320 }
    321 
    322 void HTMLMediaElement::asyncEventTimerFired(Timer<HTMLMediaElement>*)
    323 {
    324     Vector<RefPtr<Event> > pendingEvents;
    325     ExceptionCode ec = 0;
    326 
    327     m_pendingEvents.swap(pendingEvents);
    328     unsigned count = pendingEvents.size();
    329     for (unsigned ndx = 0; ndx < count; ++ndx)
    330         dispatchEvent(pendingEvents[ndx].release(), ec);
    331 }
    332 
    333 void HTMLMediaElement::loadTimerFired(Timer<HTMLMediaElement>*)
    334 {
    335     if (m_loadState == LoadingFromSourceElement)
    336         loadNextSourceChild();
    337     else
    338         loadInternal();
    339 }
    340 
    341 static String serializeTimeOffset(float time)
    342 {
    343     String timeString = String::number(time);
    344     // FIXME serialize time offset values properly (format not specified yet)
    345     timeString.append("s");
    346     return timeString;
    347 }
    348 
    349 static float parseTimeOffset(const String& timeString, bool* ok = 0)
    350 {
    351     const UChar* characters = timeString.characters();
    352     unsigned length = timeString.length();
    353 
    354     if (length && characters[length - 1] == 's')
    355         length--;
    356 
    357     // FIXME parse time offset values (format not specified yet)
    358     float val = charactersToFloat(characters, length, ok);
    359     return val;
    360 }
    361 
    362 float HTMLMediaElement::getTimeOffsetAttribute(const QualifiedName& name, float valueOnError) const
    363 {
    364     bool ok;
    365     String timeString = getAttribute(name);
    366     float result = parseTimeOffset(timeString, &ok);
    367     if (ok)
    368         return result;
    369     return valueOnError;
    370 }
    371 
    372 void HTMLMediaElement::setTimeOffsetAttribute(const QualifiedName& name, float value)
    373 {
    374     setAttribute(name, serializeTimeOffset(value));
    375 }
    376 
    377 PassRefPtr<MediaError> HTMLMediaElement::error() const
    378 {
    379     return m_error;
    380 }
    381 
    382 KURL HTMLMediaElement::src() const
    383 {
    384     return document()->completeURL(getAttribute(srcAttr));
    385 }
    386 
    387 void HTMLMediaElement::setSrc(const String& url)
    388 {
    389     setAttribute(srcAttr, url);
    390 }
    391 
    392 String HTMLMediaElement::currentSrc() const
    393 {
    394     return m_currentSrc;
    395 }
    396 
    397 HTMLMediaElement::NetworkState HTMLMediaElement::networkState() const
    398 {
    399     return m_networkState;
    400 }
    401 
    402 String HTMLMediaElement::canPlayType(const String& mimeType) const
    403 {
    404     MediaPlayer::SupportsType support = MediaPlayer::supportsType(ContentType(mimeType));
    405     String canPlay;
    406 
    407     // 4.8.10.3
    408     switch (support)
    409     {
    410         case MediaPlayer::IsNotSupported:
    411             canPlay = "";
    412             break;
    413         case MediaPlayer::MayBeSupported:
    414             canPlay = "maybe";
    415             break;
    416         case MediaPlayer::IsSupported:
    417             canPlay = "probably";
    418             break;
    419     }
    420 
    421     return canPlay;
    422 }
    423 
    424 void HTMLMediaElement::load(bool isUserGesture, ExceptionCode& ec)
    425 {
    426     if (m_restrictions & RequireUserGestureForLoadRestriction && !isUserGesture)
    427         ec = INVALID_STATE_ERR;
    428     else {
    429         prepareForLoad();
    430         loadInternal();
    431     }
    432 }
    433 
    434 void HTMLMediaElement::prepareForLoad()
    435 {
    436     // Perform the cleanup required for the resource load algorithm to run.
    437     stopPeriodicTimers();
    438     m_loadTimer.stop();
    439     m_sentStalledEvent = false;
    440     m_haveFiredLoadedData = false;
    441 
    442     // 2 - Abort any already-running instance of the resource selection algorithm for this element.
    443     m_currentSourceNode = 0;
    444 
    445     // 3 - If there are any tasks from the media element's media element event task source in
    446     // one of the task queues, then remove those tasks.
    447     cancelPendingEventsAndCallbacks();
    448 }
    449 
    450 void HTMLMediaElement::loadInternal()
    451 {
    452     // If the load() method for this element is already being invoked, then abort these steps.
    453     if (m_processingLoad)
    454         return;
    455     m_processingLoad = true;
    456 
    457     // Steps 1 and 2 were done in prepareForLoad()
    458 
    459     // 3 - If the media element's networkState is set to NETWORK_LOADING or NETWORK_IDLE, queue
    460     // a task to fire a simple event named abort at the media element.
    461     if (m_networkState == NETWORK_LOADING || m_networkState == NETWORK_IDLE)
    462         scheduleEvent(eventNames().abortEvent);
    463 
    464     // 4
    465     if (m_networkState != NETWORK_EMPTY) {
    466         m_networkState = NETWORK_EMPTY;
    467         m_readyState = HAVE_NOTHING;
    468         m_paused = true;
    469         m_seeking = false;
    470         if (m_player) {
    471             m_player->pause();
    472             m_playing = false;
    473             m_player->seek(0);
    474         }
    475         scheduleEvent(eventNames().emptiedEvent);
    476     }
    477 
    478     // 5 - Set the playbackRate attribute to the value of the defaultPlaybackRate attribute.
    479     setPlaybackRate(defaultPlaybackRate());
    480 
    481     // 6 - Set the error attribute to null and the autoplaying flag to true.
    482     m_error = 0;
    483     m_autoplaying = true;
    484 
    485     m_playedTimeRanges = TimeRanges::create();
    486     m_lastSeekTime = 0;
    487     m_closedCaptionsVisible = false;
    488 
    489     // 7 - Invoke the media element's resource selection algorithm.
    490     selectMediaResource();
    491     m_processingLoad = false;
    492 }
    493 
    494 void HTMLMediaElement::selectMediaResource()
    495 {
    496     // 1 - Set the networkState to NETWORK_NO_SOURCE
    497     m_networkState = NETWORK_NO_SOURCE;
    498 
    499     // 2 - Asynchronously await a stable state.
    500 
    501     // 3 - ... the media element has neither a src attribute ...
    502     String mediaSrc = getAttribute(srcAttr);
    503     if (!mediaSrc) {
    504         // ... nor a source element child: ...
    505         Node* node;
    506         for (node = firstChild(); node; node = node->nextSibling()) {
    507             if (node->hasTagName(sourceTag))
    508                 break;
    509         }
    510 
    511         if (!node) {
    512             m_loadState = WaitingForSource;
    513 
    514             // ... set the networkState to NETWORK_EMPTY, and abort these steps
    515             m_networkState = NETWORK_EMPTY;
    516             ASSERT(!m_delayingTheLoadEvent);
    517             return;
    518         }
    519     }
    520 
    521     // 4
    522     m_delayingTheLoadEvent = true;
    523     m_networkState = NETWORK_LOADING;
    524 
    525     // 5
    526     scheduleEvent(eventNames().loadstartEvent);
    527 
    528     // 6 - If the media element has a src attribute, then run these substeps
    529     ContentType contentType("");
    530     if (!mediaSrc.isNull()) {
    531         KURL mediaURL = document()->completeURL(mediaSrc);
    532         if (isSafeToLoadURL(mediaURL, Complain) && dispatchBeforeLoadEvent(mediaURL.string())) {
    533             m_loadState = LoadingFromSrcAttr;
    534             loadResource(mediaURL, contentType);
    535         } else
    536             noneSupported();
    537 
    538         return;
    539     }
    540 
    541     // Otherwise, the source elements will be used
    542     m_currentSourceNode = 0;
    543     loadNextSourceChild();
    544 }
    545 
    546 void HTMLMediaElement::loadNextSourceChild()
    547 {
    548     ContentType contentType("");
    549     KURL mediaURL = selectNextSourceChild(&contentType, Complain);
    550     if (!mediaURL.isValid()) {
    551         waitForSourceChange();
    552         return;
    553     }
    554 
    555     m_loadState = LoadingFromSourceElement;
    556     loadResource(mediaURL, contentType);
    557 }
    558 
    559 void HTMLMediaElement::loadResource(const KURL& initialURL, ContentType& contentType)
    560 {
    561     ASSERT(isSafeToLoadURL(initialURL, Complain));
    562 
    563     Frame* frame = document()->frame();
    564     if (!frame)
    565         return;
    566     FrameLoader* loader = frame->loader();
    567     if (!loader)
    568         return;
    569 
    570     KURL url(initialURL);
    571     if (!loader->willLoadMediaElementURL(url))
    572         return;
    573 
    574     // The resource fetch algorithm
    575     m_networkState = NETWORK_LOADING;
    576 
    577     m_currentSrc = url;
    578 
    579     if (m_sendProgressEvents)
    580         startProgressEventTimer();
    581 
    582 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO)
    583     m_player = MediaPlayer::create(this);
    584 #else
    585     if (!m_player)
    586         m_player = MediaPlayer::create(this);
    587 #endif
    588 
    589     m_player->setAutobuffer(autobuffer());
    590     m_player->setPreservesPitch(m_webkitPreservesPitch);
    591     updateVolume();
    592 
    593     m_player->load(m_currentSrc, contentType);
    594 
    595     if (isVideo() && m_player->canLoadPoster()) {
    596         KURL posterUrl = static_cast<HTMLVideoElement*>(this)->poster();
    597         if (!posterUrl.isEmpty())
    598             m_player->setPoster(posterUrl);
    599     }
    600 
    601     if (renderer())
    602         renderer()->updateFromElement();
    603 }
    604 
    605 bool HTMLMediaElement::isSafeToLoadURL(const KURL& url, InvalidSourceAction actionIfInvalid)
    606 {
    607     Frame* frame = document()->frame();
    608     FrameLoader* loader = frame ? frame->loader() : 0;
    609 
    610     // don't allow remote to local urls, and check with the frame loader client.
    611     if (!loader || !SecurityOrigin::canLoad(url, String(), document())) {
    612         if (actionIfInvalid == Complain)
    613             FrameLoader::reportLocalLoadFailed(frame, url.string());
    614         return false;
    615     }
    616 
    617     return true;
    618 }
    619 
    620 void HTMLMediaElement::startProgressEventTimer()
    621 {
    622     if (m_progressEventTimer.isActive())
    623         return;
    624 
    625     m_previousProgressTime = WTF::currentTime();
    626     m_previousProgress = 0;
    627     // 350ms is not magic, it is in the spec!
    628     m_progressEventTimer.startRepeating(0.350);
    629 }
    630 
    631 void HTMLMediaElement::waitForSourceChange()
    632 {
    633     stopPeriodicTimers();
    634     m_loadState = WaitingForSource;
    635 
    636     // 6.17 - Waiting: Set the element's networkState attribute to the NETWORK_NO_SOURCE value
    637     m_networkState = NETWORK_NO_SOURCE;
    638 
    639     // 6.18 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
    640     m_delayingTheLoadEvent = false;
    641 }
    642 
    643 void HTMLMediaElement::noneSupported()
    644 {
    645     stopPeriodicTimers();
    646     m_loadState = WaitingForSource;
    647     m_currentSourceNode = 0;
    648 
    649     // 5 - Reaching this step indicates that either the URL failed to resolve, or the media
    650     // resource failed to load. Set the error attribute to a new MediaError object whose
    651     // code attribute is set to MEDIA_ERR_SRC_NOT_SUPPORTED.
    652     m_error = MediaError::create(MediaError::MEDIA_ERR_SRC_NOT_SUPPORTED);
    653 
    654     // 6 - Set the element's networkState attribute to the NETWORK_NO_SOURCE value.
    655     m_networkState = NETWORK_NO_SOURCE;
    656 
    657     // 7 - Queue a task to fire a progress event called error at the media element, in
    658     // the context of the fetching process that was used to try to obtain the media
    659     // resource in the resource fetch algorithm.
    660     scheduleEvent(eventNames().errorEvent);
    661 
    662     // 8 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
    663     m_delayingTheLoadEvent = false;
    664 
    665     // 9 -Abort these steps. Until the load() method is invoked, the element won't attempt to load another resource.
    666 
    667     if (isVideo())
    668         static_cast<HTMLVideoElement*>(this)->updatePosterImage();
    669     if (renderer())
    670         renderer()->updateFromElement();
    671 }
    672 
    673 void HTMLMediaElement::mediaEngineError(PassRefPtr<MediaError> err)
    674 {
    675     // 1 - The user agent should cancel the fetching process.
    676     stopPeriodicTimers();
    677     m_loadState = WaitingForSource;
    678 
    679     // 2 - Set the error attribute to a new MediaError object whose code attribute is
    680     // set to MEDIA_ERR_NETWORK/MEDIA_ERR_DECODE.
    681     m_error = err;
    682 
    683     // 3 - Queue a task to fire a progress event called error at the media element, in
    684     // the context of the fetching process started by this instance of this algorithm.
    685     scheduleEvent(eventNames().errorEvent);
    686 
    687     // 4 - Set the element's networkState attribute to the NETWORK_EMPTY value and queue a
    688     // task to fire a simple event called emptied at the element.
    689     m_networkState = NETWORK_EMPTY;
    690     scheduleEvent(eventNames().emptiedEvent);
    691 
    692     // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
    693     m_delayingTheLoadEvent = false;
    694 
    695     // 6 - Abort the overall resource selection algorithm.
    696     m_currentSourceNode = 0;
    697 }
    698 
    699 void HTMLMediaElement::cancelPendingEventsAndCallbacks()
    700 {
    701     m_pendingEvents.clear();
    702 
    703     for (Node* node = firstChild(); node; node = node->nextSibling()) {
    704         if (node->hasTagName(sourceTag))
    705             static_cast<HTMLSourceElement*>(node)->cancelPendingErrorEvent();
    706     }
    707 }
    708 
    709 void HTMLMediaElement::mediaPlayerNetworkStateChanged(MediaPlayer*)
    710 {
    711     beginProcessingMediaPlayerCallback();
    712     setNetworkState(m_player->networkState());
    713     endProcessingMediaPlayerCallback();
    714 }
    715 
    716 void HTMLMediaElement::setNetworkState(MediaPlayer::NetworkState state)
    717 {
    718     if (state == MediaPlayer::Empty) {
    719         // just update the cached state and leave, we can't do anything
    720         m_networkState = NETWORK_EMPTY;
    721         return;
    722     }
    723 
    724     if (state == MediaPlayer::FormatError || state == MediaPlayer::NetworkError || state == MediaPlayer::DecodeError) {
    725         stopPeriodicTimers();
    726 
    727         // If we failed while trying to load a <source> element, the movie was never parsed, and there are more
    728         // <source> children, schedule the next one
    729         if (m_readyState < HAVE_METADATA && m_loadState == LoadingFromSourceElement) {
    730             m_currentSourceNode->scheduleErrorEvent();
    731             if (havePotentialSourceChild())
    732                 scheduleNextSourceChild();
    733             else
    734                 waitForSourceChange();
    735 
    736             return;
    737         }
    738 
    739         if (state == MediaPlayer::NetworkError)
    740             mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_NETWORK));
    741         else if (state == MediaPlayer::DecodeError)
    742             mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_DECODE));
    743         else if (state == MediaPlayer::FormatError && m_loadState == LoadingFromSrcAttr)
    744             noneSupported();
    745 
    746         if (isVideo())
    747             static_cast<HTMLVideoElement*>(this)->updatePosterImage();
    748 
    749         return;
    750     }
    751 
    752     if (state == MediaPlayer::Idle) {
    753         if (m_networkState > NETWORK_IDLE) {
    754             stopPeriodicTimers();
    755             scheduleEvent(eventNames().suspendEvent);
    756         }
    757         m_networkState = NETWORK_IDLE;
    758     }
    759 
    760     if (state == MediaPlayer::Loading) {
    761         if (m_networkState < NETWORK_LOADING || m_networkState == NETWORK_NO_SOURCE)
    762             startProgressEventTimer();
    763         m_networkState = NETWORK_LOADING;
    764     }
    765 
    766     if (state == MediaPlayer::Loaded) {
    767         NetworkState oldState = m_networkState;
    768 
    769         m_networkState = NETWORK_LOADED;
    770         if (oldState < NETWORK_LOADED || oldState == NETWORK_NO_SOURCE) {
    771             m_progressEventTimer.stop();
    772 
    773             // Schedule one last progress event so we guarantee that at least one is fired
    774             // for files that load very quickly.
    775             scheduleEvent(eventNames().progressEvent);
    776 
    777             // Check to see if readyState changes need to be dealt with before sending the
    778             // 'load' event so we report 'canplaythrough' first. This is necessary because a
    779             //  media engine reports readyState and networkState changes separately
    780             MediaPlayer::ReadyState currentState = m_player->readyState();
    781             if (static_cast<ReadyState>(currentState) != m_readyState)
    782                 setReadyState(currentState);
    783 
    784             scheduleEvent(eventNames().loadEvent);
    785         }
    786     }
    787 }
    788 
    789 void HTMLMediaElement::mediaPlayerReadyStateChanged(MediaPlayer*)
    790 {
    791     beginProcessingMediaPlayerCallback();
    792 
    793     setReadyState(m_player->readyState());
    794 
    795     endProcessingMediaPlayerCallback();
    796 }
    797 
    798 void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state)
    799 {
    800     // Set "wasPotentiallyPlaying" BEFORE updating m_readyState, potentiallyPlaying() uses it
    801     bool wasPotentiallyPlaying = potentiallyPlaying();
    802 
    803     ReadyState oldState = m_readyState;
    804     m_readyState = static_cast<ReadyState>(state);
    805 
    806     if (m_readyState == oldState)
    807         return;
    808 
    809     if (m_networkState == NETWORK_EMPTY)
    810         return;
    811 
    812     if (m_seeking) {
    813         // 4.8.10.10, step 8
    814         if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA)
    815             scheduleEvent(eventNames().waitingEvent);
    816 
    817         // 4.8.10.10, step 9
    818         if (m_readyState < HAVE_CURRENT_DATA) {
    819             if (oldState >= HAVE_CURRENT_DATA)
    820                 scheduleEvent(eventNames().seekingEvent);
    821         } else {
    822             // 4.8.10.10 step 12 & 13.
    823             finishSeek();
    824         }
    825 
    826     } else {
    827         if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA) {
    828             // 4.8.10.9
    829             scheduleTimeupdateEvent(false);
    830             scheduleEvent(eventNames().waitingEvent);
    831         }
    832     }
    833 
    834     if (m_readyState >= HAVE_METADATA && oldState < HAVE_METADATA) {
    835         scheduleEvent(eventNames().durationchangeEvent);
    836         scheduleEvent(eventNames().loadedmetadataEvent);
    837 
    838 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO)
    839         if (renderer() && renderer()->isVideo()) {
    840             toRenderVideo(renderer())->videoSizeChanged();
    841         }
    842 #endif
    843         m_delayingTheLoadEvent = false;
    844         m_player->seek(0);
    845     }
    846 
    847     bool shouldUpdatePosterImage = false;
    848 
    849     // 4.8.10.7 says loadeddata is sent only when the new state *is* HAVE_CURRENT_DATA: "If the
    850     // previous ready state was HAVE_METADATA and the new ready state is HAVE_CURRENT_DATA",
    851     // but the event table at the end of the spec says it is sent when: "readyState newly
    852     // increased to HAVE_CURRENT_DATA  or greater for the first time"
    853     // We go with the later because it seems useful to count on getting this event
    854     if (m_readyState >= HAVE_CURRENT_DATA && oldState < HAVE_CURRENT_DATA && !m_haveFiredLoadedData) {
    855         m_haveFiredLoadedData = true;
    856         shouldUpdatePosterImage = true;
    857         scheduleEvent(eventNames().loadeddataEvent);
    858     }
    859 
    860     bool isPotentiallyPlaying = potentiallyPlaying();
    861     if (m_readyState == HAVE_FUTURE_DATA && oldState <= HAVE_CURRENT_DATA) {
    862         scheduleEvent(eventNames().canplayEvent);
    863         if (isPotentiallyPlaying)
    864             scheduleEvent(eventNames().playingEvent);
    865         shouldUpdatePosterImage = true;
    866     }
    867 
    868     if (m_readyState == HAVE_ENOUGH_DATA && oldState < HAVE_ENOUGH_DATA) {
    869         if (oldState <= HAVE_CURRENT_DATA)
    870             scheduleEvent(eventNames().canplayEvent);
    871 
    872         scheduleEvent(eventNames().canplaythroughEvent);
    873 
    874         if (isPotentiallyPlaying && oldState <= HAVE_CURRENT_DATA)
    875             scheduleEvent(eventNames().playingEvent);
    876 
    877         if (m_autoplaying && m_paused && autoplay()) {
    878             m_paused = false;
    879             scheduleEvent(eventNames().playEvent);
    880             scheduleEvent(eventNames().playingEvent);
    881         }
    882 
    883         shouldUpdatePosterImage = true;
    884     }
    885 
    886     if (shouldUpdatePosterImage && isVideo())
    887         static_cast<HTMLVideoElement*>(this)->updatePosterImage();
    888 
    889     updatePlayState();
    890 }
    891 
    892 void HTMLMediaElement::progressEventTimerFired(Timer<HTMLMediaElement>*)
    893 {
    894     ASSERT(m_player);
    895     if (m_networkState == NETWORK_EMPTY || m_networkState >= NETWORK_LOADED)
    896         return;
    897 
    898     unsigned progress = m_player->bytesLoaded();
    899     double time = WTF::currentTime();
    900     double timedelta = time - m_previousProgressTime;
    901 
    902     if (progress == m_previousProgress) {
    903         if (timedelta > 3.0 && !m_sentStalledEvent) {
    904             scheduleEvent(eventNames().stalledEvent);
    905             m_sentStalledEvent = true;
    906         }
    907     } else {
    908         scheduleEvent(eventNames().progressEvent);
    909         m_previousProgress = progress;
    910         m_previousProgressTime = time;
    911         m_sentStalledEvent = false;
    912         if (renderer())
    913             renderer()->updateFromElement();
    914     }
    915 }
    916 
    917 void HTMLMediaElement::rewind(float timeDelta)
    918 {
    919     ExceptionCode e;
    920     setCurrentTime(max(currentTime() - timeDelta, minTimeSeekable()), e);
    921 }
    922 
    923 void HTMLMediaElement::returnToRealtime()
    924 {
    925     ExceptionCode e;
    926     setCurrentTime(maxTimeSeekable(), e);
    927 }
    928 
    929 void HTMLMediaElement::addPlayedRange(float start, float end)
    930 {
    931     if (!m_playedTimeRanges)
    932         m_playedTimeRanges = TimeRanges::create();
    933     m_playedTimeRanges->add(start, end);
    934 }
    935 
    936 bool HTMLMediaElement::supportsSave() const
    937 {
    938     return m_player ? m_player->supportsSave() : false;
    939 }
    940 
    941 void HTMLMediaElement::seek(float time, ExceptionCode& ec)
    942 {
    943     // 4.8.10.10. Seeking
    944     // 1
    945     if (m_readyState == HAVE_NOTHING || !m_player) {
    946         ec = INVALID_STATE_ERR;
    947         return;
    948     }
    949 
    950     // 2
    951     time = min(time, duration());
    952 
    953     // 3
    954     time = max(time, 0.0f);
    955 
    956     // 4
    957     RefPtr<TimeRanges> seekableRanges = seekable();
    958     if (!seekableRanges->contain(time)) {
    959         ec = INDEX_SIZE_ERR;
    960         return;
    961     }
    962 
    963     // avoid generating events when the time won't actually change
    964     float now = currentTime();
    965     if (time == now)
    966         return;
    967 
    968     // 5
    969     if (m_playing) {
    970         if (m_lastSeekTime < now)
    971             addPlayedRange(m_lastSeekTime, now);
    972     }
    973     m_lastSeekTime = time;
    974 
    975     // 6 - set the seeking flag, it will be cleared when the engine tells is the time has actually changed
    976     m_seeking = true;
    977 
    978     // 7
    979     scheduleTimeupdateEvent(false);
    980 
    981     // 8 - this is covered, if necessary, when the engine signals a readystate change
    982 
    983     // 10
    984     m_player->seek(time);
    985     m_sentEndEvent = false;
    986 }
    987 
    988 void HTMLMediaElement::finishSeek()
    989 {
    990     // 4.8.10.10 Seeking step 12
    991     m_seeking = false;
    992 
    993     // 4.8.10.10 Seeking step 13
    994     scheduleEvent(eventNames().seekedEvent);
    995 }
    996 
    997 HTMLMediaElement::ReadyState HTMLMediaElement::readyState() const
    998 {
    999     return m_readyState;
   1000 }
   1001 
   1002 MediaPlayer::MovieLoadType HTMLMediaElement::movieLoadType() const
   1003 {
   1004     return m_player ? m_player->movieLoadType() : MediaPlayer::Unknown;
   1005 }
   1006 
   1007 bool HTMLMediaElement::hasAudio() const
   1008 {
   1009     return m_player ? m_player->hasAudio() : false;
   1010 }
   1011 
   1012 bool HTMLMediaElement::seeking() const
   1013 {
   1014     return m_seeking;
   1015 }
   1016 
   1017 // playback state
   1018 float HTMLMediaElement::currentTime() const
   1019 {
   1020     if (!m_player)
   1021         return 0;
   1022     if (m_seeking)
   1023         return m_lastSeekTime;
   1024     return m_player->currentTime();
   1025 }
   1026 
   1027 void HTMLMediaElement::setCurrentTime(float time, ExceptionCode& ec)
   1028 {
   1029     seek(time, ec);
   1030 }
   1031 
   1032 float HTMLMediaElement::startTime() const
   1033 {
   1034     if (!m_player)
   1035         return 0;
   1036     return m_player->startTime();
   1037 }
   1038 
   1039 float HTMLMediaElement::duration() const
   1040 {
   1041     if (m_readyState >= HAVE_METADATA)
   1042         return m_player->duration();
   1043 
   1044     return numeric_limits<float>::quiet_NaN();
   1045 }
   1046 
   1047 bool HTMLMediaElement::paused() const
   1048 {
   1049     return m_paused;
   1050 }
   1051 
   1052 float HTMLMediaElement::defaultPlaybackRate() const
   1053 {
   1054     return m_defaultPlaybackRate;
   1055 }
   1056 
   1057 void HTMLMediaElement::setDefaultPlaybackRate(float rate)
   1058 {
   1059     if (m_defaultPlaybackRate != rate) {
   1060         m_defaultPlaybackRate = rate;
   1061         scheduleEvent(eventNames().ratechangeEvent);
   1062     }
   1063 }
   1064 
   1065 float HTMLMediaElement::playbackRate() const
   1066 {
   1067     return m_player ? m_player->rate() : 0;
   1068 }
   1069 
   1070 void HTMLMediaElement::setPlaybackRate(float rate)
   1071 {
   1072     if (m_playbackRate != rate) {
   1073         m_playbackRate = rate;
   1074         scheduleEvent(eventNames().ratechangeEvent);
   1075     }
   1076     if (m_player && potentiallyPlaying() && m_player->rate() != rate)
   1077         m_player->setRate(rate);
   1078 }
   1079 
   1080 bool HTMLMediaElement::webkitPreservesPitch() const
   1081 {
   1082     return m_webkitPreservesPitch;
   1083 }
   1084 
   1085 void HTMLMediaElement::setWebkitPreservesPitch(bool preservesPitch)
   1086 {
   1087     m_webkitPreservesPitch = preservesPitch;
   1088 
   1089     if (!m_player)
   1090         return;
   1091 
   1092     m_player->setPreservesPitch(preservesPitch);
   1093 }
   1094 
   1095 bool HTMLMediaElement::ended() const
   1096 {
   1097     // 4.8.10.8 Playing the media resource
   1098     // The ended attribute must return true if the media element has ended
   1099     // playback and the direction of playback is forwards, and false otherwise.
   1100     return endedPlayback() && m_playbackRate > 0;
   1101 }
   1102 
   1103 bool HTMLMediaElement::autoplay() const
   1104 {
   1105     return hasAttribute(autoplayAttr);
   1106 }
   1107 
   1108 void HTMLMediaElement::setAutoplay(bool b)
   1109 {
   1110     setBooleanAttribute(autoplayAttr, b);
   1111 }
   1112 
   1113 bool HTMLMediaElement::autobuffer() const
   1114 {
   1115     return hasAttribute(autobufferAttr);
   1116 }
   1117 
   1118 void HTMLMediaElement::setAutobuffer(bool b)
   1119 {
   1120     setBooleanAttribute(autobufferAttr, b);
   1121 }
   1122 
   1123 void HTMLMediaElement::play(bool isUserGesture)
   1124 {
   1125     if (m_restrictions & RequireUserGestureForRateChangeRestriction && !isUserGesture)
   1126         return;
   1127 
   1128     playInternal();
   1129 }
   1130 
   1131 void HTMLMediaElement::playInternal()
   1132 {
   1133     // 4.8.10.9. Playing the media resource
   1134     if (!m_player || m_networkState == NETWORK_EMPTY)
   1135         scheduleLoad();
   1136 
   1137     if (endedPlayback()) {
   1138         ExceptionCode unused;
   1139         seek(0, unused);
   1140     }
   1141 
   1142     setPlaybackRate(defaultPlaybackRate());
   1143 
   1144     if (m_paused) {
   1145         m_paused = false;
   1146         scheduleEvent(eventNames().playEvent);
   1147 
   1148         if (m_readyState <= HAVE_CURRENT_DATA)
   1149             scheduleEvent(eventNames().waitingEvent);
   1150         else if (m_readyState >= HAVE_FUTURE_DATA)
   1151             scheduleEvent(eventNames().playingEvent);
   1152     }
   1153     m_autoplaying = false;
   1154 
   1155     updatePlayState();
   1156 }
   1157 
   1158 void HTMLMediaElement::pause(bool isUserGesture)
   1159 {
   1160     if (m_restrictions & RequireUserGestureForRateChangeRestriction && !isUserGesture)
   1161         return;
   1162 
   1163     pauseInternal();
   1164 }
   1165 
   1166 
   1167 void HTMLMediaElement::pauseInternal()
   1168 {
   1169     // 4.8.10.9. Playing the media resource
   1170     if (!m_player || m_networkState == NETWORK_EMPTY)
   1171         scheduleLoad();
   1172 
   1173     m_autoplaying = false;
   1174 
   1175     if (!m_paused) {
   1176         m_paused = true;
   1177         scheduleTimeupdateEvent(false);
   1178         scheduleEvent(eventNames().pauseEvent);
   1179     }
   1180 
   1181     updatePlayState();
   1182 }
   1183 
   1184 bool HTMLMediaElement::loop() const
   1185 {
   1186     return hasAttribute(loopAttr);
   1187 }
   1188 
   1189 void HTMLMediaElement::setLoop(bool b)
   1190 {
   1191     setBooleanAttribute(loopAttr, b);
   1192 }
   1193 
   1194 bool HTMLMediaElement::controls() const
   1195 {
   1196     Frame* frame = document()->frame();
   1197 
   1198     // always show controls when scripting is disabled
   1199     if (frame && !frame->script()->canExecuteScripts())
   1200         return true;
   1201 
   1202     return hasAttribute(controlsAttr);
   1203 }
   1204 
   1205 void HTMLMediaElement::setControls(bool b)
   1206 {
   1207     setBooleanAttribute(controlsAttr, b);
   1208 }
   1209 
   1210 float HTMLMediaElement::volume() const
   1211 {
   1212     return m_volume;
   1213 }
   1214 
   1215 void HTMLMediaElement::setVolume(float vol, ExceptionCode& ec)
   1216 {
   1217     if (vol < 0.0f || vol > 1.0f) {
   1218         ec = INDEX_SIZE_ERR;
   1219         return;
   1220     }
   1221 
   1222     if (m_volume != vol) {
   1223         m_volume = vol;
   1224         updateVolume();
   1225         scheduleEvent(eventNames().volumechangeEvent);
   1226     }
   1227 }
   1228 
   1229 bool HTMLMediaElement::muted() const
   1230 {
   1231     return m_muted;
   1232 }
   1233 
   1234 void HTMLMediaElement::setMuted(bool muted)
   1235 {
   1236     if (m_muted != muted) {
   1237         m_muted = muted;
   1238         // Avoid recursion when the player reports volume changes.
   1239         if (!processingMediaPlayerCallback()) {
   1240             if (m_player && m_player->supportsMuting()) {
   1241                 m_player->setMuted(m_muted);
   1242                 if (renderer())
   1243                     renderer()->updateFromElement();
   1244             } else
   1245                 updateVolume();
   1246         }
   1247         scheduleEvent(eventNames().volumechangeEvent);
   1248     }
   1249 }
   1250 
   1251 void HTMLMediaElement::togglePlayState()
   1252 {
   1253     // We can safely call the internal play/pause methods, which don't check restrictions, because
   1254     // this method is only called from the built-in media controller
   1255     if (canPlay())
   1256         playInternal();
   1257     else
   1258         pauseInternal();
   1259 }
   1260 
   1261 void HTMLMediaElement::beginScrubbing()
   1262 {
   1263     if (!paused()) {
   1264         if (ended()) {
   1265             // Because a media element stays in non-paused state when it reaches end, playback resumes
   1266             // when the slider is dragged from the end to another position unless we pause first. Do
   1267             // a "hard pause" so an event is generated, since we want to stay paused after scrubbing finishes.
   1268             pause(processingUserGesture());
   1269         } else {
   1270             // Not at the end but we still want to pause playback so the media engine doesn't try to
   1271             // continue playing during scrubbing. Pause without generating an event as we will
   1272             // unpause after scrubbing finishes.
   1273             setPausedInternal(true);
   1274         }
   1275     }
   1276 }
   1277 
   1278 void HTMLMediaElement::endScrubbing()
   1279 {
   1280     if (m_pausedInternal)
   1281         setPausedInternal(false);
   1282 }
   1283 
   1284 // The spec says to fire periodic timeupdate events (those sent while playing) every
   1285 // "15 to 250ms", we choose the slowest frequency
   1286 static const double maxTimeupdateEventFrequency = 0.25;
   1287 
   1288 void HTMLMediaElement::startPlaybackProgressTimer()
   1289 {
   1290     if (m_playbackProgressTimer.isActive())
   1291         return;
   1292 
   1293     m_previousProgressTime = WTF::currentTime();
   1294     m_previousProgress = 0;
   1295     m_playbackProgressTimer.startRepeating(maxTimeupdateEventFrequency);
   1296 }
   1297 
   1298 void HTMLMediaElement::playbackProgressTimerFired(Timer<HTMLMediaElement>*)
   1299 {
   1300     ASSERT(m_player);
   1301     if (!m_playbackRate)
   1302         return;
   1303 
   1304     scheduleTimeupdateEvent(true);
   1305 
   1306     // FIXME: deal with cue ranges here
   1307 }
   1308 
   1309 void HTMLMediaElement::scheduleTimeupdateEvent(bool periodicEvent)
   1310 {
   1311     double now = WTF::currentTime();
   1312     double timedelta = now - m_lastTimeUpdateEventWallTime;
   1313 
   1314     // throttle the periodic events
   1315     if (periodicEvent && timedelta < maxTimeupdateEventFrequency)
   1316         return;
   1317 
   1318     // Some media engines make multiple "time changed" callbacks at the same time, but we only want one
   1319     // event at a given time so filter here
   1320     float movieTime = m_player ? m_player->currentTime() : 0;
   1321     if (movieTime != m_lastTimeUpdateEventMovieTime) {
   1322         scheduleEvent(eventNames().timeupdateEvent);
   1323         m_lastTimeUpdateEventWallTime = now;
   1324         m_lastTimeUpdateEventMovieTime = movieTime;
   1325     }
   1326 }
   1327 
   1328 bool HTMLMediaElement::canPlay() const
   1329 {
   1330     return paused() || ended() || m_readyState < HAVE_METADATA;
   1331 }
   1332 
   1333 float HTMLMediaElement::percentLoaded() const
   1334 {
   1335     if (!m_player)
   1336         return 0;
   1337     float duration = m_player->duration();
   1338 
   1339     if (!duration || isinf(duration))
   1340         return 0;
   1341 
   1342     float buffered = 0;
   1343     RefPtr<TimeRanges> timeRanges = m_player->buffered();
   1344     for (unsigned i = 0; i < timeRanges->length(); ++i) {
   1345         ExceptionCode ignoredException;
   1346         float start = timeRanges->start(i, ignoredException);
   1347         float end = timeRanges->end(i, ignoredException);
   1348         buffered += end - start;
   1349     }
   1350     return buffered / duration;
   1351 }
   1352 
   1353 bool HTMLMediaElement::havePotentialSourceChild()
   1354 {
   1355     // Stash the current <source> node so we can restore it after checking
   1356     // to see there is another potential
   1357     HTMLSourceElement* currentSourceNode = m_currentSourceNode;
   1358     KURL nextURL = selectNextSourceChild(0, DoNothing);
   1359     m_currentSourceNode = currentSourceNode;
   1360 
   1361     return nextURL.isValid();
   1362 }
   1363 
   1364 KURL HTMLMediaElement::selectNextSourceChild(ContentType *contentType, InvalidSourceAction actionIfInvalid)
   1365 {
   1366     KURL mediaURL;
   1367     Node* node;
   1368     bool lookingForPreviousNode = m_currentSourceNode;
   1369     bool canUse = false;
   1370 
   1371     for (node = firstChild(); !canUse && node; node = node->nextSibling()) {
   1372         if (!node->hasTagName(sourceTag))
   1373             continue;
   1374 
   1375         if (lookingForPreviousNode) {
   1376             if (m_currentSourceNode == static_cast<HTMLSourceElement*>(node))
   1377                 lookingForPreviousNode = false;
   1378             continue;
   1379         }
   1380 
   1381         HTMLSourceElement* source = static_cast<HTMLSourceElement*>(node);
   1382         if (!source->hasAttribute(srcAttr))
   1383             goto check_again;
   1384 
   1385         if (source->hasAttribute(mediaAttr)) {
   1386             MediaQueryEvaluator screenEval("screen", document()->frame(), renderer() ? renderer()->style() : 0);
   1387             RefPtr<MediaList> media = MediaList::createAllowingDescriptionSyntax(source->media());
   1388             if (!screenEval.eval(media.get()))
   1389                 goto check_again;
   1390         }
   1391 
   1392         if (source->hasAttribute(typeAttr)) {
   1393             if (!MediaPlayer::supportsType(ContentType(source->type())))
   1394                 goto check_again;
   1395         }
   1396 
   1397         // Is it safe to load this url?
   1398         mediaURL = source->src();
   1399         if (!mediaURL.isValid() || !isSafeToLoadURL(mediaURL, actionIfInvalid) || !dispatchBeforeLoadEvent(mediaURL.string()))
   1400             goto check_again;
   1401 
   1402         // Making it this far means the <source> looks reasonable
   1403         canUse = true;
   1404         if (contentType)
   1405             *contentType = ContentType(source->type());
   1406 
   1407 check_again:
   1408         if (!canUse && actionIfInvalid == Complain)
   1409             source->scheduleErrorEvent();
   1410         m_currentSourceNode = static_cast<HTMLSourceElement*>(node);
   1411     }
   1412 
   1413     if (!canUse)
   1414         m_currentSourceNode = 0;
   1415     return canUse ? mediaURL : KURL();
   1416 }
   1417 
   1418 void HTMLMediaElement::mediaPlayerTimeChanged(MediaPlayer*)
   1419 {
   1420     beginProcessingMediaPlayerCallback();
   1421 
   1422     // Always call scheduleTimeupdateEvent when the media engine reports a time discontinuity,
   1423     // it will only queue a 'timeupdate' event if we haven't already posted one at the current
   1424     // movie time.
   1425     scheduleTimeupdateEvent(false);
   1426 
   1427     // 4.8.10.10 step 12 & 13.  Needed if no ReadyState change is associated with the seek.
   1428     if (m_readyState >= HAVE_CURRENT_DATA && m_seeking)
   1429         finishSeek();
   1430 
   1431     float now = currentTime();
   1432     float dur = duration();
   1433     if (!isnan(dur) && dur && now >= dur) {
   1434         if (loop()) {
   1435             ExceptionCode ignoredException;
   1436             m_sentEndEvent = false;
   1437             seek(0, ignoredException);
   1438         } else {
   1439             if (!m_sentEndEvent) {
   1440                 m_sentEndEvent = true;
   1441                 scheduleEvent(eventNames().endedEvent);
   1442             }
   1443         }
   1444     }
   1445     else
   1446         m_sentEndEvent = false;
   1447 
   1448     updatePlayState();
   1449     endProcessingMediaPlayerCallback();
   1450 }
   1451 
   1452 void HTMLMediaElement::mediaPlayerVolumeChanged(MediaPlayer*)
   1453 {
   1454     beginProcessingMediaPlayerCallback();
   1455     if (m_player)
   1456         m_volume = m_player->volume();
   1457     updateVolume();
   1458     endProcessingMediaPlayerCallback();
   1459 }
   1460 
   1461 void HTMLMediaElement::mediaPlayerMuteChanged(MediaPlayer*)
   1462 {
   1463     beginProcessingMediaPlayerCallback();
   1464     if (m_player)
   1465         setMuted(m_player->muted());
   1466     endProcessingMediaPlayerCallback();
   1467 }
   1468 
   1469 void HTMLMediaElement::mediaPlayerDurationChanged(MediaPlayer*)
   1470 {
   1471     beginProcessingMediaPlayerCallback();
   1472     scheduleEvent(eventNames().durationchangeEvent);
   1473 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO)
   1474     if (renderer()) {
   1475         renderer()->updateFromElement();
   1476         if (renderer()->isVideo())
   1477             toRenderVideo(renderer())->videoSizeChanged();
   1478     }
   1479 #endif
   1480     endProcessingMediaPlayerCallback();
   1481 }
   1482 
   1483 void HTMLMediaElement::mediaPlayerRateChanged(MediaPlayer*)
   1484 {
   1485     beginProcessingMediaPlayerCallback();
   1486     // Stash the rate in case the one we tried to set isn't what the engine is
   1487     // using (eg. it can't handle the rate we set)
   1488     m_playbackRate = m_player->rate();
   1489     endProcessingMediaPlayerCallback();
   1490 }
   1491 
   1492 void HTMLMediaElement::mediaPlayerSawUnsupportedTracks(MediaPlayer*)
   1493 {
   1494     // The MediaPlayer came across content it cannot completely handle.
   1495     // This is normally acceptable except when we are in a standalone
   1496     // MediaDocument. If so, tell the document what has happened.
   1497     if (ownerDocument()->isMediaDocument()) {
   1498         MediaDocument* mediaDocument = static_cast<MediaDocument*>(ownerDocument());
   1499         mediaDocument->mediaElementSawUnsupportedTracks();
   1500     }
   1501 }
   1502 
   1503 // MediaPlayerPresentation methods
   1504 void HTMLMediaElement::mediaPlayerRepaint(MediaPlayer*)
   1505 {
   1506     beginProcessingMediaPlayerCallback();
   1507     if (renderer())
   1508         renderer()->repaint();
   1509     endProcessingMediaPlayerCallback();
   1510 }
   1511 
   1512 void HTMLMediaElement::mediaPlayerSizeChanged(MediaPlayer*)
   1513 {
   1514     beginProcessingMediaPlayerCallback();
   1515 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO)
   1516     if (renderer() && renderer()->isVideo())
   1517         toRenderVideo(renderer())->videoSizeChanged();
   1518 #endif
   1519     endProcessingMediaPlayerCallback();
   1520 }
   1521 
   1522 #if USE(ACCELERATED_COMPOSITING)
   1523 bool HTMLMediaElement::mediaPlayerRenderingCanBeAccelerated(MediaPlayer*)
   1524 {
   1525     if (renderer() && renderer()->isVideo()) {
   1526         ASSERT(renderer()->view());
   1527         return renderer()->view()->compositor()->canAccelerateVideoRendering(toRenderVideo(renderer()));
   1528     }
   1529     return false;
   1530 }
   1531 
   1532 GraphicsLayer* HTMLMediaElement::mediaPlayerGraphicsLayer(MediaPlayer*)
   1533 {
   1534     if (renderer() && renderer()->isVideo())
   1535         return toRenderVideo(renderer())->videoGraphicsLayer();
   1536     return 0;
   1537 }
   1538 #endif
   1539 
   1540 PassRefPtr<TimeRanges> HTMLMediaElement::buffered() const
   1541 {
   1542     if (!m_player)
   1543         return TimeRanges::create();
   1544     return m_player->buffered();
   1545 }
   1546 
   1547 PassRefPtr<TimeRanges> HTMLMediaElement::played()
   1548 {
   1549     if (m_playing) {
   1550         float time = currentTime();
   1551         if (time > m_lastSeekTime)
   1552             addPlayedRange(m_lastSeekTime, time);
   1553     }
   1554 
   1555     if (!m_playedTimeRanges)
   1556         m_playedTimeRanges = TimeRanges::create();
   1557 
   1558     return m_playedTimeRanges->copy();
   1559 }
   1560 
   1561 PassRefPtr<TimeRanges> HTMLMediaElement::seekable() const
   1562 {
   1563     // FIXME real ranges support
   1564     if (!maxTimeSeekable())
   1565         return TimeRanges::create();
   1566     return TimeRanges::create(minTimeSeekable(), maxTimeSeekable());
   1567 }
   1568 
   1569 bool HTMLMediaElement::potentiallyPlaying() const
   1570 {
   1571     return m_readyState >= HAVE_FUTURE_DATA && couldPlayIfEnoughData();
   1572 }
   1573 
   1574 bool HTMLMediaElement::couldPlayIfEnoughData() const
   1575 {
   1576     return !paused() && !endedPlayback() && !stoppedDueToErrors() && !pausedForUserInteraction();
   1577 }
   1578 
   1579 bool HTMLMediaElement::endedPlayback() const
   1580 {
   1581     float dur = duration();
   1582     if (!m_player || isnan(dur))
   1583         return false;
   1584 
   1585     // 4.8.10.8 Playing the media resource
   1586 
   1587     // A media element is said to have ended playback when the element's
   1588     // readyState attribute is HAVE_METADATA or greater,
   1589     if (m_readyState < HAVE_METADATA)
   1590         return false;
   1591 
   1592     // and the current playback position is the end of the media resource and the direction
   1593     // of playback is forwards and the media element does not have a loop attribute specified,
   1594     float now = currentTime();
   1595     if (m_playbackRate > 0)
   1596         return now >= dur && !loop();
   1597 
   1598     // or the current playback position is the earliest possible position and the direction
   1599     // of playback is backwards
   1600     if (m_playbackRate < 0)
   1601         return now <= 0;
   1602 
   1603     return false;
   1604 }
   1605 
   1606 bool HTMLMediaElement::stoppedDueToErrors() const
   1607 {
   1608     if (m_readyState >= HAVE_METADATA && m_error) {
   1609         RefPtr<TimeRanges> seekableRanges = seekable();
   1610         if (!seekableRanges->contain(currentTime()))
   1611             return true;
   1612     }
   1613 
   1614     return false;
   1615 }
   1616 
   1617 bool HTMLMediaElement::pausedForUserInteraction() const
   1618 {
   1619 //    return !paused() && m_readyState >= HAVE_FUTURE_DATA && [UA requires a decitions from the user]
   1620     return false;
   1621 }
   1622 
   1623 float HTMLMediaElement::minTimeSeekable() const
   1624 {
   1625     return 0;
   1626 }
   1627 
   1628 float HTMLMediaElement::maxTimeSeekable() const
   1629 {
   1630     return m_player ? m_player->maxTimeSeekable() : 0;
   1631 }
   1632 
   1633 void HTMLMediaElement::updateVolume()
   1634 {
   1635     if (!m_player)
   1636         return;
   1637 
   1638     // Avoid recursion when the player reports volume changes.
   1639     if (!processingMediaPlayerCallback()) {
   1640         Page* page = document()->page();
   1641         float volumeMultiplier = page ? page->mediaVolume() : 1;
   1642 
   1643         m_player->setVolume(m_muted ? 0 : m_volume * volumeMultiplier);
   1644     }
   1645 
   1646     if (renderer())
   1647         renderer()->updateFromElement();
   1648 }
   1649 
   1650 void HTMLMediaElement::updatePlayState()
   1651 {
   1652     if (!m_player)
   1653         return;
   1654 
   1655     if (m_pausedInternal) {
   1656         if (!m_player->paused())
   1657             m_player->pause();
   1658         m_playbackProgressTimer.stop();
   1659         return;
   1660     }
   1661 
   1662     bool shouldBePlaying = potentiallyPlaying();
   1663     bool playerPaused = m_player->paused();
   1664     if (shouldBePlaying && playerPaused) {
   1665         // Set rate before calling play in case the rate was set before the media engine wasn't setup.
   1666         // The media engine should just stash the rate since it isn't already playing.
   1667         m_player->setRate(m_playbackRate);
   1668         m_player->play();
   1669         startPlaybackProgressTimer();
   1670         m_playing = true;
   1671     } else if (!shouldBePlaying && !playerPaused) {
   1672         m_player->pause();
   1673         m_playbackProgressTimer.stop();
   1674         m_playing = false;
   1675         float time = currentTime();
   1676         if (time > m_lastSeekTime)
   1677             addPlayedRange(m_lastSeekTime, time);
   1678     } else if (couldPlayIfEnoughData() && playerPaused)
   1679         m_player->prepareToPlay();
   1680 
   1681     if (renderer())
   1682         renderer()->updateFromElement();
   1683 }
   1684 
   1685 void HTMLMediaElement::setPausedInternal(bool b)
   1686 {
   1687     m_pausedInternal = b;
   1688     updatePlayState();
   1689 }
   1690 
   1691 void HTMLMediaElement::stopPeriodicTimers()
   1692 {
   1693     m_progressEventTimer.stop();
   1694     m_playbackProgressTimer.stop();
   1695 }
   1696 
   1697 void HTMLMediaElement::userCancelledLoad()
   1698 {
   1699     if (m_networkState == NETWORK_EMPTY || m_networkState >= NETWORK_LOADED)
   1700         return;
   1701 
   1702     // If the media data fetching process is aborted by the user:
   1703 
   1704     // 1 - The user agent should cancel the fetching process.
   1705 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO)
   1706     m_player.clear();
   1707 #endif
   1708     stopPeriodicTimers();
   1709 
   1710     // 2 - Set the error attribute to a new MediaError object whose code attribute is set to MEDIA_ERR_ABORTED.
   1711     m_error = MediaError::create(MediaError::MEDIA_ERR_ABORTED);
   1712 
   1713     // 3 - Queue a task to fire a progress event called abort at the media element, in the context
   1714     // of the fetching process started by this instance of this algorithm.
   1715     scheduleEvent(eventNames().abortEvent);
   1716 
   1717     // 5 - If the media element's readyState attribute has a value equal to HAVE_NOTHING, set the
   1718     // element's networkState attribute to the NETWORK_EMPTY value and queue a task to fire a
   1719     // simple event called emptied at the element. Otherwise, set set the element's networkState
   1720     // attribute to the NETWORK_IDLE value.
   1721     if (m_readyState == HAVE_NOTHING) {
   1722         m_networkState = NETWORK_EMPTY;
   1723         scheduleEvent(eventNames().emptiedEvent);
   1724     }
   1725     else
   1726         m_networkState = NETWORK_IDLE;
   1727 
   1728     // 6 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
   1729     m_delayingTheLoadEvent = false;
   1730 
   1731     // 7 - Abort the overall resource selection algorithm.
   1732     m_currentSourceNode = 0;
   1733 }
   1734 
   1735 void HTMLMediaElement::documentWillBecomeInactive()
   1736 {
   1737     if (m_isFullscreen)
   1738         exitFullscreen();
   1739 
   1740     m_inActiveDocument = false;
   1741     userCancelledLoad();
   1742 
   1743     // Stop the playback without generating events
   1744     setPausedInternal(true);
   1745 
   1746     if (renderer())
   1747         renderer()->updateFromElement();
   1748 
   1749     stopPeriodicTimers();
   1750     cancelPendingEventsAndCallbacks();
   1751 }
   1752 
   1753 void HTMLMediaElement::documentDidBecomeActive()
   1754 {
   1755     m_inActiveDocument = true;
   1756     setPausedInternal(false);
   1757 
   1758     if (m_error && m_error->code() == MediaError::MEDIA_ERR_ABORTED) {
   1759         // Restart the load if it was aborted in the middle by moving the document to the page cache.
   1760         // m_error is only left at MEDIA_ERR_ABORTED when the document becomes inactive (it is set to
   1761         //  MEDIA_ERR_ABORTED while the abortEvent is being sent, but cleared immediately afterwards).
   1762         // This behavior is not specified but it seems like a sensible thing to do.
   1763         ExceptionCode ec;
   1764         load(processingUserGesture(), ec);
   1765     }
   1766 
   1767     if (renderer())
   1768         renderer()->updateFromElement();
   1769 }
   1770 
   1771 void HTMLMediaElement::mediaVolumeDidChange()
   1772 {
   1773     updateVolume();
   1774 }
   1775 
   1776 const IntRect HTMLMediaElement::screenRect()
   1777 {
   1778     IntRect elementRect;
   1779     if (renderer())
   1780         elementRect = renderer()->view()->frameView()->contentsToScreen(renderer()->absoluteBoundingBoxRect());
   1781     return elementRect;
   1782 }
   1783 
   1784 void HTMLMediaElement::defaultEventHandler(Event* event)
   1785 {
   1786 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
   1787     RenderObject* r = renderer();
   1788     if (!r || !r->isWidget())
   1789         return;
   1790 
   1791     Widget* widget = toRenderWidget(r)->widget();
   1792     if (widget)
   1793         widget->handleEvent(event);
   1794 #else
   1795     if (renderer() && renderer()->isMedia())
   1796         toRenderMedia(renderer())->forwardEvent(event);
   1797     if (event->defaultHandled())
   1798         return;
   1799     HTMLElement::defaultEventHandler(event);
   1800 #endif
   1801 }
   1802 
   1803 bool HTMLMediaElement::processingUserGesture() const
   1804 {
   1805     Frame* frame = document()->frame();
   1806     FrameLoader* loader = frame ? frame->loader() : 0;
   1807 
   1808     // return 'true' for safety if we don't know the answer
   1809     return loader ? loader->isProcessingUserGesture() : true;
   1810 }
   1811 
   1812 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
   1813 
   1814 void HTMLMediaElement::deliverNotification(MediaPlayerProxyNotificationType notification)
   1815 {
   1816     if (notification == MediaPlayerNotificationPlayPauseButtonPressed) {
   1817         togglePlayState();
   1818         return;
   1819     }
   1820 
   1821     if (m_player)
   1822         m_player->deliverNotification(notification);
   1823 }
   1824 
   1825 void HTMLMediaElement::setMediaPlayerProxy(WebMediaPlayerProxy* proxy)
   1826 {
   1827     if (m_player)
   1828         m_player->setMediaPlayerProxy(proxy);
   1829 }
   1830 
   1831 String HTMLMediaElement::initialURL()
   1832 {
   1833     KURL initialSrc = document()->completeURL(getAttribute(srcAttr));
   1834 
   1835     if (!initialSrc.isValid())
   1836         initialSrc = selectNextSourceChild(0, DoNothing);
   1837 
   1838     m_currentSrc = initialSrc.string();
   1839 
   1840     return initialSrc;
   1841 }
   1842 
   1843 void HTMLMediaElement::finishParsingChildren()
   1844 {
   1845     HTMLElement::finishParsingChildren();
   1846     if (!m_player)
   1847         m_player = MediaPlayer::create(this);
   1848 
   1849     document()->updateStyleIfNeeded();
   1850     if (m_needWidgetUpdate && renderer())
   1851         toRenderEmbeddedObject(renderer())->updateWidget(true);
   1852 }
   1853 
   1854 #endif
   1855 
   1856 void HTMLMediaElement::enterFullscreen()
   1857 {
   1858     ASSERT(!m_isFullscreen);
   1859     if (document() && document()->page()) {
   1860         document()->page()->chrome()->client()->enterFullscreenForNode(this);
   1861         scheduleEvent(eventNames().webkitbeginfullscreenEvent);
   1862         m_isFullscreen = true;
   1863     }
   1864 }
   1865 
   1866 void HTMLMediaElement::exitFullscreen()
   1867 {
   1868     ASSERT(m_isFullscreen);
   1869     if (document() && document()->page()) {
   1870         document()->page()->chrome()->client()->exitFullscreenForNode(this);
   1871         scheduleEvent(eventNames().webkitendfullscreenEvent);
   1872     }
   1873     m_isFullscreen = false;
   1874 }
   1875 
   1876 PlatformMedia HTMLMediaElement::platformMedia() const
   1877 {
   1878     return m_player ? m_player->platformMedia() : NoPlatformMedia;
   1879 }
   1880 
   1881 bool HTMLMediaElement::hasClosedCaptions() const
   1882 {
   1883     return m_player && m_player->hasClosedCaptions();
   1884 }
   1885 
   1886 bool HTMLMediaElement::closedCaptionsVisible() const
   1887 {
   1888     return m_closedCaptionsVisible;
   1889 }
   1890 
   1891 void HTMLMediaElement::setClosedCaptionsVisible(bool closedCaptionVisible)
   1892 {
   1893     if (!m_player ||!hasClosedCaptions())
   1894         return;
   1895 
   1896     m_closedCaptionsVisible = closedCaptionVisible;
   1897     m_player->setClosedCaptionsVisible(closedCaptionVisible);
   1898     if (renderer())
   1899         renderer()->updateFromElement();
   1900 }
   1901 
   1902 void HTMLMediaElement::setWebkitClosedCaptionsVisible(bool visible)
   1903 {
   1904     setClosedCaptionsVisible(visible);
   1905 }
   1906 
   1907 bool HTMLMediaElement::webkitClosedCaptionsVisible() const
   1908 {
   1909     return closedCaptionsVisible();
   1910 }
   1911 
   1912 
   1913 bool HTMLMediaElement::webkitHasClosedCaptions() const
   1914 {
   1915     return hasClosedCaptions();
   1916 }
   1917 
   1918 }
   1919 
   1920 #endif
   1921