| /* |
| * Copyright (C) 2013 Google Inc. All rights reserved. |
| * Copyright (C) 2013-2017 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "MediaSource.h" |
| |
| #if ENABLE(MEDIA_SOURCE) |
| |
| #include "AudioTrackList.h" |
| #include "ContentType.h" |
| #include "Event.h" |
| #include "EventNames.h" |
| #include "HTMLMediaElement.h" |
| #include "Logging.h" |
| #include "MediaSourcePrivate.h" |
| #include "MediaSourceRegistry.h" |
| #include "Quirks.h" |
| #include "RuntimeEnabledFeatures.h" |
| #include "Settings.h" |
| #include "SourceBuffer.h" |
| #include "SourceBufferList.h" |
| #include "SourceBufferPrivate.h" |
| #include "TextTrackList.h" |
| #include "TimeRanges.h" |
| #include "VideoTrack.h" |
| #include "VideoTrackList.h" |
| #include <wtf/IsoMallocInlines.h> |
| |
| namespace WebCore { |
| |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaSource); |
| |
| String convertEnumerationToString(MediaSourcePrivate::AddStatus enumerationValue) |
| { |
| static const NeverDestroyed<String> values[] = { |
| MAKE_STATIC_STRING_IMPL("Ok"), |
| MAKE_STATIC_STRING_IMPL("NotSupported"), |
| MAKE_STATIC_STRING_IMPL("ReachedIdLimit"), |
| }; |
| static_assert(static_cast<size_t>(MediaSourcePrivate::AddStatus::Ok) == 0, "MediaSourcePrivate::AddStatus::Ok is not 0 as expected"); |
| static_assert(static_cast<size_t>(MediaSourcePrivate::AddStatus::NotSupported) == 1, "MediaSourcePrivate::AddStatus::NotSupported is not 1 as expected"); |
| static_assert(static_cast<size_t>(MediaSourcePrivate::AddStatus::ReachedIdLimit) == 2, "MediaSourcePrivate::AddStatus::ReachedIdLimit is not 2 as expected"); |
| ASSERT(static_cast<size_t>(enumerationValue) < WTF_ARRAY_LENGTH(values)); |
| return values[static_cast<size_t>(enumerationValue)]; |
| } |
| |
| String convertEnumerationToString(MediaSourcePrivate::EndOfStreamStatus enumerationValue) |
| { |
| static const NeverDestroyed<String> values[] = { |
| MAKE_STATIC_STRING_IMPL("EosNoError"), |
| MAKE_STATIC_STRING_IMPL("EosNetworkError"), |
| MAKE_STATIC_STRING_IMPL("EosDecodeError"), |
| }; |
| static_assert(static_cast<size_t>(MediaSourcePrivate::EndOfStreamStatus::EosNoError) == 0, "MediaSourcePrivate::EndOfStreamStatus::EosNoError is not 0 as expected"); |
| static_assert(static_cast<size_t>(MediaSourcePrivate::EndOfStreamStatus::EosNetworkError) == 1, "MediaSourcePrivate::EndOfStreamStatus::EosNetworkError is not 1 as expected"); |
| static_assert(static_cast<size_t>(MediaSourcePrivate::EndOfStreamStatus::EosDecodeError) == 2, "MediaSourcePrivate::EndOfStreamStatus::EosDecodeError is not 2 as expected"); |
| ASSERT(static_cast<size_t>(enumerationValue) < WTF_ARRAY_LENGTH(values)); |
| return values[static_cast<size_t>(enumerationValue)]; |
| } |
| |
| URLRegistry* MediaSource::s_registry; |
| |
| void MediaSource::setRegistry(URLRegistry* registry) |
| { |
| ASSERT(!s_registry); |
| s_registry = registry; |
| } |
| |
| Ref<MediaSource> MediaSource::create(ScriptExecutionContext& context) |
| { |
| auto mediaSource = adoptRef(*new MediaSource(context)); |
| mediaSource->suspendIfNeeded(); |
| return mediaSource; |
| } |
| |
| MediaSource::MediaSource(ScriptExecutionContext& context) |
| : ActiveDOMObject(&context) |
| , m_duration(MediaTime::invalidTime()) |
| , m_pendingSeekTime(MediaTime::invalidTime()) |
| #if !RELEASE_LOG_DISABLED |
| , m_logger(downcast<Document>(context).logger()) |
| #endif |
| { |
| m_sourceBuffers = SourceBufferList::create(scriptExecutionContext()); |
| m_activeSourceBuffers = SourceBufferList::create(scriptExecutionContext()); |
| m_buffered = makeUnique<PlatformTimeRanges>(); |
| } |
| |
| MediaSource::~MediaSource() |
| { |
| ALWAYS_LOG(LOGIDENTIFIER); |
| ASSERT(isClosed()); |
| } |
| |
| void MediaSource::setPrivateAndOpen(Ref<MediaSourcePrivate>&& mediaSourcePrivate) |
| { |
| DEBUG_LOG(LOGIDENTIFIER); |
| ASSERT(!m_private); |
| ASSERT(m_mediaElement); |
| m_private = WTFMove(mediaSourcePrivate); |
| m_private->setTimeFudgeFactor(currentTimeFudgeFactor()); |
| |
| // 2.4.1 Attaching to a media element |
| // https://rawgit.com/w3c/media-source/45627646344eea0170dd1cbc5a3d508ca751abb8/media-source-respec.html#mediasource-attach |
| |
| // ↳ If readyState is NOT set to "closed" |
| // Run the "If the media data cannot be fetched at all, due to network errors, causing the user agent to give up trying |
| // to fetch the resource" steps of the resource fetch algorithm's media data processing steps list. |
| if (!isClosed()) { |
| m_mediaElement->mediaLoadingFailedFatally(MediaPlayer::NetworkState::NetworkError); |
| return; |
| } |
| |
| // ↳ Otherwise |
| // 1. Set the media element's delaying-the-load-event-flag to false. |
| m_mediaElement->setShouldDelayLoadEvent(false); |
| |
| // 2. Set the readyState attribute to "open". |
| // 3. Queue a task to fire a simple event named sourceopen at the MediaSource. |
| setReadyState(ReadyState::Open); |
| |
| // 4. Continue the resource fetch algorithm by running the remaining "Otherwise (mode is local)" steps, |
| // with these clarifications: |
| // NOTE: This is handled in HTMLMediaElement. |
| } |
| |
| void MediaSource::addedToRegistry() |
| { |
| DEBUG_LOG(LOGIDENTIFIER); |
| ++m_associatedRegistryCount; |
| } |
| |
| void MediaSource::removedFromRegistry() |
| { |
| DEBUG_LOG(LOGIDENTIFIER); |
| ASSERT(m_associatedRegistryCount); |
| --m_associatedRegistryCount; |
| } |
| |
| MediaTime MediaSource::duration() const |
| { |
| return m_duration; |
| } |
| |
| MediaTime MediaSource::currentTime() const |
| { |
| return m_mediaElement ? m_mediaElement->currentMediaTime() : MediaTime::zeroTime(); |
| } |
| |
| std::unique_ptr<PlatformTimeRanges> MediaSource::buffered() const |
| { |
| return makeUnique<PlatformTimeRanges>(*m_buffered); |
| } |
| |
| void MediaSource::seekToTime(const MediaTime& time) |
| { |
| if (isClosed()) |
| return; |
| |
| ALWAYS_LOG(LOGIDENTIFIER, time); |
| |
| // 2.4.3 Seeking |
| // https://rawgit.com/w3c/media-source/45627646344eea0170dd1cbc5a3d508ca751abb8/media-source-respec.html#mediasource-seeking |
| |
| m_pendingSeekTime = time; |
| m_private->setIsSeeking(true); |
| |
| // Run the following steps as part of the "Wait until the user agent has established whether or not the |
| // media data for the new playback position is available, and, if it is, until it has decoded enough data |
| // to play back that position" step of the seek algorithm: |
| // ↳ If new playback position is not in any TimeRange of HTMLMediaElement.buffered |
| if (!hasBufferedTime(time)) { |
| // 1. If the HTMLMediaElement.readyState attribute is greater than HAVE_METADATA, |
| // then set the HTMLMediaElement.readyState attribute to HAVE_METADATA. |
| m_private->setReadyState(MediaPlayer::ReadyState::HaveMetadata); |
| |
| // 2. The media element waits until an appendBuffer() or an appendStream() call causes the coded |
| // frame processing algorithm to set the HTMLMediaElement.readyState attribute to a value greater |
| // than HAVE_METADATA. |
| m_private->waitForSeekCompleted(); |
| return; |
| } |
| // ↳ Otherwise |
| // Continue |
| |
| // https://bugs.webkit.org/show_bug.cgi?id=125157 broke seek on MediaPlayerPrivateGStreamerMSE |
| #if !USE(GSTREAMER) |
| m_private->waitForSeekCompleted(); |
| #endif |
| completeSeek(); |
| } |
| |
| void MediaSource::completeSeek() |
| { |
| if (isClosed()) |
| return; |
| |
| // 2.4.3 Seeking, ctd. |
| // https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#mediasource-seeking |
| |
| ASSERT(m_pendingSeekTime.isValid()); |
| |
| ALWAYS_LOG(LOGIDENTIFIER, m_pendingSeekTime); |
| |
| // 2. The media element resets all decoders and initializes each one with data from the appropriate |
| // initialization segment. |
| // 3. The media element feeds coded frames from the active track buffers into the decoders starting |
| // with the closest random access point before the new playback position. |
| MediaTime pendingSeekTime = m_pendingSeekTime; |
| m_pendingSeekTime = MediaTime::invalidTime(); |
| m_private->setIsSeeking(false); |
| for (auto& sourceBuffer : *m_activeSourceBuffers) |
| sourceBuffer->seekToTime(pendingSeekTime); |
| |
| // 4. Resume the seek algorithm at the "Await a stable state" step. |
| m_private->seekCompleted(); |
| |
| monitorSourceBuffers(); |
| } |
| |
| Ref<TimeRanges> MediaSource::seekable() |
| { |
| // 6. HTMLMediaElement Extensions, seekable |
| // W3C Editor's Draft 16 September 2016 |
| // https://rawgit.com/w3c/media-source/45627646344eea0170dd1cbc5a3d508ca751abb8/media-source-respec.html#htmlmediaelement-extensions |
| |
| // ↳ If duration equals NaN: |
| // Return an empty TimeRanges object. |
| if (m_duration.isInvalid()) |
| return TimeRanges::create(); |
| |
| // ↳ If duration equals positive Infinity: |
| if (m_duration.isPositiveInfinite()) { |
| auto buffered = this->buffered(); |
| // If live seekable range is not empty: |
| if (m_liveSeekable && m_liveSeekable->length()) { |
| // Let union ranges be the union of live seekable range and the HTMLMediaElement.buffered attribute. |
| buffered->unionWith(*m_liveSeekable); |
| // Return a single range with a start time equal to the earliest start time in union ranges |
| // and an end time equal to the highest end time in union ranges and abort these steps. |
| buffered->add(buffered->start(0), buffered->maximumBufferedTime()); |
| return TimeRanges::create(*buffered); |
| } |
| |
| // If the HTMLMediaElement.buffered attribute returns an empty TimeRanges object, then return |
| // an empty TimeRanges object and abort these steps. |
| if (!buffered->length()) |
| return TimeRanges::create(); |
| |
| // Return a single range with a start time of 0 and an end time equal to the highest end time |
| // reported by the HTMLMediaElement.buffered attribute. |
| return TimeRanges::create({MediaTime::zeroTime(), buffered->maximumBufferedTime()}); |
| } |
| |
| // ↳ Otherwise: |
| // Return a single range with a start time of 0 and an end time equal to duration. |
| return TimeRanges::create({MediaTime::zeroTime(), m_duration}); |
| } |
| |
| ExceptionOr<void> MediaSource::setLiveSeekableRange(double start, double end) |
| { |
| // W3C Editor's Draft 16 September 2016 |
| // https://rawgit.com/w3c/media-source/45627646344eea0170dd1cbc5a3d508ca751abb8/media-source-respec.html#dom-mediasource-setliveseekablerange |
| |
| ALWAYS_LOG(LOGIDENTIFIER, "start = ", start, ", end = ", end); |
| |
| // If the readyState attribute is not "open" then throw an InvalidStateError exception and abort these steps. |
| if (!isOpen()) |
| return Exception { InvalidStateError }; |
| |
| // If start is negative or greater than end, then throw a TypeError exception and abort these steps. |
| if (start < 0 || start > end) |
| return Exception { TypeError }; |
| |
| // Set live seekable range to be a new normalized TimeRanges object containing a single range |
| // whose start position is start and end position is end. |
| m_liveSeekable = makeUnique<PlatformTimeRanges>(MediaTime::createWithDouble(start), MediaTime::createWithDouble(end)); |
| |
| return { }; |
| } |
| |
| ExceptionOr<void> MediaSource::clearLiveSeekableRange() |
| { |
| // W3C Editor's Draft 16 September 2016 |
| // https://rawgit.com/w3c/media-source/45627646344eea0170dd1cbc5a3d508ca751abb8/media-source-respec.html#dom-mediasource-clearliveseekablerange |
| |
| ALWAYS_LOG(LOGIDENTIFIER); |
| |
| // If the readyState attribute is not "open" then throw an InvalidStateError exception and abort these steps. |
| if (!isOpen()) |
| return Exception { InvalidStateError }; |
| m_liveSeekable = nullptr; |
| return { }; |
| } |
| |
| const MediaTime& MediaSource::currentTimeFudgeFactor() |
| { |
| // Allow hasCurrentTime() to be off by as much as the length of two 24fps video frames |
| static NeverDestroyed<MediaTime> fudgeFactor(2002, 24000); |
| return fudgeFactor; |
| } |
| |
| bool MediaSource::contentTypeShouldGenerateTimestamps(const ContentType& contentType) |
| { |
| return contentType.containerType() == "audio/aac" || contentType.containerType() == "audio/mpeg"; |
| } |
| |
| bool MediaSource::hasBufferedTime(const MediaTime& time) |
| { |
| if (time > duration()) |
| return false; |
| |
| auto ranges = buffered(); |
| if (!ranges->length()) |
| return false; |
| |
| return abs(ranges->nearest(time) - time) <= currentTimeFudgeFactor(); |
| } |
| |
| bool MediaSource::hasCurrentTime() |
| { |
| return hasBufferedTime(currentTime()); |
| } |
| |
| bool MediaSource::hasFutureTime() |
| { |
| MediaTime currentTime = this->currentTime(); |
| MediaTime duration = this->duration(); |
| |
| if (currentTime >= duration) |
| return true; |
| |
| auto ranges = buffered(); |
| MediaTime nearest = ranges->nearest(currentTime); |
| if (abs(nearest - currentTime) > currentTimeFudgeFactor()) |
| return false; |
| |
| size_t found = ranges->find(nearest); |
| if (found == notFound) |
| return false; |
| |
| MediaTime localEnd = ranges->end(found); |
| if (localEnd == duration) |
| return true; |
| |
| return localEnd - currentTime > currentTimeFudgeFactor(); |
| } |
| |
| void MediaSource::monitorSourceBuffers() |
| { |
| if (isClosed()) |
| return; |
| |
| // 2.4.4 SourceBuffer Monitoring |
| // https://rawgit.com/w3c/media-source/45627646344eea0170dd1cbc5a3d508ca751abb8/media-source-respec.html#buffer-monitoring |
| |
| // Note, the behavior if activeSourceBuffers is empty is undefined. |
| if (!m_activeSourceBuffers) { |
| m_private->setReadyState(MediaPlayer::ReadyState::HaveNothing); |
| return; |
| } |
| |
| // ↳ If the HTMLMediaElement.readyState attribute equals HAVE_NOTHING: |
| if (mediaElement()->readyState() == HTMLMediaElement::HAVE_NOTHING) { |
| // 1. Abort these steps. |
| return; |
| } |
| |
| // ↳ If HTMLMediaElement.buffered does not contain a TimeRange for the current playback position: |
| if (!hasCurrentTime()) { |
| // 1. Set the HTMLMediaElement.readyState attribute to HAVE_METADATA. |
| // 2. If this is the first transition to HAVE_METADATA, then queue a task to fire a simple event |
| // named loadedmetadata at the media element. |
| m_private->setReadyState(MediaPlayer::ReadyState::HaveMetadata); |
| |
| // 3. Abort these steps. |
| return; |
| } |
| |
| // ↳ If HTMLMediaElement.buffered contains a TimeRange that includes the current |
| // playback position and enough data to ensure uninterrupted playback: |
| auto ranges = buffered(); |
| if (std::all_of(m_activeSourceBuffers->begin(), m_activeSourceBuffers->end(), [&](auto& sourceBuffer) { |
| return sourceBuffer->canPlayThroughRange(*ranges); |
| })) { |
| // 1. Set the HTMLMediaElement.readyState attribute to HAVE_ENOUGH_DATA. |
| // 2. Queue a task to fire a simple event named canplaythrough at the media element. |
| // 3. Playback may resume at this point if it was previously suspended by a transition to HAVE_CURRENT_DATA. |
| m_private->setReadyState(MediaPlayer::ReadyState::HaveEnoughData); |
| |
| if (m_pendingSeekTime.isValid()) |
| completeSeek(); |
| |
| // 4. Abort these steps. |
| return; |
| } |
| |
| // ↳ If HTMLMediaElement.buffered contains a TimeRange that includes the current playback |
| // position and some time beyond the current playback position, then run the following steps: |
| if (hasFutureTime()) { |
| // 1. Set the HTMLMediaElement.readyState attribute to HAVE_FUTURE_DATA. |
| // 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. |
| // 3. Playback may resume at this point if it was previously suspended by a transition to HAVE_CURRENT_DATA. |
| m_private->setReadyState(MediaPlayer::ReadyState::HaveFutureData); |
| |
| if (m_pendingSeekTime.isValid()) |
| completeSeek(); |
| |
| // 4. Abort these steps. |
| return; |
| } |
| |
| // ↳ If HTMLMediaElement.buffered contains a TimeRange that ends at the current playback position and does not have a range covering the time immediately after the current position: |
| // NOTE: Logically, !(all objects do not contain currentTime) == (some objects contain current time) |
| |
| // 1. Set the HTMLMediaElement.readyState attribute to HAVE_CURRENT_DATA. |
| // 2. If this is the first transition to HAVE_CURRENT_DATA, then queue a task to fire a simple |
| // event named loadeddata at the media element. |
| // 3. Playback is suspended at this point since the media element doesn't have enough data to |
| // advance the media timeline. |
| m_private->setReadyState(MediaPlayer::ReadyState::HaveCurrentData); |
| |
| if (m_pendingSeekTime.isValid()) |
| completeSeek(); |
| |
| // 4. Abort these steps. |
| } |
| |
| ExceptionOr<void> MediaSource::setDuration(double duration) |
| { |
| // 2.1 Attributes - Duration |
| // https://www.w3.org/TR/2016/REC-media-source-20161117/#attributes |
| |
| ALWAYS_LOG(LOGIDENTIFIER, duration); |
| |
| // On setting, run the following steps: |
| // 1. If the value being set is negative or NaN then throw a TypeError exception and abort these steps. |
| if (duration < 0.0 || std::isnan(duration)) |
| return Exception { TypeError }; |
| |
| // 2. If the readyState attribute is not "open" then throw an InvalidStateError exception and abort these steps. |
| if (!isOpen()) |
| return Exception { InvalidStateError }; |
| |
| // 3. If the updating attribute equals true on any SourceBuffer in sourceBuffers, then throw an InvalidStateError |
| // exception and abort these steps. |
| for (auto& sourceBuffer : *m_sourceBuffers) { |
| if (sourceBuffer->updating()) |
| return Exception { InvalidStateError }; |
| } |
| |
| // 4. Run the duration change algorithm with new duration set to the value being assigned to this attribute. |
| return setDurationInternal(MediaTime::createWithDouble(duration)); |
| } |
| |
| ExceptionOr<void> MediaSource::setDurationInternal(const MediaTime& duration) |
| { |
| // 2.4.6 Duration Change |
| // https://www.w3.org/TR/2016/REC-media-source-20161117/#duration-change-algorithm |
| |
| MediaTime newDuration = duration; |
| |
| // 1. If the current value of duration is equal to new duration, then return. |
| if (newDuration == m_duration) |
| return { }; |
| |
| // 2. If new duration is less than the highest presentation timestamp of any buffered coded frames |
| // for all SourceBuffer objects in sourceBuffers, then throw an InvalidStateError exception and |
| // abort these steps. |
| // 3. Let highest end time be the largest track buffer ranges end time across all the track buffers |
| // across all SourceBuffer objects in sourceBuffers. |
| MediaTime highestPresentationTimestamp; |
| MediaTime highestEndTime; |
| for (auto& sourceBuffer : *m_sourceBuffers) { |
| highestPresentationTimestamp = std::max(highestPresentationTimestamp, sourceBuffer->highestPresentationTimestamp()); |
| highestEndTime = std::max(highestEndTime, sourceBuffer->bufferedInternal().ranges().maximumBufferedTime()); |
| } |
| if (highestPresentationTimestamp.isValid() && newDuration < highestPresentationTimestamp) |
| return Exception { InvalidStateError }; |
| |
| // 4. If new duration is less than highest end time, then |
| // 4.1. Update new duration to equal highest end time. |
| if (highestEndTime.isValid() && newDuration < highestEndTime) |
| newDuration = highestEndTime; |
| |
| // 5. Update duration to new duration. |
| m_duration = newDuration; |
| ALWAYS_LOG(LOGIDENTIFIER, duration); |
| |
| // 6. Update the media duration to new duration and run the HTMLMediaElement duration change algorithm. |
| m_private->durationChanged(duration); |
| |
| return { }; |
| } |
| |
| void MediaSource::setReadyState(ReadyState state) |
| { |
| auto oldState = readyState(); |
| if (oldState == state) |
| return; |
| |
| m_readyState = state; |
| |
| onReadyStateChange(oldState, state); |
| } |
| |
| ExceptionOr<void> MediaSource::endOfStream(std::optional<EndOfStreamError> error) |
| { |
| ALWAYS_LOG(LOGIDENTIFIER); |
| |
| // 2.2 https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#widl-MediaSource-endOfStream-void-EndOfStreamError-error |
| // 1. If the readyState attribute is not in the "open" state then throw an |
| // InvalidStateError exception and abort these steps. |
| if (!isOpen()) |
| return Exception { InvalidStateError }; |
| |
| // 2. If the updating attribute equals true on any SourceBuffer in sourceBuffers, then throw an |
| // InvalidStateError exception and abort these steps. |
| if (std::any_of(m_sourceBuffers->begin(), m_sourceBuffers->end(), [](auto& sourceBuffer) { return sourceBuffer->updating(); })) |
| return Exception { InvalidStateError }; |
| |
| // 3. Run the end of stream algorithm with the error parameter set to error. |
| streamEndedWithError(error); |
| |
| return { }; |
| } |
| |
| void MediaSource::streamEndedWithError(std::optional<EndOfStreamError> error) |
| { |
| #if !RELEASE_LOG_DISABLED |
| if (error) |
| ALWAYS_LOG(LOGIDENTIFIER, error.value()); |
| else |
| ALWAYS_LOG(LOGIDENTIFIER); |
| #endif |
| |
| if (isClosed()) |
| return; |
| |
| // 2.4.7 https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#end-of-stream-algorithm |
| |
| // 1. Change the readyState attribute value to "ended". |
| // 2. Queue a task to fire a simple event named sourceended at the MediaSource. |
| setReadyState(ReadyState::Ended); |
| |
| // 3. |
| if (!error) { |
| // ↳ If error is not set, is null, or is an empty string |
| // 1. Run the duration change algorithm with new duration set to the highest end time reported by |
| // the buffered attribute across all SourceBuffer objects in sourceBuffers. |
| MediaTime maxEndTime; |
| for (auto& sourceBuffer : *m_sourceBuffers) { |
| if (auto length = sourceBuffer->bufferedInternal().length()) |
| maxEndTime = std::max(sourceBuffer->bufferedInternal().ranges().end(length - 1), maxEndTime); |
| } |
| setDurationInternal(maxEndTime); |
| |
| // 2. Notify the media element that it now has all of the media data. |
| for (auto& sourceBuffer : *m_sourceBuffers) |
| sourceBuffer->setMediaSourceEnded(true); |
| m_private->markEndOfStream(MediaSourcePrivate::EosNoError); |
| } else if (error == EndOfStreamError::Network) { |
| m_private->markEndOfStream(MediaSourcePrivate::EosNetworkError); |
| // ↳ If error is set to "network" |
| ASSERT(m_mediaElement); |
| if (m_mediaElement->readyState() == HTMLMediaElement::HAVE_NOTHING) { |
| // ↳ If the HTMLMediaElement.readyState attribute equals HAVE_NOTHING |
| // Run the "If the media data cannot be fetched at all, due to network errors, causing |
| // the user agent to give up trying to fetch the resource" steps of the resource fetch algorithm. |
| // NOTE: This step is handled by HTMLMediaElement::mediaLoadingFailed(). |
| m_mediaElement->mediaLoadingFailed(MediaPlayer::NetworkState::NetworkError); |
| } else { |
| // ↳ If the HTMLMediaElement.readyState attribute is greater than HAVE_NOTHING |
| // Run the "If the connection is interrupted after some media data has been received, causing the |
| // user agent to give up trying to fetch the resource" steps of the resource fetch algorithm. |
| // NOTE: This step is handled by HTMLMediaElement::mediaLoadingFailedFatally(). |
| m_mediaElement->mediaLoadingFailedFatally(MediaPlayer::NetworkState::NetworkError); |
| } |
| } else { |
| // ↳ If error is set to "decode" |
| ASSERT(error == EndOfStreamError::Decode); |
| m_private->markEndOfStream(MediaSourcePrivate::EosDecodeError); |
| |
| ASSERT(m_mediaElement); |
| if (m_mediaElement->readyState() == HTMLMediaElement::HAVE_NOTHING) { |
| // ↳ If the HTMLMediaElement.readyState attribute equals HAVE_NOTHING |
| // Run the "If the media data can be fetched but is found by inspection to be in an unsupported |
| // format, or can otherwise not be rendered at all" steps of the resource fetch algorithm. |
| // NOTE: This step is handled by HTMLMediaElement::mediaLoadingFailed(). |
| m_mediaElement->mediaLoadingFailed(MediaPlayer::NetworkState::FormatError); |
| } else { |
| // ↳ If the HTMLMediaElement.readyState attribute is greater than HAVE_NOTHING |
| // Run the media data is corrupted steps of the resource fetch algorithm. |
| // NOTE: This step is handled by HTMLMediaElement::mediaLoadingFailedFatally(). |
| m_mediaElement->mediaLoadingFailedFatally(MediaPlayer::NetworkState::DecodeError); |
| } |
| } |
| } |
| |
| static ContentType addVP9FullRangeVideoFlagToContentType(const ContentType& type) |
| { |
| auto countPeriods = [] (const String& codec) { |
| unsigned count = 0; |
| unsigned position = 0; |
| |
| while (codec.find('.', position) != notFound) { |
| ++count; |
| ++position; |
| } |
| |
| return count; |
| }; |
| |
| for (auto codec : type.codecs()) { |
| if (!codec.startsWith("vp09") || countPeriods(codec) != 7) |
| continue; |
| |
| auto rawType = type.raw(); |
| auto position = rawType.find(codec); |
| ASSERT(position != notFound); |
| if (position == notFound) |
| continue; |
| |
| rawType.insert(".00", position + codec.length()); |
| return ContentType(rawType); |
| } |
| return type; |
| } |
| |
| ExceptionOr<Ref<SourceBuffer>> MediaSource::addSourceBuffer(const String& type) |
| { |
| DEBUG_LOG(LOGIDENTIFIER, type); |
| |
| // 2.2 http://www.w3.org/TR/media-source/#widl-MediaSource-addSourceBuffer-SourceBuffer-DOMString-type |
| // When this method is invoked, the user agent must run the following steps: |
| |
| // 1. If type is an empty string then throw a TypeError exception and abort these steps. |
| if (type.isEmpty()) |
| return Exception { TypeError }; |
| |
| // 2. If type contains a MIME type that is not supported ..., then throw a |
| // NotSupportedError exception and abort these steps. |
| Vector<ContentType> mediaContentTypesRequiringHardwareSupport; |
| if (m_mediaElement) |
| mediaContentTypesRequiringHardwareSupport.appendVector(m_mediaElement->document().settings().mediaContentTypesRequiringHardwareSupport()); |
| |
| auto context = scriptExecutionContext(); |
| if (!context) |
| return Exception { NotAllowedError }; |
| |
| if (!isTypeSupported(*context, type, WTFMove(mediaContentTypesRequiringHardwareSupport))) |
| return Exception { NotSupportedError }; |
| |
| // 4. If the readyState attribute is not in the "open" state then throw an |
| // InvalidStateError exception and abort these steps. |
| if (!isOpen()) |
| return Exception { InvalidStateError }; |
| |
| // 5. Create a new SourceBuffer object and associated resources. |
| ContentType contentType(type); |
| if (context->isDocument() && downcast<Document>(context)->quirks().needsVP9FullRangeFlagQuirk()) |
| contentType = addVP9FullRangeVideoFlagToContentType(contentType); |
| |
| auto sourceBufferPrivate = createSourceBufferPrivate(contentType); |
| |
| if (sourceBufferPrivate.hasException()) { |
| // 2. If type contains a MIME type that is not supported ..., then throw a NotSupportedError exception and abort these steps. |
| // 3. If the user agent can't handle any more SourceBuffer objects then throw a QuotaExceededError exception and abort these steps |
| return sourceBufferPrivate.releaseException(); |
| } |
| |
| auto buffer = SourceBuffer::create(sourceBufferPrivate.releaseReturnValue(), this); |
| DEBUG_LOG(LOGIDENTIFIER, "created SourceBuffer"); |
| |
| // 6. Set the generate timestamps flag on the new object to the value in the "Generate Timestamps Flag" |
| // column of the byte stream format registry [MSE-REGISTRY] entry that is associated with type. |
| // NOTE: In the current byte stream format registry <http://www.w3.org/2013/12/byte-stream-format-registry/> |
| // only the "MPEG Audio Byte Stream Format" has the "Generate Timestamps Flag" value set. |
| bool shouldGenerateTimestamps = contentTypeShouldGenerateTimestamps(contentType); |
| buffer->setShouldGenerateTimestamps(shouldGenerateTimestamps); |
| |
| // 7. If the generate timestamps flag equals true: |
| // ↳ Set the mode attribute on the new object to "sequence". |
| // Otherwise: |
| // ↳ Set the mode attribute on the new object to "segments". |
| buffer->setMode(shouldGenerateTimestamps ? SourceBuffer::AppendMode::Sequence : SourceBuffer::AppendMode::Segments); |
| |
| // 8. Add the new object to sourceBuffers and fire a addsourcebuffer on that object. |
| m_sourceBuffers->add(buffer.copyRef()); |
| regenerateActiveSourceBuffers(); |
| |
| // 9. Return the new object to the caller. |
| return buffer; |
| } |
| |
| ExceptionOr<void> MediaSource::removeSourceBuffer(SourceBuffer& buffer) |
| { |
| DEBUG_LOG(LOGIDENTIFIER); |
| |
| Ref<SourceBuffer> protect(buffer); |
| |
| // 2. If sourceBuffer specifies an object that is not in sourceBuffers then |
| // throw a NotFoundError exception and abort these steps. |
| if (!m_sourceBuffers->length() || !m_sourceBuffers->contains(buffer)) |
| return Exception { NotFoundError }; |
| |
| // 3. If the sourceBuffer.updating attribute equals true, then run the following steps: ... |
| buffer.abortIfUpdating(); |
| |
| ASSERT(scriptExecutionContext()); |
| if (!scriptExecutionContext()->activeDOMObjectsAreStopped()) { |
| // 4. Let SourceBuffer audioTracks list equal the AudioTrackList object returned by sourceBuffer.audioTracks. |
| auto* audioTracks = buffer.audioTracksIfExists(); |
| |
| // 5. If the SourceBuffer audioTracks list is not empty, then run the following steps: |
| if (audioTracks && audioTracks->length()) { |
| // 5.1 Let HTMLMediaElement audioTracks list equal the AudioTrackList object returned by the audioTracks |
| // attribute on the HTMLMediaElement. |
| // 5.2 Let the removed enabled audio track flag equal false. |
| bool removedEnabledAudioTrack = false; |
| |
| // 5.3 For each AudioTrack object in the SourceBuffer audioTracks list, run the following steps: |
| while (audioTracks->length()) { |
| auto& track = *audioTracks->lastItem(); |
| |
| // 5.3.1 Set the sourceBuffer attribute on the AudioTrack object to null. |
| track.setSourceBuffer(nullptr); |
| |
| // 5.3.2 If the enabled attribute on the AudioTrack object is true, then set the removed enabled |
| // audio track flag to true. |
| if (track.enabled()) |
| removedEnabledAudioTrack = true; |
| |
| // 5.3.3 Remove the AudioTrack object from the HTMLMediaElement audioTracks list. |
| // 5.3.4 Queue a task to fire a trusted event named removetrack, that does not bubble and is not |
| // cancelable, and that uses the TrackEvent interface, at the HTMLMediaElement audioTracks list. |
| if (mediaElement()) |
| mediaElement()->removeAudioTrack(track); |
| |
| // 5.3.5 Remove the AudioTrack object from the SourceBuffer audioTracks list. |
| // 5.3.6 Queue a task to fire a trusted event named removetrack, that does not bubble and is not |
| // cancelable, and that uses the TrackEvent interface, at the SourceBuffer audioTracks list. |
| audioTracks->remove(track); |
| } |
| |
| // 5.4 If the removed enabled audio track flag equals true, then queue a task to fire a simple event |
| // named change at the HTMLMediaElement audioTracks list. |
| if (removedEnabledAudioTrack) |
| mediaElement()->ensureAudioTracks().scheduleChangeEvent(); |
| } |
| |
| // 6. Let SourceBuffer videoTracks list equal the VideoTrackList object returned by sourceBuffer.videoTracks. |
| auto* videoTracks = buffer.videoTracksIfExists(); |
| |
| // 7. If the SourceBuffer videoTracks list is not empty, then run the following steps: |
| if (videoTracks && videoTracks->length()) { |
| // 7.1 Let HTMLMediaElement videoTracks list equal the VideoTrackList object returned by the videoTracks |
| // attribute on the HTMLMediaElement. |
| // 7.2 Let the removed selected video track flag equal false. |
| bool removedSelectedVideoTrack = false; |
| |
| // 7.3 For each VideoTrack object in the SourceBuffer videoTracks list, run the following steps: |
| while (videoTracks->length()) { |
| auto& track = *videoTracks->lastItem(); |
| |
| // 7.3.1 Set the sourceBuffer attribute on the VideoTrack object to null. |
| track.setSourceBuffer(nullptr); |
| |
| // 7.3.2 If the selected attribute on the VideoTrack object is true, then set the removed selected |
| // video track flag to true. |
| if (track.selected()) |
| removedSelectedVideoTrack = true; |
| |
| // 7.3.3 Remove the VideoTrack object from the HTMLMediaElement videoTracks list. |
| // 7.3.4 Queue a task to fire a trusted event named removetrack, that does not bubble and is not |
| // cancelable, and that uses the TrackEvent interface, at the HTMLMediaElement videoTracks list. |
| if (mediaElement()) |
| mediaElement()->removeVideoTrack(track); |
| |
| // 7.3.5 Remove the VideoTrack object from the SourceBuffer videoTracks list. |
| // 7.3.6 Queue a task to fire a trusted event named removetrack, that does not bubble and is not |
| // cancelable, and that uses the TrackEvent interface, at the SourceBuffer videoTracks list. |
| videoTracks->remove(track); |
| } |
| |
| // 7.4 If the removed selected video track flag equals true, then queue a task to fire a simple event |
| // named change at the HTMLMediaElement videoTracks list. |
| if (removedSelectedVideoTrack) |
| mediaElement()->ensureVideoTracks().scheduleChangeEvent(); |
| } |
| |
| // 8. Let SourceBuffer textTracks list equal the TextTrackList object returned by sourceBuffer.textTracks. |
| auto* textTracks = buffer.textTracksIfExists(); |
| |
| // 9. If the SourceBuffer textTracks list is not empty, then run the following steps: |
| if (textTracks && textTracks->length()) { |
| // 9.1 Let HTMLMediaElement textTracks list equal the TextTrackList object returned by the textTracks |
| // attribute on the HTMLMediaElement. |
| // 9.2 Let the removed enabled text track flag equal false. |
| bool removedEnabledTextTrack = false; |
| |
| // 9.3 For each TextTrack object in the SourceBuffer textTracks list, run the following steps: |
| while (textTracks->length()) { |
| auto& track = *textTracks->lastItem(); |
| |
| // 9.3.1 Set the sourceBuffer attribute on the TextTrack object to null. |
| track.setSourceBuffer(nullptr); |
| |
| // 9.3.2 If the mode attribute on the TextTrack object is set to "showing" or "hidden", then |
| // set the removed enabled text track flag to true. |
| if (track.mode() == TextTrack::Mode::Showing || track.mode() == TextTrack::Mode::Hidden) |
| removedEnabledTextTrack = true; |
| |
| // 9.3.3 Remove the TextTrack object from the HTMLMediaElement textTracks list. |
| // 9.3.4 Queue a task to fire a trusted event named removetrack, that does not bubble and is not |
| // cancelable, and that uses the TrackEvent interface, at the HTMLMediaElement textTracks list. |
| if (mediaElement()) |
| mediaElement()->removeTextTrack(track); |
| |
| // 9.3.5 Remove the TextTrack object from the SourceBuffer textTracks list. |
| // 9.3.6 Queue a task to fire a trusted event named removetrack, that does not bubble and is not |
| // cancelable, and that uses the TrackEvent interface, at the SourceBuffer textTracks list. |
| textTracks->remove(track); |
| } |
| |
| // 9.4 If the removed enabled text track flag equals true, then queue a task to fire a simple event |
| // named change at the HTMLMediaElement textTracks list. |
| if (removedEnabledTextTrack) |
| mediaElement()->ensureTextTracks().scheduleChangeEvent(); |
| } |
| } |
| |
| // 10. If sourceBuffer is in activeSourceBuffers, then remove sourceBuffer from activeSourceBuffers ... |
| m_activeSourceBuffers->remove(buffer); |
| |
| // 11. Remove sourceBuffer from sourceBuffers and fire a removesourcebuffer event |
| // on that object. |
| m_sourceBuffers->remove(buffer); |
| |
| // 12. Destroy all resources for sourceBuffer. |
| buffer.removedFromMediaSource(); |
| |
| return { }; |
| } |
| |
| bool MediaSource::isTypeSupported(ScriptExecutionContext& context, const String& type) |
| { |
| Vector<ContentType> mediaContentTypesRequiringHardwareSupport; |
| if (context.isDocument()) { |
| auto& document = downcast<Document>(context); |
| mediaContentTypesRequiringHardwareSupport.appendVector(document.settings().mediaContentTypesRequiringHardwareSupport()); |
| } |
| |
| return isTypeSupported(context, type, WTFMove(mediaContentTypesRequiringHardwareSupport)); |
| } |
| |
| bool MediaSource::isTypeSupported(ScriptExecutionContext& context, const String& type, Vector<ContentType>&& contentTypesRequiringHardwareSupport) |
| { |
| // Section 2.2 isTypeSupported() method steps. |
| // https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#widl-MediaSource-isTypeSupported-boolean-DOMString-type |
| // 1. If type is an empty string, then return false. |
| if (type.isNull() || type.isEmpty()) |
| return false; |
| |
| ContentType contentType(type); |
| if (context.isDocument() && downcast<Document>(context).quirks().needsVP9FullRangeFlagQuirk()) |
| contentType = addVP9FullRangeVideoFlagToContentType(contentType); |
| |
| String codecs = contentType.parameter("codecs"); |
| |
| // 2. If type does not contain a valid MIME type string, then return false. |
| if (contentType.containerType().isEmpty()) |
| return false; |
| |
| // 3. If type contains a media type or media subtype that the MediaSource does not support, then return false. |
| // 4. If type contains at a codec that the MediaSource does not support, then return false. |
| // 5. If the MediaSource does not support the specified combination of media type, media subtype, and codecs then return false. |
| // 6. Return true. |
| MediaEngineSupportParameters parameters; |
| parameters.type = contentType; |
| parameters.isMediaSource = true; |
| parameters.contentTypesRequiringHardwareSupport = WTFMove(contentTypesRequiringHardwareSupport); |
| |
| MediaPlayer::SupportsType supported = MediaPlayer::supportsType(parameters); |
| |
| if (codecs.isEmpty()) |
| return supported != MediaPlayer::SupportsType::IsNotSupported; |
| |
| return supported == MediaPlayer::SupportsType::IsSupported; |
| } |
| |
| bool MediaSource::isOpen() const |
| { |
| return readyState() == ReadyState::Open; |
| } |
| |
| bool MediaSource::isClosed() const |
| { |
| return readyState() == ReadyState::Closed; |
| } |
| |
| bool MediaSource::isEnded() const |
| { |
| return readyState() == ReadyState::Ended; |
| } |
| |
| void MediaSource::detachFromElement(HTMLMediaElement& element) |
| { |
| ALWAYS_LOG(LOGIDENTIFIER); |
| |
| ASSERT_UNUSED(element, m_mediaElement == &element); |
| |
| // 2.4.2 Detaching from a media element |
| // https://rawgit.com/w3c/media-source/45627646344eea0170dd1cbc5a3d508ca751abb8/media-source-respec.html#mediasource-detach |
| |
| // 1. Set the readyState attribute to "closed". |
| // 7. Queue a task to fire a simple event named sourceclose at the MediaSource. |
| setReadyState(ReadyState::Closed); |
| |
| // 2. Update duration to NaN. |
| m_duration = MediaTime::invalidTime(); |
| |
| // 3. Remove all the SourceBuffer objects from activeSourceBuffers. |
| // 4. Queue a task to fire a simple event named removesourcebuffer at activeSourceBuffers. |
| while (m_activeSourceBuffers->length()) |
| removeSourceBuffer(*m_activeSourceBuffers->item(0)); |
| |
| // 5. Remove all the SourceBuffer objects from sourceBuffers. |
| // 6. Queue a task to fire a simple event named removesourcebuffer at sourceBuffers. |
| while (m_sourceBuffers->length()) |
| removeSourceBuffer(*m_sourceBuffers->item(0)); |
| |
| m_private = nullptr; |
| m_mediaElement = nullptr; |
| } |
| |
| void MediaSource::sourceBufferDidChangeActiveState(SourceBuffer&, bool) |
| { |
| regenerateActiveSourceBuffers(); |
| } |
| |
| void MediaSource::sourceBufferDidChangeBufferedDirty(SourceBuffer&, bool) |
| { |
| updateBufferedIfNeeded(); |
| } |
| |
| bool MediaSource::attachToElement(HTMLMediaElement& element) |
| { |
| if (m_mediaElement) |
| return false; |
| |
| ASSERT(isClosed()); |
| |
| m_mediaElement = element; |
| return true; |
| } |
| |
| void MediaSource::openIfInEndedState() |
| { |
| if (m_readyState != ReadyState::Ended) |
| return; |
| |
| ALWAYS_LOG(LOGIDENTIFIER); |
| |
| setReadyState(ReadyState::Open); |
| m_private->unmarkEndOfStream(); |
| for (auto& sourceBuffer : *m_sourceBuffers) |
| sourceBuffer->setMediaSourceEnded(false); |
| } |
| |
| bool MediaSource::virtualHasPendingActivity() const |
| { |
| return m_private || m_associatedRegistryCount; |
| } |
| |
| void MediaSource::stop() |
| { |
| ALWAYS_LOG(LOGIDENTIFIER); |
| |
| if (m_mediaElement) |
| m_mediaElement->detachMediaSource(); |
| m_readyState = ReadyState::Closed; |
| m_private = nullptr; |
| } |
| |
| const char* MediaSource::activeDOMObjectName() const |
| { |
| return "MediaSource"; |
| } |
| |
| void MediaSource::onReadyStateChange(ReadyState oldState, ReadyState newState) |
| { |
| ALWAYS_LOG(LOGIDENTIFIER, "old state = ", oldState, ", new state = ", newState); |
| |
| for (auto& buffer : *m_sourceBuffers) |
| buffer->readyStateChanged(); |
| |
| if (isOpen()) { |
| scheduleEvent(eventNames().sourceopenEvent); |
| return; |
| } |
| |
| if (oldState == ReadyState::Open && newState == ReadyState::Ended) { |
| scheduleEvent(eventNames().sourceendedEvent); |
| return; |
| } |
| |
| ASSERT(isClosed()); |
| scheduleEvent(eventNames().sourcecloseEvent); |
| } |
| |
| Vector<PlatformTimeRanges> MediaSource::activeRanges() const |
| { |
| Vector<PlatformTimeRanges> activeRanges; |
| for (auto& sourceBuffer : *m_activeSourceBuffers) |
| activeRanges.append(sourceBuffer->bufferedInternal().ranges()); |
| return activeRanges; |
| } |
| |
| ExceptionOr<Ref<SourceBufferPrivate>> MediaSource::createSourceBufferPrivate(const ContentType& incomingType) |
| { |
| ContentType type { incomingType }; |
| |
| auto context = scriptExecutionContext(); |
| if (context && context->isDocument() && downcast<Document>(context)->quirks().needsVP9FullRangeFlagQuirk()) |
| type = addVP9FullRangeVideoFlagToContentType(incomingType); |
| |
| RefPtr<SourceBufferPrivate> sourceBufferPrivate; |
| switch (m_private->addSourceBuffer(type, RuntimeEnabledFeatures::sharedFeatures().webMParserEnabled(), sourceBufferPrivate)) { |
| case MediaSourcePrivate::AddStatus::Ok: |
| return sourceBufferPrivate.releaseNonNull(); |
| case MediaSourcePrivate::AddStatus::NotSupported: |
| // 2.2 https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-MediaSource-addSourceBuffer-SourceBuffer-DOMString-type |
| // Step 2: If type contains a MIME type ... that is not supported with the types |
| // specified for the other SourceBuffer objects in sourceBuffers, then throw |
| // a NotSupportedError exception and abort these steps. |
| return Exception { NotSupportedError }; |
| case MediaSourcePrivate::AddStatus::ReachedIdLimit: |
| // 2.2 https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-MediaSource-addSourceBuffer-SourceBuffer-DOMString-type |
| // Step 3: If the user agent can't handle any more SourceBuffer objects then throw |
| // a QuotaExceededError exception and abort these steps. |
| return Exception { QuotaExceededError }; |
| } |
| |
| ASSERT_NOT_REACHED(); |
| return Exception { QuotaExceededError }; |
| } |
| |
| void MediaSource::scheduleEvent(const AtomString& eventName) |
| { |
| DEBUG_LOG(LOGIDENTIFIER, "scheduling '", eventName, "'"); |
| |
| queueTaskToDispatchEvent(*this, TaskSource::MediaElement, Event::create(eventName, Event::CanBubble::No, Event::IsCancelable::No)); |
| } |
| |
| ScriptExecutionContext* MediaSource::scriptExecutionContext() const |
| { |
| return ActiveDOMObject::scriptExecutionContext(); |
| } |
| |
| EventTargetInterface MediaSource::eventTargetInterface() const |
| { |
| return MediaSourceEventTargetInterfaceType; |
| } |
| |
| URLRegistry& MediaSource::registry() const |
| { |
| return MediaSourceRegistry::registry(); |
| } |
| |
| void MediaSource::regenerateActiveSourceBuffers() |
| { |
| Vector<RefPtr<SourceBuffer>> newList; |
| for (auto& sourceBuffer : *m_sourceBuffers) { |
| if (sourceBuffer->active()) |
| newList.append(sourceBuffer); |
| } |
| m_activeSourceBuffers->swap(newList); |
| for (auto& sourceBuffer : *m_activeSourceBuffers) |
| sourceBuffer->setBufferedDirty(true); |
| |
| updateBufferedIfNeeded(); |
| } |
| |
| void MediaSource::updateBufferedIfNeeded() |
| { |
| if (m_buffered && m_activeSourceBuffers->length() && std::all_of(m_activeSourceBuffers->begin(), m_activeSourceBuffers->end(), [](auto& buffer) { return !buffer->isBufferedDirty(); })) |
| return; |
| |
| m_buffered = makeUnique<PlatformTimeRanges>(); |
| for (auto& sourceBuffer : *m_activeSourceBuffers) |
| sourceBuffer->setBufferedDirty(false); |
| |
| // Implements MediaSource algorithm for HTMLMediaElement.buffered. |
| // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#htmlmediaelement-extensions |
| Vector<PlatformTimeRanges> activeRanges = this->activeRanges(); |
| |
| // 1. If activeSourceBuffers.length equals 0 then return an empty TimeRanges object and abort these steps. |
| if (activeRanges.isEmpty()) |
| return; |
| |
| // 2. Let active ranges be the ranges returned by buffered for each SourceBuffer object in activeSourceBuffers. |
| // 3. Let highest end time be the largest range end time in the active ranges. |
| MediaTime highestEndTime = MediaTime::zeroTime(); |
| for (auto& ranges : activeRanges) { |
| unsigned length = ranges.length(); |
| if (length) |
| highestEndTime = std::max(highestEndTime, ranges.end(length - 1)); |
| } |
| |
| // Return an empty range if all ranges are empty. |
| if (!highestEndTime) |
| return; |
| |
| // 4. Let intersection ranges equal a TimeRange object containing a single range from 0 to highest end time. |
| m_buffered->add(MediaTime::zeroTime(), highestEndTime); |
| |
| // 5. For each SourceBuffer object in activeSourceBuffers run the following steps: |
| bool ended = readyState() == ReadyState::Ended; |
| for (auto& sourceRanges : activeRanges) { |
| // 5.1 Let source ranges equal the ranges returned by the buffered attribute on the current SourceBuffer. |
| // 5.2 If readyState is "ended", then set the end time on the last range in source ranges to highest end time. |
| if (ended && sourceRanges.length()) |
| sourceRanges.add(sourceRanges.start(sourceRanges.length() - 1), highestEndTime); |
| |
| // 5.3 Let new intersection ranges equal the intersection between the intersection ranges and the source ranges. |
| // 5.4 Replace the ranges in intersection ranges with the new intersection ranges. |
| m_buffered->intersectWith(sourceRanges); |
| } |
| |
| if (m_private) |
| m_private->bufferedChanged(*m_buffered); |
| } |
| |
| #if !RELEASE_LOG_DISABLED |
| void MediaSource::setLogIdentifier(const void* identifier) |
| { |
| m_logIdentifier = identifier; |
| ALWAYS_LOG(LOGIDENTIFIER); |
| } |
| |
| WTFLogChannel& MediaSource::logChannel() const |
| { |
| return LogMediaSource; |
| } |
| #endif |
| |
| void MediaSource::failedToCreateRenderer(RendererType type) |
| { |
| if (auto context = scriptExecutionContext()) |
| context->addConsoleMessage(MessageSource::JS, MessageLevel::Error, makeString("MediaSource ", type == RendererType::Video ? "video" : "audio", " renderer creation failed.")); |
| } |
| |
| } |
| |
| #endif |