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/ExceptionCode.h" 37 #include "core/events/Event.h" 38 #include "core/events/GenericEventQueue.h" 39 #include "core/html/TimeRanges.h" 40 #include "modules/mediasource/MediaSourceRegistry.h" 41 #include "platform/Logging.h" 42 #include "platform/TraceEvent.h" 43 #include "public/platform/WebMediaSource.h" 44 #include "public/platform/WebSourceBuffer.h" 45 #include "wtf/text/WTFString.h" 46 47 using blink::WebMediaSource; 48 using blink::WebSourceBuffer; 49 50 namespace WebCore { 51 52 MediaSourceBase::MediaSourceBase(ExecutionContext* context) 53 : ActiveDOMObject(context) 54 , m_readyState(closedKeyword()) 55 , m_asyncEventQueue(GenericEventQueue::create(this)) 56 , m_attachedElement(0) 57 { 58 } 59 60 MediaSourceBase::~MediaSourceBase() 61 { 62 } 63 64 const AtomicString& MediaSourceBase::openKeyword() 65 { 66 DEFINE_STATIC_LOCAL(const AtomicString, open, ("open", AtomicString::ConstructFromLiteral)); 67 return open; 68 } 69 70 const AtomicString& MediaSourceBase::closedKeyword() 71 { 72 DEFINE_STATIC_LOCAL(const AtomicString, closed, ("closed", AtomicString::ConstructFromLiteral)); 73 return closed; 74 } 75 76 const AtomicString& MediaSourceBase::endedKeyword() 77 { 78 DEFINE_STATIC_LOCAL(const AtomicString, ended, ("ended", AtomicString::ConstructFromLiteral)); 79 return ended; 80 } 81 82 void MediaSourceBase::setWebMediaSourceAndOpen(PassOwnPtr<WebMediaSource> webMediaSource) 83 { 84 TRACE_EVENT_ASYNC_END0("media", "MediaSourceBase::attachToElement", this); 85 ASSERT(webMediaSource); 86 ASSERT(!m_webMediaSource); 87 ASSERT(m_attachedElement); 88 m_webMediaSource = webMediaSource; 89 setReadyState(openKeyword()); 90 } 91 92 void MediaSourceBase::addedToRegistry() 93 { 94 setPendingActivity(this); 95 } 96 97 void MediaSourceBase::removedFromRegistry() 98 { 99 unsetPendingActivity(this); 100 } 101 102 double MediaSourceBase::duration() const 103 { 104 return isClosed() ? std::numeric_limits<float>::quiet_NaN() : m_webMediaSource->duration(); 105 } 106 107 PassRefPtr<TimeRanges> MediaSourceBase::buffered() const 108 { 109 // Implements MediaSource algorithm for HTMLMediaElement.buffered. 110 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#htmlmediaelement-extensions 111 Vector<RefPtr<TimeRanges> > ranges = activeRanges(); 112 113 // 1. If activeSourceBuffers.length equals 0 then return an empty TimeRanges object and abort these steps. 114 if (ranges.isEmpty()) 115 return TimeRanges::create(); 116 117 // 2. Let active ranges be the ranges returned by buffered for each SourceBuffer object in activeSourceBuffers. 118 // 3. Let highest end time be the largest range end time in the active ranges. 119 double highestEndTime = -1; 120 for (size_t i = 0; i < ranges.size(); ++i) { 121 unsigned length = ranges[i]->length(); 122 if (length) 123 highestEndTime = std::max(highestEndTime, ranges[i]->end(length - 1, ASSERT_NO_EXCEPTION)); 124 } 125 126 // Return an empty range if all ranges are empty. 127 if (highestEndTime < 0) 128 return TimeRanges::create(); 129 130 // 4. Let intersection ranges equal a TimeRange object containing a single range from 0 to highest end time. 131 RefPtr<TimeRanges> intersectionRanges = TimeRanges::create(0, highestEndTime); 132 133 // 5. For each SourceBuffer object in activeSourceBuffers run the following steps: 134 bool ended = readyState() == endedKeyword(); 135 for (size_t i = 0; i < ranges.size(); ++i) { 136 // 5.1 Let source ranges equal the ranges returned by the buffered attribute on the current SourceBuffer. 137 TimeRanges* sourceRanges = ranges[i].get(); 138 139 // 5.2 If readyState is "ended", then set the end time on the last range in source ranges to highest end time. 140 if (ended && sourceRanges->length()) 141 sourceRanges->add(sourceRanges->start(sourceRanges->length() - 1, ASSERT_NO_EXCEPTION), highestEndTime); 142 143 // 5.3 Let new intersection ranges equal the the intersection between the intersection ranges and the source ranges. 144 // 5.4 Replace the ranges in intersection ranges with the new intersection ranges. 145 intersectionRanges->intersectWith(sourceRanges); 146 } 147 148 return intersectionRanges.release(); 149 } 150 151 void MediaSourceBase::setDuration(double duration, ExceptionState& exceptionState) 152 { 153 if (duration < 0.0 || std::isnan(duration)) { 154 exceptionState.throwUninformativeAndGenericDOMException(InvalidAccessError); 155 return; 156 } 157 if (!isOpen()) { 158 exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError); 159 return; 160 } 161 162 // Synchronously process duration change algorithm to enforce any required 163 // seek is started prior to returning. 164 m_attachedElement->durationChanged(duration); 165 m_webMediaSource->setDuration(duration); 166 } 167 168 169 void MediaSourceBase::setReadyState(const AtomicString& state) 170 { 171 ASSERT(state == openKeyword() || state == closedKeyword() || state == endedKeyword()); 172 173 AtomicString oldState = readyState(); 174 WTF_LOG(Media, "MediaSourceBase::setReadyState() %p : %s -> %s", this, oldState.string().ascii().data(), state.string().ascii().data()); 175 176 if (state == closedKeyword()) { 177 m_webMediaSource.clear(); 178 m_attachedElement = 0; 179 } 180 181 if (oldState == state) 182 return; 183 184 m_readyState = state; 185 186 onReadyStateChange(oldState, state); 187 } 188 189 void MediaSourceBase::endOfStream(const AtomicString& error, ExceptionState& exceptionState) 190 { 191 DEFINE_STATIC_LOCAL(const AtomicString, network, ("network", AtomicString::ConstructFromLiteral)); 192 DEFINE_STATIC_LOCAL(const AtomicString, decode, ("decode", AtomicString::ConstructFromLiteral)); 193 194 // 3.1 http://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#dom-endofstream 195 // 1. If the readyState attribute is not in the "open" state then throw an 196 // InvalidStateError exception and abort these steps. 197 if (!isOpen()) { 198 exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError); 199 return; 200 } 201 202 WebMediaSource::EndOfStreamStatus eosStatus = WebMediaSource::EndOfStreamStatusNoError; 203 204 if (error.isNull() || error.isEmpty()) { 205 eosStatus = WebMediaSource::EndOfStreamStatusNoError; 206 } else if (error == network) { 207 eosStatus = WebMediaSource::EndOfStreamStatusNetworkError; 208 } else if (error == decode) { 209 eosStatus = WebMediaSource::EndOfStreamStatusDecodeError; 210 } else { 211 exceptionState.throwUninformativeAndGenericDOMException(InvalidAccessError); 212 return; 213 } 214 215 // 2. Change the readyState attribute value to "ended". 216 setReadyState(endedKeyword()); 217 m_webMediaSource->markEndOfStream(eosStatus); 218 } 219 220 bool MediaSourceBase::isOpen() const 221 { 222 return readyState() == openKeyword(); 223 } 224 225 bool MediaSourceBase::isClosed() const 226 { 227 return readyState() == closedKeyword(); 228 } 229 230 void MediaSourceBase::close() 231 { 232 setReadyState(closedKeyword()); 233 } 234 235 bool MediaSourceBase::attachToElement(HTMLMediaElement* element) 236 { 237 if (m_attachedElement) 238 return false; 239 240 ASSERT(isClosed()); 241 242 TRACE_EVENT_ASYNC_BEGIN0("media", "MediaSourceBase::attachToElement", this); 243 m_attachedElement = element; 244 return true; 245 } 246 247 void MediaSourceBase::openIfInEndedState() 248 { 249 if (m_readyState != endedKeyword()) 250 return; 251 252 setReadyState(openKeyword()); 253 m_webMediaSource->unmarkEndOfStream(); 254 } 255 256 bool MediaSourceBase::hasPendingActivity() const 257 { 258 return m_attachedElement || m_webMediaSource 259 || m_asyncEventQueue->hasPendingEvents() 260 || ActiveDOMObject::hasPendingActivity(); 261 } 262 263 void MediaSourceBase::stop() 264 { 265 m_asyncEventQueue->close(); 266 if (!isClosed()) 267 setReadyState(closedKeyword()); 268 m_webMediaSource.clear(); 269 } 270 271 PassOwnPtr<WebSourceBuffer> MediaSourceBase::createWebSourceBuffer(const String& type, const Vector<String>& codecs, ExceptionState& exceptionState) 272 { 273 WebSourceBuffer* webSourceBuffer = 0; 274 switch (m_webMediaSource->addSourceBuffer(type, codecs, &webSourceBuffer)) { 275 case WebMediaSource::AddStatusOk: 276 return adoptPtr(webSourceBuffer); 277 case WebMediaSource::AddStatusNotSupported: 278 ASSERT(!webSourceBuffer); 279 // 2.2 https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-MediaSource-addSourceBuffer-SourceBuffer-DOMString-type 280 // Step 2: If type contains a MIME type ... that is not supported with the types 281 // specified for the other SourceBuffer objects in sourceBuffers, then throw 282 // a NotSupportedError exception and abort these steps. 283 exceptionState.throwUninformativeAndGenericDOMException(NotSupportedError); 284 return nullptr; 285 case WebMediaSource::AddStatusReachedIdLimit: 286 ASSERT(!webSourceBuffer); 287 // 2.2 https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-MediaSource-addSourceBuffer-SourceBuffer-DOMString-type 288 // Step 3: If the user agent can't handle any more SourceBuffer objects then throw 289 // a QuotaExceededError exception and abort these steps. 290 exceptionState.throwUninformativeAndGenericDOMException(QuotaExceededError); 291 return nullptr; 292 } 293 294 ASSERT_NOT_REACHED(); 295 return nullptr; 296 } 297 298 void MediaSourceBase::scheduleEvent(const AtomicString& eventName) 299 { 300 ASSERT(m_asyncEventQueue); 301 302 RefPtr<Event> event = Event::create(eventName); 303 event->setTarget(this); 304 305 m_asyncEventQueue->enqueueEvent(event.release()); 306 } 307 308 ExecutionContext* MediaSourceBase::executionContext() const 309 { 310 return ActiveDOMObject::executionContext(); 311 } 312 313 URLRegistry& MediaSourceBase::registry() const 314 { 315 return MediaSourceRegistry::registry(); 316 } 317 318 } 319