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 "MediaSource.h" 33 34#if ENABLE(MEDIA_SOURCE) 35 36#include "AudioTrack.h" 37#include "AudioTrackList.h" 38#include "ContentType.h" 39#include "Event.h" 40#include "ExceptionCode.h" 41#include "ExceptionCodePlaceholder.h" 42#include "GenericEventQueue.h" 43#include "HTMLMediaElement.h" 44#include "Logging.h" 45#include "MIMETypeRegistry.h" 46#include "MediaError.h" 47#include "MediaPlayer.h" 48#include "MediaSourceRegistry.h" 49#include "SourceBufferPrivate.h" 50#include "TextTrack.h" 51#include "TextTrackList.h" 52#include "TimeRanges.h" 53#include "VideoTrack.h" 54#include "VideoTrackList.h" 55#include <runtime/Uint8Array.h> 56#include <wtf/text/CString.h> 57#include <wtf/text/WTFString.h> 58 59namespace WebCore { 60 61URLRegistry* MediaSource::s_registry = 0; 62 63void MediaSource::setRegistry(URLRegistry* registry) 64{ 65 ASSERT(!s_registry); 66 s_registry = registry; 67} 68 69PassRefPtr<MediaSource> MediaSource::create(ScriptExecutionContext& context) 70{ 71 RefPtr<MediaSource> mediaSource(adoptRef(new MediaSource(context))); 72 mediaSource->suspendIfNeeded(); 73 return mediaSource.release(); 74} 75 76MediaSource::MediaSource(ScriptExecutionContext& context) 77 : ActiveDOMObject(&context) 78 , m_mediaElement(0) 79 , m_duration(std::numeric_limits<double>::quiet_NaN()) 80 , m_pendingSeekTime(MediaTime::invalidTime()) 81 , m_readyState(closedKeyword()) 82 , m_asyncEventQueue(*this) 83{ 84 LOG(MediaSource, "MediaSource::MediaSource %p", this); 85 m_sourceBuffers = SourceBufferList::create(scriptExecutionContext()); 86 m_activeSourceBuffers = SourceBufferList::create(scriptExecutionContext()); 87} 88 89MediaSource::~MediaSource() 90{ 91 LOG(MediaSource, "MediaSource::~MediaSource %p", this); 92 ASSERT(isClosed()); 93} 94 95const AtomicString& MediaSource::openKeyword() 96{ 97 DEPRECATED_DEFINE_STATIC_LOCAL(const AtomicString, open, ("open", AtomicString::ConstructFromLiteral)); 98 return open; 99} 100 101const AtomicString& MediaSource::closedKeyword() 102{ 103 DEPRECATED_DEFINE_STATIC_LOCAL(const AtomicString, closed, ("closed", AtomicString::ConstructFromLiteral)); 104 return closed; 105} 106 107const AtomicString& MediaSource::endedKeyword() 108{ 109 DEPRECATED_DEFINE_STATIC_LOCAL(const AtomicString, ended, ("ended", AtomicString::ConstructFromLiteral)); 110 return ended; 111} 112 113void MediaSource::setPrivateAndOpen(PassRef<MediaSourcePrivate> mediaSourcePrivate) 114{ 115 ASSERT(!m_private); 116 ASSERT(m_mediaElement); 117 m_private = WTF::move(mediaSourcePrivate); 118 setReadyState(openKeyword()); 119} 120 121void MediaSource::addedToRegistry() 122{ 123 setPendingActivity(this); 124} 125 126void MediaSource::removedFromRegistry() 127{ 128 unsetPendingActivity(this); 129} 130 131double MediaSource::duration() const 132{ 133 return m_duration; 134} 135 136double MediaSource::currentTime() const 137{ 138 return m_mediaElement ? m_mediaElement->currentTime() : 0; 139} 140 141std::unique_ptr<PlatformTimeRanges> MediaSource::buffered() const 142{ 143 // Implements MediaSource algorithm for HTMLMediaElement.buffered. 144 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#htmlmediaelement-extensions 145 Vector<RefPtr<TimeRanges>> ranges = activeRanges(); 146 147 // 1. If activeSourceBuffers.length equals 0 then return an empty TimeRanges object and abort these steps. 148 if (ranges.isEmpty()) 149 return PlatformTimeRanges::create(); 150 151 // 2. Let active ranges be the ranges returned by buffered for each SourceBuffer object in activeSourceBuffers. 152 // 3. Let highest end time be the largest range end time in the active ranges. 153 double highestEndTime = -1; 154 for (size_t i = 0; i < ranges.size(); ++i) { 155 unsigned length = ranges[i]->length(); 156 if (length) 157 highestEndTime = std::max(highestEndTime, ranges[i]->end(length - 1, ASSERT_NO_EXCEPTION)); 158 } 159 160 // Return an empty range if all ranges are empty. 161 if (highestEndTime < 0) 162 return PlatformTimeRanges::create(); 163 164 // 4. Let intersection ranges equal a TimeRange object containing a single range from 0 to highest end time. 165 RefPtr<TimeRanges> intersectionRanges = TimeRanges::create(0, highestEndTime); 166 167 // 5. For each SourceBuffer object in activeSourceBuffers run the following steps: 168 bool ended = readyState() == endedKeyword(); 169 for (size_t i = 0; i < ranges.size(); ++i) { 170 // 5.1 Let source ranges equal the ranges returned by the buffered attribute on the current SourceBuffer. 171 TimeRanges* sourceRanges = ranges[i].get(); 172 173 // 5.2 If readyState is "ended", then set the end time on the last range in source ranges to highest end time. 174 if (ended && sourceRanges->length()) 175 sourceRanges->add(sourceRanges->start(sourceRanges->length() - 1, ASSERT_NO_EXCEPTION), highestEndTime); 176 177 // 5.3 Let new intersection ranges equal the the intersection between the intersection ranges and the source ranges. 178 // 5.4 Replace the ranges in intersection ranges with the new intersection ranges. 179 intersectionRanges->intersectWith(*sourceRanges); 180 } 181 182 return PlatformTimeRanges::create(intersectionRanges->ranges()); 183} 184 185void MediaSource::seekToTime(const MediaTime& time) 186{ 187 // 2.4.3 Seeking 188 // https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#mediasource-seeking 189 190 m_pendingSeekTime = time; 191 192 // Run the following steps as part of the "Wait until the user agent has established whether or not the 193 // media data for the new playback position is available, and, if it is, until it has decoded enough data 194 // to play back that position" step of the seek algorithm: 195 // 1. The media element looks for media segments containing the new playback position in each SourceBuffer 196 // object in activeSourceBuffers. 197 for (auto& sourceBuffer : *m_activeSourceBuffers) { 198 // ↳ If one or more of the objects in activeSourceBuffers is missing media segments for the new 199 // playback position 200 if (!sourceBuffer->buffered()->ranges().contain(time)) { 201 // 1.1 Set the HTMLMediaElement.readyState attribute to HAVE_METADATA. 202 m_private->setReadyState(MediaPlayer::HaveMetadata); 203 204 // 1.2 The media element waits until an appendBuffer() or an appendStream() call causes the coded 205 // frame processing algorithm to set the HTMLMediaElement.readyState attribute to a value greater 206 // than HAVE_METADATA. 207 LOG(MediaSource, "MediaSource::seekToTime(%p) - waitForSeekCompleted()", this); 208 m_private->waitForSeekCompleted(); 209 return; 210 } 211 // ↳ Otherwise 212 // Continue 213 } 214 215 completeSeek(); 216} 217 218void MediaSource::completeSeek() 219{ 220 // 2.4.3 Seeking, ctd. 221 // https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#mediasource-seeking 222 223 ASSERT(m_pendingSeekTime.isValid()); 224 225 // 2. The media element resets all decoders and initializes each one with data from the appropriate 226 // initialization segment. 227 // 3. The media element feeds coded frames from the active track buffers into the decoders starting 228 // with the closest random access point before the new playback position. 229 for (auto& sourceBuffer : *m_activeSourceBuffers) 230 sourceBuffer->seekToTime(m_pendingSeekTime); 231 232 // 4. Resume the seek algorithm at the "Await a stable state" step. 233 m_private->seekCompleted(); 234 235 m_pendingSeekTime = MediaTime::invalidTime(); 236 monitorSourceBuffers(); 237} 238 239void MediaSource::monitorSourceBuffers() 240{ 241 // 2.4.4 SourceBuffer Monitoring 242 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#buffer-monitoring 243 244 // Note, the behavior if activeSourceBuffers is empty is undefined. 245 if (!m_activeSourceBuffers) { 246 m_private->setReadyState(MediaPlayer::HaveNothing); 247 return; 248 } 249 250 // ↳ If buffered for all objects in activeSourceBuffers do not contain TimeRanges for the current 251 // playback position: 252 auto begin = m_activeSourceBuffers->begin(); 253 auto end = m_activeSourceBuffers->end(); 254 if (std::all_of(begin, end, [](RefPtr<SourceBuffer>& sourceBuffer) { 255 return !sourceBuffer->hasCurrentTime(); 256 })) { 257 // 1. Set the HTMLMediaElement.readyState attribute to HAVE_METADATA. 258 // 2. If this is the first transition to HAVE_METADATA, then queue a task to fire a simple event 259 // named loadedmetadata at the media element. 260 m_private->setReadyState(MediaPlayer::HaveMetadata); 261 262 // 3. Abort these steps. 263 return; 264 } 265 266 // ↳ If buffered for all objects in activeSourceBuffers contain TimeRanges that include the current 267 // playback position and enough data to ensure uninterrupted playback: 268 if (std::all_of(begin, end, [](RefPtr<SourceBuffer>& sourceBuffer) { 269 return sourceBuffer->hasFutureTime() && sourceBuffer->canPlayThrough(); 270 })) { 271 // 1. Set the HTMLMediaElement.readyState attribute to HAVE_ENOUGH_DATA. 272 // 2. Queue a task to fire a simple event named canplaythrough at the media element. 273 // 3. Playback may resume at this point if it was previously suspended by a transition to HAVE_CURRENT_DATA. 274 m_private->setReadyState(MediaPlayer::HaveEnoughData); 275 276 if (m_pendingSeekTime.isValid()) 277 completeSeek(); 278 279 // 4. Abort these steps. 280 return; 281 } 282 283 // ↳ If buffered for all objects in activeSourceBuffers contain a TimeRange that includes 284 // the current playback position and some time beyond the current playback position, then run the following steps: 285 if (std::all_of(begin, end, [](RefPtr<SourceBuffer>& sourceBuffer) { 286 return sourceBuffer->hasFutureTime(); 287 })) { 288 // 1. Set the HTMLMediaElement.readyState attribute to HAVE_FUTURE_DATA. 289 // 2. If the previous value of HTMLMediaElement.readyState was less than HAVE_FUTURE_DATA, then queue a task to fire a simple event named canplay at the media element. 290 // 3. Playback may resume at this point if it was previously suspended by a transition to HAVE_CURRENT_DATA. 291 m_private->setReadyState(MediaPlayer::HaveFutureData); 292 293 if (m_pendingSeekTime.isValid()) 294 completeSeek(); 295 296 // 4. Abort these steps. 297 return; 298 } 299 300 // ↳ If buffered for at least one object in activeSourceBuffers contains a TimeRange that ends 301 // at the current playback position and does not have a range covering the time immediately 302 // after the current position: 303 // NOTE: Logically, !(all objects do not contain currentTime) == (some objects contain current time) 304 305 // 1. Set the HTMLMediaElement.readyState attribute to HAVE_CURRENT_DATA. 306 // 2. If this is the first transition to HAVE_CURRENT_DATA, then queue a task to fire a simple 307 // event named loadeddata at the media element. 308 // 3. Playback is suspended at this point since the media element doesn't have enough data to 309 // advance the media timeline. 310 m_private->setReadyState(MediaPlayer::HaveCurrentData); 311 312 if (m_pendingSeekTime.isValid()) 313 completeSeek(); 314 315 // 4. Abort these steps. 316} 317 318void MediaSource::setDuration(double duration, ExceptionCode& ec) 319{ 320 // 2.1 Attributes - Duration 321 // https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#attributes 322 323 // On setting, run the following steps: 324 // 1. If the value being set is negative or NaN then throw an INVALID_ACCESS_ERR exception and abort these steps. 325 if (duration < 0.0 || std::isnan(duration)) { 326 ec = INVALID_ACCESS_ERR; 327 return; 328 } 329 330 // 2. If the readyState attribute is not "open" then throw an INVALID_STATE_ERR exception and abort these steps. 331 if (!isOpen()) { 332 ec = INVALID_STATE_ERR; 333 return; 334 } 335 336 // 3. If the updating attribute equals true on any SourceBuffer in sourceBuffers, then throw an INVALID_STATE_ERR 337 // exception and abort these steps. 338 for (auto& sourceBuffer : *m_sourceBuffers) { 339 if (sourceBuffer->updating()) { 340 ec = INVALID_STATE_ERR; 341 return; 342 } 343 } 344 345 // 4. Run the duration change algorithm with new duration set to the value being assigned to this attribute. 346 setDurationInternal(duration); 347} 348 349void MediaSource::setDurationInternal(double duration) 350{ 351 // Duration Change Algorithm 352 // https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#duration-change-algorithm 353 354 // 1. If the current value of duration is equal to new duration, then return. 355 if (duration == m_duration) 356 return; 357 358 // 2. Set old duration to the current value of duration. 359 double oldDuration = m_duration; 360 361 // 3. Update duration to new duration. 362 m_duration = duration; 363 364 // 4. If the new duration is less than old duration, then call remove(new duration, old duration) 365 // on all objects in sourceBuffers. 366 if (!isnan(oldDuration) && duration < oldDuration) { 367 for (auto& sourceBuffer : *m_sourceBuffers) 368 sourceBuffer->remove(duration, oldDuration, IGNORE_EXCEPTION); 369 } 370 371 // 5. If a user agent is unable to partially render audio frames or text cues that start before and end after the 372 // duration, then run the following steps: 373 // 5.1 Update new duration to the highest end time reported by the buffered attribute across all SourceBuffer objects 374 // in sourceBuffers. 375 // 5.2 Update duration to new duration. 376 // NOTE: Assume UA is able to partially render audio frames. 377 378 // 6. Update the media controller duration to new duration and run the HTMLMediaElement duration change algorithm. 379 LOG(MediaSource, "MediaSource::setDurationInternal(%p) - duration(%g)", this, duration); 380 m_private->durationChanged(); 381} 382 383void MediaSource::setReadyState(const AtomicString& state) 384{ 385 ASSERT(state == openKeyword() || state == closedKeyword() || state == endedKeyword()); 386 387 AtomicString oldState = readyState(); 388 LOG(MediaSource, "MediaSource::setReadyState() %p : %s -> %s", this, oldState.string().ascii().data(), state.string().ascii().data()); 389 390 if (state == closedKeyword()) { 391 m_private.clear(); 392 m_mediaElement = 0; 393 m_duration = std::numeric_limits<double>::quiet_NaN(); 394 } 395 396 if (oldState == state) 397 return; 398 399 m_readyState = state; 400 401 onReadyStateChange(oldState, state); 402} 403 404static bool SourceBufferIsUpdating(RefPtr<SourceBuffer>& sourceBuffer) 405{ 406 return sourceBuffer->updating(); 407} 408 409void MediaSource::endOfStream(ExceptionCode& ec) 410{ 411 endOfStream(emptyAtom, ec); 412} 413 414void MediaSource::endOfStream(const AtomicString& error, ExceptionCode& ec) 415{ 416 // 2.2 https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#widl-MediaSource-endOfStream-void-EndOfStreamError-error 417 // 1. If the readyState attribute is not in the "open" state then throw an 418 // INVALID_STATE_ERR exception and abort these steps. 419 if (!isOpen()) { 420 ec = INVALID_STATE_ERR; 421 return; 422 } 423 424 // 2. If the updating attribute equals true on any SourceBuffer in sourceBuffers, then throw an 425 // INVALID_STATE_ERR exception and abort these steps. 426 if (std::any_of(m_sourceBuffers->begin(), m_sourceBuffers->end(), SourceBufferIsUpdating)) { 427 ec = INVALID_STATE_ERR; 428 return; 429 } 430 431 // 3. Run the end of stream algorithm with the error parameter set to error. 432 streamEndedWithError(error, ec); 433} 434 435void MediaSource::streamEndedWithError(const AtomicString& error, ExceptionCode& ec) 436{ 437 DEPRECATED_DEFINE_STATIC_LOCAL(const AtomicString, network, ("network", AtomicString::ConstructFromLiteral)); 438 DEPRECATED_DEFINE_STATIC_LOCAL(const AtomicString, decode, ("decode", AtomicString::ConstructFromLiteral)); 439 440 // 2.4.7 https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#end-of-stream-algorithm 441 442 // 3. 443 if (error.isEmpty()) { 444 // ↳ If error is not set, is null, or is an empty string 445 // 1. Run the duration change algorithm with new duration set to the highest end time reported by 446 // the buffered attribute across all SourceBuffer objects in sourceBuffers. 447 double maxEndTime = 0; 448 for (auto& sourceBuffer : *m_sourceBuffers) { 449 if (auto length = sourceBuffer->buffered()->length()) 450 maxEndTime = std::max(sourceBuffer->buffered()->end(length - 1, IGNORE_EXCEPTION), maxEndTime); 451 } 452 setDurationInternal(maxEndTime); 453 454 // 2. Notify the media element that it now has all of the media data. 455 m_private->markEndOfStream(MediaSourcePrivate::EosNoError); 456 } 457 458 // NOTE: Do steps 1 & 2 after step 3 (with an empty error) to avoid the MediaSource's readyState being re-opened by a 459 // remove() operation resulting from a duration change. 460 // FIXME: Re-number or update this section once <https://www.w3.org/Bugs/Public/show_bug.cgi?id=26316> is resolved. 461 // 1. Change the readyState attribute value to "ended". 462 // 2. Queue a task to fire a simple event named sourceended at the MediaSource. 463 setReadyState(endedKeyword()); 464 465 if (error == network) { 466 // ↳ If error is set to "network" 467 ASSERT(m_mediaElement); 468 if (m_mediaElement->readyState() == HTMLMediaElement::HAVE_NOTHING) { 469 // ↳ If the HTMLMediaElement.readyState attribute equals HAVE_NOTHING 470 // Run the "If the media data cannot be fetched at all, due to network errors, causing 471 // the user agent to give up trying to fetch the resource" steps of the resource fetch algorithm. 472 // NOTE: This step is handled by HTMLMediaElement::mediaLoadingFailed(). 473 m_mediaElement->mediaLoadingFailed(MediaPlayer::NetworkError); 474 } else { 475 // ↳ If the HTMLMediaElement.readyState attribute is greater than HAVE_NOTHING 476 // Run the "If the connection is interrupted after some media data has been received, causing the 477 // user agent to give up trying to fetch the resource" steps of the resource fetch algorithm. 478 // NOTE: This step is handled by HTMLMediaElement::mediaLoadingFailedFatally(). 479 m_mediaElement->mediaLoadingFailedFatally(MediaPlayer::NetworkError); 480 } 481 } else if (error == decode) { 482 // ↳ If error is set to "decode" 483 ASSERT(m_mediaElement); 484 if (m_mediaElement->readyState() == HTMLMediaElement::HAVE_NOTHING) { 485 // ↳ If the HTMLMediaElement.readyState attribute equals HAVE_NOTHING 486 // Run the "If the media data can be fetched but is found by inspection to be in an unsupported 487 // format, or can otherwise not be rendered at all" steps of the resource fetch algorithm. 488 // NOTE: This step is handled by HTMLMediaElement::mediaLoadingFailed(). 489 m_mediaElement->mediaLoadingFailed(MediaPlayer::FormatError); 490 } else { 491 // ↳ If the HTMLMediaElement.readyState attribute is greater than HAVE_NOTHING 492 // Run the media data is corrupted steps of the resource fetch algorithm. 493 // NOTE: This step is handled by HTMLMediaElement::mediaLoadingFailedFatally(). 494 m_mediaElement->mediaLoadingFailedFatally(MediaPlayer::DecodeError); 495 } 496 } else if (!error.isEmpty()) { 497 // ↳ Otherwise 498 // Throw an INVALID_ACCESS_ERR exception. 499 ec = INVALID_ACCESS_ERR; 500 } 501} 502 503SourceBuffer* MediaSource::addSourceBuffer(const String& type, ExceptionCode& ec) 504{ 505 LOG(MediaSource, "MediaSource::addSourceBuffer(%s) %p", type.ascii().data(), this); 506 507 // 2.2 https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-MediaSource-addSourceBuffer-SourceBuffer-DOMString-type 508 // 1. If type is null or an empty then throw an INVALID_ACCESS_ERR exception and 509 // abort these steps. 510 if (type.isNull() || type.isEmpty()) { 511 ec = INVALID_ACCESS_ERR; 512 return nullptr; 513 } 514 515 // 2. If type contains a MIME type that is not supported ..., then throw a 516 // NOT_SUPPORTED_ERR exception and abort these steps. 517 if (!isTypeSupported(type)) { 518 ec = NOT_SUPPORTED_ERR; 519 return nullptr; 520 } 521 522 // 4. If the readyState attribute is not in the "open" state then throw an 523 // INVALID_STATE_ERR exception and abort these steps. 524 if (!isOpen()) { 525 ec = INVALID_STATE_ERR; 526 return nullptr; 527 } 528 529 // 5. Create a new SourceBuffer object and associated resources. 530 ContentType contentType(type); 531 RefPtr<SourceBufferPrivate> sourceBufferPrivate = createSourceBufferPrivate(contentType, ec); 532 533 if (!sourceBufferPrivate) { 534 ASSERT(ec == NOT_SUPPORTED_ERR || ec == QUOTA_EXCEEDED_ERR); 535 // 2. If type contains a MIME type that is not supported ..., then throw a NOT_SUPPORTED_ERR exception and abort these steps. 536 // 3. If the user agent can't handle any more SourceBuffer objects then throw a QUOTA_EXCEEDED_ERR exception and abort these steps 537 return nullptr; 538 } 539 540 RefPtr<SourceBuffer> buffer = SourceBuffer::create(sourceBufferPrivate.releaseNonNull(), this); 541 // 6. Add the new object to sourceBuffers and fire a addsourcebuffer on that object. 542 m_sourceBuffers->add(buffer); 543 regenerateActiveSourceBuffers(); 544 545 // 7. Return the new object to the caller. 546 return buffer.get(); 547} 548 549void MediaSource::removeSourceBuffer(SourceBuffer* buffer, ExceptionCode& ec) 550{ 551 LOG(MediaSource, "MediaSource::removeSourceBuffer() %p", this); 552 RefPtr<SourceBuffer> protect(buffer); 553 554 // 2.2 https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-MediaSource-removeSourceBuffer-void-SourceBuffer-sourceBuffer 555 // 1. If sourceBuffer is null then throw an INVALID_ACCESS_ERR exception and 556 // abort these steps. 557 if (!buffer) { 558 ec = INVALID_ACCESS_ERR; 559 return; 560 } 561 562 // 2. If sourceBuffer specifies an object that is not in sourceBuffers then 563 // throw a NOT_FOUND_ERR exception and abort these steps. 564 if (!m_sourceBuffers->length() || !m_sourceBuffers->contains(buffer)) { 565 ec = NOT_FOUND_ERR; 566 return; 567 } 568 569 // 3. If the sourceBuffer.updating attribute equals true, then run the following steps: ... 570 buffer->abortIfUpdating(); 571 572 // 4. Let SourceBuffer audioTracks list equal the AudioTrackList object returned by sourceBuffer.audioTracks. 573 RefPtr<AudioTrackList> audioTracks = buffer->audioTracks(); 574 575 // 5. If the SourceBuffer audioTracks list is not empty, then run the following steps: 576 if (audioTracks->length()) { 577 // 5.1 Let HTMLMediaElement audioTracks list equal the AudioTrackList object returned by the audioTracks 578 // attribute on the HTMLMediaElement. 579 // 5.2 Let the removed enabled audio track flag equal false. 580 bool removedEnabledAudioTrack = false; 581 582 // 5.3 For each AudioTrack object in the SourceBuffer audioTracks list, run the following steps: 583 while (audioTracks->length()) { 584 AudioTrack* track = audioTracks->lastItem(); 585 586 // 5.3.1 Set the sourceBuffer attribute on the AudioTrack object to null. 587 track->setSourceBuffer(0); 588 589 // 5.3.2 If the enabled attribute on the AudioTrack object is true, then set the removed enabled 590 // audio track flag to true. 591 if (track->enabled()) 592 removedEnabledAudioTrack = true; 593 594 // 5.3.3 Remove the AudioTrack object from the HTMLMediaElement audioTracks list. 595 // 5.3.4 Queue a task to fire a trusted event named removetrack, that does not bubble and is not 596 // cancelable, and that uses the TrackEvent interface, at the HTMLMediaElement audioTracks list. 597 if (mediaElement()) 598 mediaElement()->removeAudioTrack(track); 599 600 // 5.3.5 Remove the AudioTrack object from the SourceBuffer audioTracks list. 601 // 5.3.6 Queue a task to fire a trusted event named removetrack, that does not bubble and is not 602 // cancelable, and that uses the TrackEvent interface, at the SourceBuffer audioTracks list. 603 audioTracks->remove(track); 604 } 605 606 // 5.4 If the removed enabled audio track flag equals true, then queue a task to fire a simple event 607 // named change at the HTMLMediaElement audioTracks list. 608 if (removedEnabledAudioTrack) 609 mediaElement()->audioTracks()->scheduleChangeEvent(); 610 } 611 612 // 6. Let SourceBuffer videoTracks list equal the VideoTrackList object returned by sourceBuffer.videoTracks. 613 RefPtr<VideoTrackList> videoTracks = buffer->videoTracks(); 614 615 // 7. If the SourceBuffer videoTracks list is not empty, then run the following steps: 616 if (videoTracks->length()) { 617 // 7.1 Let HTMLMediaElement videoTracks list equal the VideoTrackList object returned by the videoTracks 618 // attribute on the HTMLMediaElement. 619 // 7.2 Let the removed selected video track flag equal false. 620 bool removedSelectedVideoTrack = false; 621 622 // 7.3 For each VideoTrack object in the SourceBuffer videoTracks list, run the following steps: 623 while (videoTracks->length()) { 624 VideoTrack* track = videoTracks->lastItem(); 625 626 // 7.3.1 Set the sourceBuffer attribute on the VideoTrack object to null. 627 track->setSourceBuffer(0); 628 629 // 7.3.2 If the selected attribute on the VideoTrack object is true, then set the removed selected 630 // video track flag to true. 631 if (track->selected()) 632 removedSelectedVideoTrack = true; 633 634 // 7.3.3 Remove the VideoTrack object from the HTMLMediaElement videoTracks list. 635 // 7.3.4 Queue a task to fire a trusted event named removetrack, that does not bubble and is not 636 // cancelable, and that uses the TrackEvent interface, at the HTMLMediaElement videoTracks list. 637 if (mediaElement()) 638 mediaElement()->removeVideoTrack(track); 639 640 // 7.3.5 Remove the VideoTrack object from the SourceBuffer videoTracks list. 641 // 7.3.6 Queue a task to fire a trusted event named removetrack, that does not bubble and is not 642 // cancelable, and that uses the TrackEvent interface, at the SourceBuffer videoTracks list. 643 videoTracks->remove(track); 644 } 645 646 // 7.4 If the removed selected video track flag equals true, then queue a task to fire a simple event 647 // named change at the HTMLMediaElement videoTracks list. 648 if (removedSelectedVideoTrack) 649 mediaElement()->videoTracks()->scheduleChangeEvent(); 650 } 651 652 // 8. Let SourceBuffer textTracks list equal the TextTrackList object returned by sourceBuffer.textTracks. 653 RefPtr<TextTrackList> textTracks = buffer->textTracks(); 654 655 // 9. If the SourceBuffer textTracks list is not empty, then run the following steps: 656 if (textTracks->length()) { 657 // 9.1 Let HTMLMediaElement textTracks list equal the TextTrackList object returned by the textTracks 658 // attribute on the HTMLMediaElement. 659 // 9.2 Let the removed enabled text track flag equal false. 660 bool removedEnabledTextTrack = false; 661 662 // 9.3 For each TextTrack object in the SourceBuffer textTracks list, run the following steps: 663 while (textTracks->length()) { 664 TextTrack* track = textTracks->lastItem(); 665 666 // 9.3.1 Set the sourceBuffer attribute on the TextTrack object to null. 667 track->setSourceBuffer(0); 668 669 // 9.3.2 If the mode attribute on the TextTrack object is set to "showing" or "hidden", then 670 // set the removed enabled text track flag to true. 671 if (track->mode() == TextTrack::showingKeyword() || track->mode() == TextTrack::hiddenKeyword()) 672 removedEnabledTextTrack = true; 673 674 // 9.3.3 Remove the TextTrack object from the HTMLMediaElement textTracks list. 675 // 9.3.4 Queue a task to fire a trusted event named removetrack, that does not bubble and is not 676 // cancelable, and that uses the TrackEvent interface, at the HTMLMediaElement textTracks list. 677 if (mediaElement()) 678 mediaElement()->removeTextTrack(track); 679 680 // 9.3.5 Remove the TextTrack object from the SourceBuffer textTracks list. 681 // 9.3.6 Queue a task to fire a trusted event named removetrack, that does not bubble and is not 682 // cancelable, and that uses the TrackEvent interface, at the SourceBuffer textTracks list. 683 textTracks->remove(track); 684 } 685 686 // 9.4 If the removed enabled text track flag equals true, then queue a task to fire a simple event 687 // named change at the HTMLMediaElement textTracks list. 688 if (removedEnabledTextTrack) 689 mediaElement()->textTracks()->scheduleChangeEvent(); 690 } 691 692 693 // 10. If sourceBuffer is in activeSourceBuffers, then remove sourceBuffer from activeSourceBuffers ... 694 m_activeSourceBuffers->remove(buffer); 695 696 // 11. Remove sourceBuffer from sourceBuffers and fire a removesourcebuffer event 697 // on that object. 698 m_sourceBuffers->remove(buffer); 699 700 // 12. Destroy all resources for sourceBuffer. 701 buffer->removedFromMediaSource(); 702} 703 704bool MediaSource::isTypeSupported(const String& type) 705{ 706 LOG(MediaSource, "MediaSource::isTypeSupported(%s)", type.ascii().data()); 707 708 // Section 2.2 isTypeSupported() method steps. 709 // https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#widl-MediaSource-isTypeSupported-boolean-DOMString-type 710 // 1. If type is an empty string, then return false. 711 if (type.isNull() || type.isEmpty()) 712 return false; 713 714 ContentType contentType(type); 715 String codecs = contentType.parameter("codecs"); 716 717 // 2. If type does not contain a valid MIME type string, then return false. 718 if (contentType.type().isEmpty() || codecs.isEmpty()) 719 return false; 720 721 // 3. If type contains a media type or media subtype that the MediaSource does not support, then return false. 722 // 4. If type contains at a codec that the MediaSource does not support, then return false. 723 // 5. If the MediaSource does not support the specified combination of media type, media subtype, and codecs then return false. 724 // 6. Return true. 725 MediaEngineSupportParameters parameters; 726 parameters.type = contentType.type(); 727 parameters.codecs = codecs; 728 parameters.isMediaSource = true; 729 return MediaPlayer::supportsType(parameters, 0) != MediaPlayer::IsNotSupported; 730} 731 732bool MediaSource::isOpen() const 733{ 734 return readyState() == openKeyword(); 735} 736 737bool MediaSource::isClosed() const 738{ 739 return readyState() == closedKeyword(); 740} 741 742bool MediaSource::isEnded() const 743{ 744 return readyState() == endedKeyword(); 745} 746 747void MediaSource::close() 748{ 749 setReadyState(closedKeyword()); 750} 751 752void MediaSource::sourceBufferDidChangeAcitveState(SourceBuffer*, bool) 753{ 754 regenerateActiveSourceBuffers(); 755} 756 757bool MediaSource::attachToElement(HTMLMediaElement* element) 758{ 759 if (m_mediaElement) 760 return false; 761 762 ASSERT(isClosed()); 763 764 m_mediaElement = element; 765 return true; 766} 767 768void MediaSource::openIfInEndedState() 769{ 770 if (m_readyState != endedKeyword()) 771 return; 772 773 setReadyState(openKeyword()); 774 m_private->unmarkEndOfStream(); 775} 776 777bool MediaSource::hasPendingActivity() const 778{ 779 return m_private || m_asyncEventQueue.hasPendingEvents() 780 || ActiveDOMObject::hasPendingActivity(); 781} 782 783void MediaSource::stop() 784{ 785 m_asyncEventQueue.close(); 786 if (!isClosed()) 787 setReadyState(closedKeyword()); 788 m_private.clear(); 789} 790 791void MediaSource::onReadyStateChange(const AtomicString& oldState, const AtomicString& newState) 792{ 793 if (isOpen()) { 794 scheduleEvent(eventNames().sourceopenEvent); 795 return; 796 } 797 798 if (oldState == openKeyword() && newState == endedKeyword()) { 799 scheduleEvent(eventNames().sourceendedEvent); 800 return; 801 } 802 803 ASSERT(isClosed()); 804 805 m_activeSourceBuffers->clear(); 806 807 // Clear SourceBuffer references to this object. 808 for (unsigned long i = 0, length = m_sourceBuffers->length(); i < length; ++i) 809 m_sourceBuffers->item(i)->removedFromMediaSource(); 810 m_sourceBuffers->clear(); 811 812 scheduleEvent(eventNames().sourcecloseEvent); 813} 814 815Vector<RefPtr<TimeRanges>> MediaSource::activeRanges() const 816{ 817 Vector<RefPtr<TimeRanges>> activeRanges(m_activeSourceBuffers->length()); 818 for (size_t i = 0, length = m_activeSourceBuffers->length(); i < length; ++i) 819 activeRanges[i] = m_activeSourceBuffers->item(i)->buffered(ASSERT_NO_EXCEPTION); 820 821 return activeRanges; 822} 823 824RefPtr<SourceBufferPrivate> MediaSource::createSourceBufferPrivate(const ContentType& type, ExceptionCode& ec) 825{ 826 RefPtr<SourceBufferPrivate> sourceBufferPrivate; 827 switch (m_private->addSourceBuffer(type, sourceBufferPrivate)) { 828 case MediaSourcePrivate::Ok: { 829 return sourceBufferPrivate; 830 } 831 case MediaSourcePrivate::NotSupported: 832 // 2.2 https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-MediaSource-addSourceBuffer-SourceBuffer-DOMString-type 833 // Step 2: If type contains a MIME type ... that is not supported with the types 834 // specified for the other SourceBuffer objects in sourceBuffers, then throw 835 // a NOT_SUPPORTED_ERR exception and abort these steps. 836 ec = NOT_SUPPORTED_ERR; 837 return nullptr; 838 case MediaSourcePrivate::ReachedIdLimit: 839 // 2.2 https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-MediaSource-addSourceBuffer-SourceBuffer-DOMString-type 840 // Step 3: If the user agent can't handle any more SourceBuffer objects then throw 841 // a QUOTA_EXCEEDED_ERR exception and abort these steps. 842 ec = QUOTA_EXCEEDED_ERR; 843 return nullptr; 844 } 845 846 ASSERT_NOT_REACHED(); 847 return nullptr; 848} 849 850void MediaSource::scheduleEvent(const AtomicString& eventName) 851{ 852 RefPtr<Event> event = Event::create(eventName, false, false); 853 event->setTarget(this); 854 855 m_asyncEventQueue.enqueueEvent(event.release()); 856} 857 858ScriptExecutionContext* MediaSource::scriptExecutionContext() const 859{ 860 return ActiveDOMObject::scriptExecutionContext(); 861} 862 863EventTargetInterface MediaSource::eventTargetInterface() const 864{ 865 return MediaSourceEventTargetInterfaceType; 866} 867 868URLRegistry& MediaSource::registry() const 869{ 870 return MediaSourceRegistry::registry(); 871} 872 873void MediaSource::regenerateActiveSourceBuffers() 874{ 875 Vector<RefPtr<SourceBuffer>> newList; 876 for (auto& sourceBuffer : *m_sourceBuffers) { 877 if (sourceBuffer->active()) 878 newList.append(sourceBuffer); 879 } 880 m_activeSourceBuffers->swap(newList); 881} 882 883} 884 885#endif 886