Home | History | Annotate | Download | only in mediasource
      1 /*
      2  * Copyright (C) 2013 Google 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 are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 #include "config.h"
     32 #include "modules/mediasource/MediaSourceBase.h"
     33 
     34 #include "bindings/v8/ExceptionState.h"
     35 #include "bindings/v8/ExceptionStatePlaceholder.h"
     36 #include "core/dom/Event.h"
     37 #include "core/dom/ExceptionCode.h"
     38 #include "core/dom/GenericEventQueue.h"
     39 #include "core/platform/Logging.h"
     40 #include "core/platform/graphics/SourceBufferPrivate.h"
     41 #include "modules/mediasource/MediaSourceRegistry.h"
     42 #include "wtf/text/WTFString.h"
     43 
     44 namespace WebCore {
     45 
     46 MediaSourceBase::MediaSourceBase(ScriptExecutionContext* context)
     47     : ActiveDOMObject(context)
     48     , m_readyState(closedKeyword())
     49     , m_asyncEventQueue(GenericEventQueue::create(this))
     50     , m_attachedElement(0)
     51 {
     52 }
     53 
     54 MediaSourceBase::~MediaSourceBase()
     55 {
     56 }
     57 
     58 const AtomicString& MediaSourceBase::openKeyword()
     59 {
     60     DEFINE_STATIC_LOCAL(const AtomicString, open, ("open", AtomicString::ConstructFromLiteral));
     61     return open;
     62 }
     63 
     64 const AtomicString& MediaSourceBase::closedKeyword()
     65 {
     66     DEFINE_STATIC_LOCAL(const AtomicString, closed, ("closed", AtomicString::ConstructFromLiteral));
     67     return closed;
     68 }
     69 
     70 const AtomicString& MediaSourceBase::endedKeyword()
     71 {
     72     DEFINE_STATIC_LOCAL(const AtomicString, ended, ("ended", AtomicString::ConstructFromLiteral));
     73     return ended;
     74 }
     75 
     76 void MediaSourceBase::setPrivateAndOpen(PassOwnPtr<MediaSourcePrivate> mediaSourcePrivate)
     77 {
     78     ASSERT(mediaSourcePrivate);
     79     ASSERT(!m_private);
     80     ASSERT(m_attachedElement);
     81     m_private = mediaSourcePrivate;
     82     setReadyState(openKeyword());
     83 }
     84 
     85 void MediaSourceBase::addedToRegistry()
     86 {
     87     setPendingActivity(this);
     88 }
     89 
     90 void MediaSourceBase::removedFromRegistry()
     91 {
     92     unsetPendingActivity(this);
     93 }
     94 
     95 double MediaSourceBase::duration() const
     96 {
     97     return isClosed() ? std::numeric_limits<float>::quiet_NaN() : m_private->duration();
     98 }
     99 
    100 PassRefPtr<TimeRanges> MediaSourceBase::buffered() const
    101 {
    102     // Implements MediaSource algorithm for HTMLMediaElement.buffered.
    103     // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#htmlmediaelement-extensions
    104     Vector<RefPtr<TimeRanges> > ranges = activeRanges();
    105 
    106     // 1. If activeSourceBuffers.length equals 0 then return an empty TimeRanges object and abort these steps.
    107     if (ranges.isEmpty())
    108         return TimeRanges::create();
    109 
    110     // 2. Let active ranges be the ranges returned by buffered for each SourceBuffer object in activeSourceBuffers.
    111     // 3. Let highest end time be the largest range end time in the active ranges.
    112     double highestEndTime = -1;
    113     for (size_t i = 0; i < ranges.size(); ++i) {
    114         unsigned length = ranges[i]->length();
    115         if (length)
    116             highestEndTime = std::max(highestEndTime, ranges[i]->end(length - 1, ASSERT_NO_EXCEPTION));
    117     }
    118 
    119     // Return an empty range if all ranges are empty.
    120     if (highestEndTime < 0)
    121         return TimeRanges::create();
    122 
    123     // 4. Let intersection ranges equal a TimeRange object containing a single range from 0 to highest end time.
    124     RefPtr<TimeRanges> intersectionRanges = TimeRanges::create(0, highestEndTime);
    125 
    126     // 5. For each SourceBuffer object in activeSourceBuffers run the following steps:
    127     bool ended = readyState() == endedKeyword();
    128     for (size_t i = 0; i < ranges.size(); ++i) {
    129         // 5.1 Let source ranges equal the ranges returned by the buffered attribute on the current SourceBuffer.
    130         TimeRanges* sourceRanges = ranges[i].get();
    131 
    132         // 5.2 If readyState is "ended", then set the end time on the last range in source ranges to highest end time.
    133         if (ended && sourceRanges->length())
    134             sourceRanges->add(sourceRanges->start(sourceRanges->length() - 1, ASSERT_NO_EXCEPTION), highestEndTime);
    135 
    136         // 5.3 Let new intersection ranges equal the the intersection between the intersection ranges and the source ranges.
    137         // 5.4 Replace the ranges in intersection ranges with the new intersection ranges.
    138         intersectionRanges->intersectWith(sourceRanges);
    139     }
    140 
    141     return intersectionRanges.release();
    142 }
    143 
    144 void MediaSourceBase::setDuration(double duration, ExceptionState& es)
    145 {
    146     if (duration < 0.0 || std::isnan(duration)) {
    147         es.throwDOMException(InvalidAccessError);
    148         return;
    149     }
    150     if (!isOpen()) {
    151         es.throwDOMException(InvalidStateError);
    152         return;
    153     }
    154 
    155     // Synchronously process duration change algorithm to enforce any required
    156     // seek is started prior to returning.
    157     m_attachedElement->durationChanged(duration);
    158     m_private->setDuration(duration);
    159 }
    160 
    161 
    162 void MediaSourceBase::setReadyState(const AtomicString& state)
    163 {
    164     ASSERT(state == openKeyword() || state == closedKeyword() || state == endedKeyword());
    165 
    166     AtomicString oldState = readyState();
    167     LOG(Media, "MediaSourceBase::setReadyState() %p : %s -> %s", this, oldState.string().ascii().data(), state.string().ascii().data());
    168 
    169     if (state == closedKeyword()) {
    170         m_private.clear();
    171         m_attachedElement = 0;
    172     }
    173 
    174     if (oldState == state)
    175         return;
    176 
    177     m_readyState = state;
    178 
    179     onReadyStateChange(oldState, state);
    180 }
    181 
    182 void MediaSourceBase::endOfStream(const AtomicString& error, ExceptionState& es)
    183 {
    184     DEFINE_STATIC_LOCAL(const AtomicString, network, ("network", AtomicString::ConstructFromLiteral));
    185     DEFINE_STATIC_LOCAL(const AtomicString, decode, ("decode", AtomicString::ConstructFromLiteral));
    186 
    187     // 3.1 http://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#dom-endofstream
    188     // 1. If the readyState attribute is not in the "open" state then throw an
    189     // InvalidStateError exception and abort these steps.
    190     if (!isOpen()) {
    191         es.throwDOMException(InvalidStateError);
    192         return;
    193     }
    194 
    195     MediaSourcePrivate::EndOfStreamStatus eosStatus = MediaSourcePrivate::EosNoError;
    196 
    197     if (error.isNull() || error.isEmpty()) {
    198         eosStatus = MediaSourcePrivate::EosNoError;
    199     } else if (error == network) {
    200         eosStatus = MediaSourcePrivate::EosNetworkError;
    201     } else if (error == decode) {
    202         eosStatus = MediaSourcePrivate::EosDecodeError;
    203     } else {
    204         es.throwDOMException(InvalidAccessError);
    205         return;
    206     }
    207 
    208     // 2. Change the readyState attribute value to "ended".
    209     setReadyState(endedKeyword());
    210     m_private->markEndOfStream(eosStatus);
    211 }
    212 
    213 bool MediaSourceBase::isOpen() const
    214 {
    215     return readyState() == openKeyword();
    216 }
    217 
    218 bool MediaSourceBase::isClosed() const
    219 {
    220     return readyState() == closedKeyword();
    221 }
    222 
    223 void MediaSourceBase::close()
    224 {
    225     setReadyState(closedKeyword());
    226 }
    227 
    228 bool MediaSourceBase::attachToElement(HTMLMediaElement* element)
    229 {
    230     if (m_attachedElement)
    231         return false;
    232 
    233     ASSERT(isClosed());
    234 
    235     m_attachedElement = element;
    236     return true;
    237 }
    238 
    239 void MediaSourceBase::openIfInEndedState()
    240 {
    241     if (m_readyState != endedKeyword())
    242         return;
    243 
    244     setReadyState(openKeyword());
    245     m_private->unmarkEndOfStream();
    246 }
    247 
    248 bool MediaSourceBase::hasPendingActivity() const
    249 {
    250     return m_private || m_asyncEventQueue->hasPendingEvents()
    251         || ActiveDOMObject::hasPendingActivity();
    252 }
    253 
    254 void MediaSourceBase::stop()
    255 {
    256     m_asyncEventQueue->close();
    257     if (!isClosed())
    258         setReadyState(closedKeyword());
    259     m_private.clear();
    260 }
    261 
    262 PassOwnPtr<SourceBufferPrivate> MediaSourceBase::createSourceBufferPrivate(const String& type, const MediaSourcePrivate::CodecsArray& codecs, ExceptionState& es)
    263 {
    264     OwnPtr<SourceBufferPrivate> sourceBufferPrivate;
    265     switch (m_private->addSourceBuffer(type, codecs, &sourceBufferPrivate)) {
    266     case MediaSourcePrivate::Ok: {
    267         return sourceBufferPrivate.release();
    268     }
    269     case MediaSourcePrivate::NotSupported:
    270         // 2.2 https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-MediaSource-addSourceBuffer-SourceBuffer-DOMString-type
    271         // Step 2: If type contains a MIME type ... that is not supported with the types
    272         // specified for the other SourceBuffer objects in sourceBuffers, then throw
    273         // a NotSupportedError exception and abort these steps.
    274         es.throwDOMException(NotSupportedError);
    275         return nullptr;
    276     case MediaSourcePrivate::ReachedIdLimit:
    277         // 2.2 https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-MediaSource-addSourceBuffer-SourceBuffer-DOMString-type
    278         // Step 3: If the user agent can't handle any more SourceBuffer objects then throw
    279         // a QuotaExceededError exception and abort these steps.
    280         es.throwDOMException(QuotaExceededError);
    281         return nullptr;
    282     }
    283 
    284     ASSERT_NOT_REACHED();
    285     return nullptr;
    286 }
    287 
    288 void MediaSourceBase::scheduleEvent(const AtomicString& eventName)
    289 {
    290     ASSERT(m_asyncEventQueue);
    291 
    292     RefPtr<Event> event = Event::create(eventName, false, false);
    293     event->setTarget(this);
    294 
    295     m_asyncEventQueue->enqueueEvent(event.release());
    296 }
    297 
    298 ScriptExecutionContext* MediaSourceBase::scriptExecutionContext() const
    299 {
    300     return ActiveDOMObject::scriptExecutionContext();
    301 }
    302 
    303 EventTargetData* MediaSourceBase::eventTargetData()
    304 {
    305     return &m_eventTargetData;
    306 }
    307 
    308 EventTargetData* MediaSourceBase::ensureEventTargetData()
    309 {
    310     return &m_eventTargetData;
    311 }
    312 
    313 URLRegistry& MediaSourceBase::registry() const
    314 {
    315     return MediaSourceRegistry::registry();
    316 }
    317 
    318 }
    319