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