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/MediaSource.h" 33 34 #include "bindings/v8/ExceptionMessages.h" 35 #include "bindings/v8/ExceptionState.h" 36 #include "bindings/v8/ExceptionStatePlaceholder.h" 37 #include "core/dom/ExceptionCode.h" 38 #include "core/events/Event.h" 39 #include "core/events/GenericEventQueue.h" 40 #include "core/html/HTMLMediaElement.h" 41 #include "core/html/TimeRanges.h" 42 #include "modules/mediasource/MediaSourceRegistry.h" 43 #include "platform/ContentType.h" 44 #include "platform/Logging.h" 45 #include "platform/MIMETypeRegistry.h" 46 #include "platform/RuntimeEnabledFeatures.h" 47 #include "platform/TraceEvent.h" 48 #include "public/platform/WebMediaSource.h" 49 #include "public/platform/WebSourceBuffer.h" 50 #include "wtf/Uint8Array.h" 51 #include "wtf/text/CString.h" 52 53 using blink::WebMediaSource; 54 using blink::WebSourceBuffer; 55 56 namespace WebCore { 57 58 static bool throwExceptionIfClosedOrUpdating(bool isOpen, bool isUpdating, ExceptionState& exceptionState) 59 { 60 if (!isOpen) { 61 exceptionState.throwDOMException(InvalidStateError, "The MediaSource's readyState is not 'open'."); 62 return true; 63 } 64 if (isUpdating) { 65 exceptionState.throwDOMException(InvalidStateError, "The 'updating' attribute is true on one or more of this MediaSource's SourceBuffers."); 66 return true; 67 } 68 69 return false; 70 } 71 72 const AtomicString& MediaSource::openKeyword() 73 { 74 DEFINE_STATIC_LOCAL(const AtomicString, open, ("open", AtomicString::ConstructFromLiteral)); 75 return open; 76 } 77 78 const AtomicString& MediaSource::closedKeyword() 79 { 80 DEFINE_STATIC_LOCAL(const AtomicString, closed, ("closed", AtomicString::ConstructFromLiteral)); 81 return closed; 82 } 83 84 const AtomicString& MediaSource::endedKeyword() 85 { 86 DEFINE_STATIC_LOCAL(const AtomicString, ended, ("ended", AtomicString::ConstructFromLiteral)); 87 return ended; 88 } 89 90 PassRefPtrWillBeRawPtr<MediaSource> MediaSource::create(ExecutionContext* context) 91 { 92 RefPtrWillBeRawPtr<MediaSource> mediaSource(adoptRefWillBeRefCountedGarbageCollected(new MediaSource(context))); 93 mediaSource->suspendIfNeeded(); 94 return mediaSource.release(); 95 } 96 97 MediaSource::MediaSource(ExecutionContext* context) 98 : ActiveDOMObject(context) 99 , m_readyState(closedKeyword()) 100 , m_asyncEventQueue(GenericEventQueue::create(this)) 101 , m_attachedElement(0) 102 , m_sourceBuffers(SourceBufferList::create(executionContext(), m_asyncEventQueue.get())) 103 , m_activeSourceBuffers(SourceBufferList::create(executionContext(), m_asyncEventQueue.get())) 104 { 105 WTF_LOG(Media, "MediaSource::MediaSource %p", this); 106 ScriptWrappable::init(this); 107 } 108 109 MediaSource::~MediaSource() 110 { 111 WTF_LOG(Media, "MediaSource::~MediaSource %p", this); 112 ASSERT(isClosed()); 113 } 114 115 SourceBuffer* MediaSource::addSourceBuffer(const String& type, ExceptionState& exceptionState) 116 { 117 WTF_LOG(Media, "MediaSource::addSourceBuffer(%s) %p", type.ascii().data(), this); 118 119 // 2.2 https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-MediaSource-addSourceBuffer-SourceBuffer-DOMString-type 120 // 1. If type is an empty string then throw an InvalidAccessError exception 121 // and abort these steps. 122 if (type.isEmpty()) { 123 exceptionState.throwDOMException(InvalidAccessError, "The type provided is empty."); 124 return 0; 125 } 126 127 // 2. If type contains a MIME type that is not supported ..., then throw a 128 // NotSupportedError exception and abort these steps. 129 if (!isTypeSupported(type)) { 130 exceptionState.throwDOMException(NotSupportedError, "The type provided ('" + type + "') is unsupported."); 131 return 0; 132 } 133 134 // 4. If the readyState attribute is not in the "open" state then throw an 135 // InvalidStateError exception and abort these steps. 136 if (!isOpen()) { 137 exceptionState.throwDOMException(InvalidStateError, "The MediaSource's readyState is not 'open'."); 138 return 0; 139 } 140 141 // 5. Create a new SourceBuffer object and associated resources. 142 ContentType contentType(type); 143 Vector<String> codecs = contentType.codecs(); 144 OwnPtr<WebSourceBuffer> webSourceBuffer = createWebSourceBuffer(contentType.type(), codecs, exceptionState); 145 146 if (!webSourceBuffer) { 147 ASSERT(exceptionState.code() == NotSupportedError || exceptionState.code() == QuotaExceededError); 148 // 2. If type contains a MIME type that is not supported ..., then throw a NotSupportedError exception and abort these steps. 149 // 3. If the user agent can't handle any more SourceBuffer objects then throw a QuotaExceededError exception and abort these steps 150 return 0; 151 } 152 153 RefPtrWillBeRawPtr<SourceBuffer> buffer = SourceBuffer::create(webSourceBuffer.release(), this, m_asyncEventQueue.get()); 154 // 6. Add the new object to sourceBuffers and fire a addsourcebuffer on that object. 155 m_sourceBuffers->add(buffer); 156 m_activeSourceBuffers->add(buffer); 157 // 7. Return the new object to the caller. 158 return buffer.get(); 159 } 160 161 void MediaSource::removeSourceBuffer(SourceBuffer* buffer, ExceptionState& exceptionState) 162 { 163 WTF_LOG(Media, "MediaSource::removeSourceBuffer() %p", this); 164 RefPtr<SourceBuffer> protect(buffer); 165 166 // 2.2 https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-MediaSource-removeSourceBuffer-void-SourceBuffer-sourceBuffer 167 168 // 1. If sourceBuffer specifies an object that is not in sourceBuffers then 169 // throw a NotFoundError exception and abort these steps. 170 if (!m_sourceBuffers->length() || !m_sourceBuffers->contains(buffer)) { 171 exceptionState.throwDOMException(NotFoundError, "The SourceBuffer provided is not contained in this MediaSource."); 172 return; 173 } 174 175 // 2. If the sourceBuffer.updating attribute equals true, then run the following steps: ... 176 buffer->abortIfUpdating(); 177 178 // Steps 3-8 are related to updating audioTracks, videoTracks, and textTracks which aren't implmented yet. 179 // FIXME(91649): support track selection 180 181 // 9. If sourceBuffer is in activeSourceBuffers, then remove sourceBuffer from activeSourceBuffers ... 182 m_activeSourceBuffers->remove(buffer); 183 184 // 10. Remove sourceBuffer from sourceBuffers and fire a removesourcebuffer event 185 // on that object. 186 m_sourceBuffers->remove(buffer); 187 188 // 11. Destroy all resources for sourceBuffer. 189 buffer->removedFromMediaSource(); 190 } 191 192 void MediaSource::onReadyStateChange(const AtomicString& oldState, const AtomicString& newState) 193 { 194 if (isOpen()) { 195 scheduleEvent(EventTypeNames::sourceopen); 196 return; 197 } 198 199 if (oldState == openKeyword() && newState == endedKeyword()) { 200 scheduleEvent(EventTypeNames::sourceended); 201 return; 202 } 203 204 ASSERT(isClosed()); 205 206 m_activeSourceBuffers->clear(); 207 208 // Clear SourceBuffer references to this object. 209 for (unsigned long i = 0; i < m_sourceBuffers->length(); ++i) 210 m_sourceBuffers->item(i)->removedFromMediaSource(); 211 m_sourceBuffers->clear(); 212 213 scheduleEvent(EventTypeNames::sourceclose); 214 } 215 216 bool MediaSource::isUpdating() const 217 { 218 // Return true if any member of |m_sourceBuffers| is updating. 219 for (unsigned long i = 0; i < m_sourceBuffers->length(); ++i) { 220 if (m_sourceBuffers->item(i)->updating()) 221 return true; 222 } 223 224 return false; 225 } 226 227 bool MediaSource::isTypeSupported(const String& type) 228 { 229 WTF_LOG(Media, "MediaSource::isTypeSupported(%s)", type.ascii().data()); 230 231 // Section 2.2 isTypeSupported() method steps. 232 // https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#widl-MediaSource-isTypeSupported-boolean-DOMString-type 233 // 1. If type is an empty string, then return false. 234 if (type.isNull() || type.isEmpty()) 235 return false; 236 237 ContentType contentType(type); 238 String codecs = contentType.parameter("codecs"); 239 240 // 2. If type does not contain a valid MIME type string, then return false. 241 if (contentType.type().isEmpty()) 242 return false; 243 244 // 3. If type contains a media type or media subtype that the MediaSource does not support, then return false. 245 // 4. If type contains at a codec that the MediaSource does not support, then return false. 246 // 5. If the MediaSource does not support the specified combination of media type, media subtype, and codecs then return false. 247 // 6. Return true. 248 return MIMETypeRegistry::isSupportedMediaSourceMIMEType(contentType.type(), codecs); 249 } 250 251 const AtomicString& MediaSource::interfaceName() const 252 { 253 return EventTargetNames::MediaSource; 254 } 255 256 ExecutionContext* MediaSource::executionContext() const 257 { 258 return ActiveDOMObject::executionContext(); 259 } 260 261 void MediaSource::trace(Visitor* visitor) 262 { 263 visitor->trace(m_asyncEventQueue); 264 visitor->trace(m_sourceBuffers); 265 visitor->trace(m_activeSourceBuffers); 266 EventTargetWithInlineData::trace(visitor); 267 } 268 269 void MediaSource::setWebMediaSourceAndOpen(PassOwnPtr<WebMediaSource> webMediaSource) 270 { 271 TRACE_EVENT_ASYNC_END0("media", "MediaSource::attachToElement", this); 272 ASSERT(webMediaSource); 273 ASSERT(!m_webMediaSource); 274 ASSERT(m_attachedElement); 275 m_webMediaSource = webMediaSource; 276 setReadyState(openKeyword()); 277 } 278 279 void MediaSource::addedToRegistry() 280 { 281 setPendingActivity(this); 282 } 283 284 void MediaSource::removedFromRegistry() 285 { 286 unsetPendingActivity(this); 287 } 288 289 double MediaSource::duration() const 290 { 291 return isClosed() ? std::numeric_limits<float>::quiet_NaN() : m_webMediaSource->duration(); 292 } 293 294 PassRefPtr<TimeRanges> MediaSource::buffered() const 295 { 296 // Implements MediaSource algorithm for HTMLMediaElement.buffered. 297 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#htmlmediaelement-extensions 298 Vector<RefPtr<TimeRanges> > ranges(m_activeSourceBuffers->length()); 299 for (size_t i = 0; i < m_activeSourceBuffers->length(); ++i) 300 ranges[i] = m_activeSourceBuffers->item(i)->buffered(ASSERT_NO_EXCEPTION); 301 302 // 1. If activeSourceBuffers.length equals 0 then return an empty TimeRanges object and abort these steps. 303 if (ranges.isEmpty()) 304 return TimeRanges::create(); 305 306 // 2. Let active ranges be the ranges returned by buffered for each SourceBuffer object in activeSourceBuffers. 307 // 3. Let highest end time be the largest range end time in the active ranges. 308 double highestEndTime = -1; 309 for (size_t i = 0; i < ranges.size(); ++i) { 310 unsigned length = ranges[i]->length(); 311 if (length) 312 highestEndTime = std::max(highestEndTime, ranges[i]->end(length - 1, ASSERT_NO_EXCEPTION)); 313 } 314 315 // Return an empty range if all ranges are empty. 316 if (highestEndTime < 0) 317 return TimeRanges::create(); 318 319 // 4. Let intersection ranges equal a TimeRange object containing a single range from 0 to highest end time. 320 RefPtr<TimeRanges> intersectionRanges = TimeRanges::create(0, highestEndTime); 321 322 // 5. For each SourceBuffer object in activeSourceBuffers run the following steps: 323 bool ended = readyState() == endedKeyword(); 324 for (size_t i = 0; i < ranges.size(); ++i) { 325 // 5.1 Let source ranges equal the ranges returned by the buffered attribute on the current SourceBuffer. 326 TimeRanges* sourceRanges = ranges[i].get(); 327 328 // 5.2 If readyState is "ended", then set the end time on the last range in source ranges to highest end time. 329 if (ended && sourceRanges->length()) 330 sourceRanges->add(sourceRanges->start(sourceRanges->length() - 1, ASSERT_NO_EXCEPTION), highestEndTime); 331 332 // 5.3 Let new intersection ranges equal the the intersection between the intersection ranges and the source ranges. 333 // 5.4 Replace the ranges in intersection ranges with the new intersection ranges. 334 intersectionRanges->intersectWith(sourceRanges); 335 } 336 337 return intersectionRanges.release(); 338 } 339 340 void MediaSource::setDuration(double duration, ExceptionState& exceptionState) 341 { 342 // 2.1 http://www.w3.org/TR/media-source/#widl-MediaSource-duration 343 // 1. If the value being set is negative or NaN then throw an InvalidAccessError 344 // exception and abort these steps. 345 if (std::isnan(duration)) { 346 exceptionState.throwDOMException(InvalidAccessError, ExceptionMessages::notAFiniteNumber(duration, "duration")); 347 return; 348 } 349 if (duration < 0.0) { 350 exceptionState.throwDOMException(InvalidAccessError, ExceptionMessages::indexExceedsMinimumBound("duration", duration, 0.0)); 351 return; 352 } 353 354 // 2. If the readyState attribute is not "open" then throw an InvalidStateError 355 // exception and abort these steps. 356 // 3. If the updating attribute equals true on any SourceBuffer in sourceBuffers, 357 // then throw an InvalidStateError exception and abort these steps. 358 if (throwExceptionIfClosedOrUpdating(isOpen(), isUpdating(), exceptionState)) 359 return; 360 361 // 4. Run the duration change algorithm with new duration set to the value being 362 // assigned to this attribute. 363 durationChangeAlgorithm(duration); 364 } 365 366 void MediaSource::durationChangeAlgorithm(double newDuration) 367 { 368 // Section 2.6.4 Duration change 369 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#duration-change-algorithm 370 // 1. If the current value of duration is equal to new duration, then return. 371 if (newDuration == duration()) 372 return; 373 374 // 2. Set old duration to the current value of duration. 375 double oldDuration = duration(); 376 377 bool requestSeek = m_attachedElement->currentTime() > newDuration; 378 379 // 3. Update duration to new duration. 380 m_webMediaSource->setDuration(newDuration); 381 382 // 4. If the new duration is less than old duration, then call remove(new duration, old duration) on all all objects in sourceBuffers. 383 if (newDuration < oldDuration) { 384 for (size_t i = 0; i < m_sourceBuffers->length(); ++i) 385 m_sourceBuffers->item(i)->remove(newDuration, oldDuration, ASSERT_NO_EXCEPTION); 386 } 387 388 // 5. If a user agent is unable to partially render audio frames or text cues that start before and end after the duration, then run the following steps: 389 // NOTE: Currently we assume that the media engine is able to render partial frames/cues. If a media 390 // engine gets added that doesn't support this, then we'll need to add logic to handle the substeps. 391 392 // 6. Update the media controller duration to new duration and run the HTMLMediaElement duration change algorithm. 393 m_attachedElement->durationChanged(newDuration, requestSeek); 394 } 395 396 void MediaSource::setReadyState(const AtomicString& state) 397 { 398 ASSERT(state == openKeyword() || state == closedKeyword() || state == endedKeyword()); 399 400 AtomicString oldState = readyState(); 401 WTF_LOG(Media, "MediaSource::setReadyState() %p : %s -> %s", this, oldState.ascii().data(), state.ascii().data()); 402 403 if (state == closedKeyword()) { 404 m_webMediaSource.clear(); 405 m_attachedElement = 0; 406 } 407 408 if (oldState == state) 409 return; 410 411 m_readyState = state; 412 413 onReadyStateChange(oldState, state); 414 } 415 416 void MediaSource::endOfStream(const AtomicString& error, ExceptionState& exceptionState) 417 { 418 DEFINE_STATIC_LOCAL(const AtomicString, network, ("network", AtomicString::ConstructFromLiteral)); 419 DEFINE_STATIC_LOCAL(const AtomicString, decode, ("decode", AtomicString::ConstructFromLiteral)); 420 421 if (error == network) { 422 endOfStreamInternal(blink::WebMediaSource::EndOfStreamStatusNetworkError, exceptionState); 423 } else if (error == decode) { 424 endOfStreamInternal(blink::WebMediaSource::EndOfStreamStatusDecodeError, exceptionState); 425 } else { 426 ASSERT_NOT_REACHED(); // IDL enforcement should prevent this case. 427 } 428 } 429 430 void MediaSource::endOfStream(ExceptionState& exceptionState) 431 { 432 endOfStreamInternal(blink::WebMediaSource::EndOfStreamStatusNoError, exceptionState); 433 } 434 435 void MediaSource::endOfStreamInternal(const blink::WebMediaSource::EndOfStreamStatus eosStatus, ExceptionState& exceptionState) 436 { 437 // 2.2 http://www.w3.org/TR/media-source/#widl-MediaSource-endOfStream-void-EndOfStreamError-error 438 // 1. If the readyState attribute is not in the "open" state then throw an 439 // InvalidStateError exception and abort these steps. 440 // 2. If the updating attribute equals true on any SourceBuffer in sourceBuffers, then throw an 441 // InvalidStateError exception and abort these steps. 442 if (throwExceptionIfClosedOrUpdating(isOpen(), isUpdating(), exceptionState)) 443 return; 444 445 // 3. Run the end of stream algorithm with the error parameter set to error. 446 // 1. Change the readyState attribute value to "ended". 447 // 2. Queue a task to fire a simple event named sourceended at the MediaSource. 448 setReadyState(endedKeyword()); 449 450 // 3. Do various steps based on |eosStatus|. 451 m_webMediaSource->markEndOfStream(eosStatus); 452 } 453 454 bool MediaSource::isOpen() const 455 { 456 return readyState() == openKeyword(); 457 } 458 459 bool MediaSource::isClosed() const 460 { 461 return readyState() == closedKeyword(); 462 } 463 464 void MediaSource::close() 465 { 466 setReadyState(closedKeyword()); 467 } 468 469 bool MediaSource::attachToElement(HTMLMediaElement* element) 470 { 471 if (m_attachedElement) 472 return false; 473 474 ASSERT(isClosed()); 475 476 TRACE_EVENT_ASYNC_BEGIN0("media", "MediaSource::attachToElement", this); 477 m_attachedElement = element; 478 return true; 479 } 480 481 void MediaSource::openIfInEndedState() 482 { 483 if (m_readyState != endedKeyword()) 484 return; 485 486 setReadyState(openKeyword()); 487 m_webMediaSource->unmarkEndOfStream(); 488 } 489 490 bool MediaSource::hasPendingActivity() const 491 { 492 return m_attachedElement || m_webMediaSource 493 || m_asyncEventQueue->hasPendingEvents() 494 || ActiveDOMObject::hasPendingActivity(); 495 } 496 497 void MediaSource::stop() 498 { 499 m_asyncEventQueue->close(); 500 if (!isClosed()) 501 setReadyState(closedKeyword()); 502 m_webMediaSource.clear(); 503 } 504 505 PassOwnPtr<WebSourceBuffer> MediaSource::createWebSourceBuffer(const String& type, const Vector<String>& codecs, ExceptionState& exceptionState) 506 { 507 WebSourceBuffer* webSourceBuffer = 0; 508 509 switch (m_webMediaSource->addSourceBuffer(type, codecs, &webSourceBuffer)) { 510 case WebMediaSource::AddStatusOk: 511 return adoptPtr(webSourceBuffer); 512 case WebMediaSource::AddStatusNotSupported: 513 ASSERT(!webSourceBuffer); 514 // 2.2 https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-MediaSource-addSourceBuffer-SourceBuffer-DOMString-type 515 // Step 2: If type contains a MIME type ... that is not supported with the types 516 // specified for the other SourceBuffer objects in sourceBuffers, then throw 517 // a NotSupportedError exception and abort these steps. 518 exceptionState.throwDOMException(NotSupportedError, "The type provided ('" + type + "') is not supported."); 519 return nullptr; 520 case WebMediaSource::AddStatusReachedIdLimit: 521 ASSERT(!webSourceBuffer); 522 // 2.2 https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-MediaSource-addSourceBuffer-SourceBuffer-DOMString-type 523 // Step 3: If the user agent can't handle any more SourceBuffer objects then throw 524 // a QuotaExceededError exception and abort these steps. 525 exceptionState.throwDOMException(QuotaExceededError, "This MediaSource has reached the limit of SourceBuffer objects it can handle. No additional SourceBuffer objects may be added."); 526 return nullptr; 527 } 528 529 ASSERT_NOT_REACHED(); 530 return nullptr; 531 } 532 533 void MediaSource::scheduleEvent(const AtomicString& eventName) 534 { 535 ASSERT(m_asyncEventQueue); 536 537 RefPtrWillBeRawPtr<Event> event = Event::create(eventName); 538 event->setTarget(this); 539 540 m_asyncEventQueue->enqueueEvent(event.release()); 541 } 542 543 URLRegistry& MediaSource::registry() const 544 { 545 return MediaSourceRegistry::registry(); 546 } 547 548 } // namespace WebCore 549