| /* |
| * Copyright (C) 2013 Google Inc. All rights reserved. |
| * Copyright (C) 2013-2020 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 "SourceBuffer.h" |
| |
| #if ENABLE(MEDIA_SOURCE) |
| |
| #include "AudioTrack.h" |
| #include "AudioTrackList.h" |
| #include "AudioTrackPrivate.h" |
| #include "BufferSource.h" |
| #include "ContentTypeUtilities.h" |
| #include "Event.h" |
| #include "EventNames.h" |
| #include "HTMLMediaElement.h" |
| #include "InbandTextTrack.h" |
| #include "InbandTextTrackPrivate.h" |
| #include "Logging.h" |
| #include "MediaDescription.h" |
| #include "MediaSource.h" |
| #include "SharedBuffer.h" |
| #include "SourceBufferList.h" |
| #include "SourceBufferPrivate.h" |
| #include "TextTrackList.h" |
| #include "TimeRanges.h" |
| #include "VideoTrack.h" |
| #include "VideoTrackList.h" |
| #include "VideoTrackPrivate.h" |
| #include "WebCoreOpaqueRoot.h" |
| #include <JavaScriptCore/JSCInlines.h> |
| #include <JavaScriptCore/JSLock.h> |
| #include <JavaScriptCore/VM.h> |
| #include <limits> |
| #include <wtf/CheckedArithmetic.h> |
| #include <wtf/IsoMallocInlines.h> |
| #include <wtf/WeakPtr.h> |
| |
| namespace WebCore { |
| |
| WTF_MAKE_ISO_ALLOCATED_IMPL(SourceBuffer); |
| |
| static const double ExponentialMovingAverageCoefficient = 0.1; |
| |
| Ref<SourceBuffer> SourceBuffer::create(Ref<SourceBufferPrivate>&& sourceBufferPrivate, MediaSource* source) |
| { |
| auto sourceBuffer = adoptRef(*new SourceBuffer(WTFMove(sourceBufferPrivate), source)); |
| sourceBuffer->suspendIfNeeded(); |
| return sourceBuffer; |
| } |
| |
| SourceBuffer::SourceBuffer(Ref<SourceBufferPrivate>&& sourceBufferPrivate, MediaSource* source) |
| : ActiveDOMObject(source->scriptExecutionContext()) |
| , m_private(WTFMove(sourceBufferPrivate)) |
| , m_source(source) |
| , m_opaqueRootProvider([this] { return opaqueRoot(); }) |
| , m_appendBufferTimer(*this, &SourceBuffer::appendBufferTimerFired) |
| , m_appendWindowStart(MediaTime::zeroTime()) |
| , m_appendWindowEnd(MediaTime::positiveInfiniteTime()) |
| , m_appendState(WaitingForSegment) |
| , m_timeOfBufferingMonitor(MonotonicTime::now()) |
| , m_pendingRemoveStart(MediaTime::invalidTime()) |
| , m_pendingRemoveEnd(MediaTime::invalidTime()) |
| , m_removeTimer(*this, &SourceBuffer::removeTimerFired) |
| #if !RELEASE_LOG_DISABLED |
| , m_logger(m_private->sourceBufferLogger()) |
| , m_logIdentifier(m_private->sourceBufferLogIdentifier()) |
| #endif |
| { |
| ASSERT(m_source); |
| ALWAYS_LOG(LOGIDENTIFIER); |
| |
| m_private->setClient(this); |
| m_private->setIsAttached(true); |
| } |
| |
| SourceBuffer::~SourceBuffer() |
| { |
| ASSERT(isRemoved()); |
| ALWAYS_LOG(LOGIDENTIFIER); |
| |
| m_private->setIsAttached(false); |
| m_private->setClient(nullptr); |
| } |
| |
| ExceptionOr<Ref<TimeRanges>> SourceBuffer::buffered() const |
| { |
| // Section 3.1 buffered attribute steps. |
| // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#attributes-1 |
| // 1. If this object has been removed from the sourceBuffers attribute of the parent media source then throw an |
| // InvalidStateError exception and abort these steps. |
| if (isRemoved()) |
| return Exception { InvalidStateError }; |
| |
| // 2. Return a new static normalized TimeRanges object for the media segments buffered. |
| return m_private->buffered()->copy(); |
| } |
| |
| double SourceBuffer::timestampOffset() const |
| { |
| return m_private->timestampOffset().toDouble(); |
| } |
| |
| ExceptionOr<void> SourceBuffer::setTimestampOffset(double offset) |
| { |
| // Section 3.1 timestampOffset attribute setter steps. |
| // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#attributes-1 |
| // 1. Let new timestamp offset equal the new value being assigned to this attribute. |
| // 2. If this object has been removed from the sourceBuffers attribute of the parent media source, then throw an |
| // InvalidStateError exception and abort these steps. |
| // 3. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps. |
| if (isRemoved() || m_updating) |
| return Exception { InvalidStateError }; |
| |
| // 4. If the readyState attribute of the parent media source is in the "ended" state then run the following steps: |
| // 4.1 Set the readyState attribute of the parent media source to "open" |
| // 4.2 Queue a task to fire a simple event named sourceopen at the parent media source. |
| m_source->openIfInEndedState(); |
| |
| // 5. If the append state equals PARSING_MEDIA_SEGMENT, then throw an InvalidStateError and abort these steps. |
| if (m_appendState == ParsingMediaSegment) |
| return Exception { InvalidStateError }; |
| |
| MediaTime newTimestampOffset = MediaTime::createWithDouble(offset); |
| |
| // 6. If the mode attribute equals "sequence", then set the group start timestamp to new timestamp offset. |
| if (m_mode == AppendMode::Sequence) |
| m_private->setGroupStartTimestamp(newTimestampOffset); |
| |
| // 7. Update the attribute to the new value. |
| m_private->setTimestampOffset(newTimestampOffset); |
| |
| m_private->resetTimestampOffsetInTrackBuffers(); |
| |
| return { }; |
| } |
| |
| double SourceBuffer::appendWindowStart() const |
| { |
| return m_appendWindowStart.toDouble(); |
| } |
| |
| ExceptionOr<void> SourceBuffer::setAppendWindowStart(double newValue) |
| { |
| // Section 3.1 appendWindowStart attribute setter steps. |
| // W3C Editor's Draft 16 September 2016 |
| // https://rawgit.com/w3c/media-source/45627646344eea0170dd1cbc5a3d508ca751abb8/media-source-respec.html#dom-sourcebuffer-appendwindowstart |
| // 1. If this object has been removed from the sourceBuffers attribute of the parent media source, |
| // then throw an InvalidStateError exception and abort these steps. |
| // 2. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps. |
| if (isRemoved() || m_updating) |
| return Exception { InvalidStateError }; |
| |
| // 3. If the new value is less than 0 or greater than or equal to appendWindowEnd then |
| // throw an TypeError exception and abort these steps. |
| if (newValue < 0 || newValue >= m_appendWindowEnd.toDouble()) |
| return Exception { TypeError }; |
| |
| // 4. Update the attribute to the new value. |
| m_appendWindowStart = MediaTime::createWithDouble(newValue); |
| m_private->setAppendWindowStart(m_appendWindowStart); |
| |
| return { }; |
| } |
| |
| double SourceBuffer::appendWindowEnd() const |
| { |
| return m_appendWindowEnd.toDouble(); |
| } |
| |
| ExceptionOr<void> SourceBuffer::setAppendWindowEnd(double newValue) |
| { |
| // Section 3.1 appendWindowEnd attribute setter steps. |
| // W3C Editor's Draft 16 September 2016 |
| // https://rawgit.com/w3c/media-source/45627646344eea0170dd1cbc5a3d508ca751abb8/media-source-respec.html#dom-sourcebuffer-appendwindowend |
| // 1. If this object has been removed from the sourceBuffers attribute of the parent media source, |
| // then throw an InvalidStateError exception and abort these steps. |
| // 2. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps. |
| if (isRemoved() || m_updating) |
| return Exception { InvalidStateError }; |
| |
| // 3. If the new value equals NaN, then throw an TypeError and abort these steps. |
| // 4. If the new value is less than or equal to appendWindowStart then throw an TypeError exception |
| // and abort these steps. |
| if (std::isnan(newValue) || newValue <= m_appendWindowStart.toDouble()) |
| return Exception { TypeError }; |
| |
| // 5.. Update the attribute to the new value. |
| m_appendWindowEnd = MediaTime::createWithDouble(newValue); |
| m_private->setAppendWindowEnd(m_appendWindowEnd); |
| |
| return { }; |
| } |
| |
| ExceptionOr<void> SourceBuffer::appendBuffer(const BufferSource& data) |
| { |
| return appendBufferInternal(static_cast<const unsigned char*>(data.data()), data.length()); |
| } |
| |
| void SourceBuffer::resetParserState() |
| { |
| // Section 3.5.2 Reset Parser State algorithm steps. |
| // http://www.w3.org/TR/2014/CR-media-source-20140717/#sourcebuffer-reset-parser-state |
| // 1. If the append state equals PARSING_MEDIA_SEGMENT and the input buffer contains some complete coded frames, |
| // then run the coded frame processing algorithm until all of these complete coded frames have been processed. |
| // FIXME: If any implementation will work in pulling mode (instead of async push to SourceBufferPrivate, and forget) |
| // this should be handled somehow either here, or in m_private->abort(); |
| |
| // 2. Unset the last decode timestamp on all track buffers. |
| // 3. Unset the last frame duration on all track buffers. |
| // 4. Unset the highest presentation timestamp on all track buffers. |
| // 5. Set the need random access point flag on all track buffers to true. |
| m_private->resetTrackBuffers(); |
| |
| // 6. Remove all bytes from the input buffer. |
| // Note: this is handled by abortIfUpdating() |
| // 7. Set append state to WAITING_FOR_SEGMENT. |
| m_appendState = WaitingForSegment; |
| |
| m_private->resetParserState(); |
| } |
| |
| ExceptionOr<void> SourceBuffer::abort() |
| { |
| // Section 3.2 abort() method steps. |
| // https://rawgit.com/w3c/media-source/45627646344eea0170dd1cbc5a3d508ca751abb8/media-source-respec.html#dom-sourcebuffer-abort |
| // 1. If this object has been removed from the sourceBuffers attribute of the parent media source |
| // then throw an InvalidStateError exception and abort these steps. |
| // 2. If the readyState attribute of the parent media source is not in the "open" state |
| // then throw an InvalidStateError exception and abort these steps. |
| if (isRemoved() || !m_source->isOpen()) |
| return Exception { InvalidStateError }; |
| |
| // 3. If the range removal algorithm is running, then throw an InvalidStateError exception and abort these steps. |
| if (m_removeTimer.isActive()) |
| return Exception { InvalidStateError }; |
| |
| // 4. If the sourceBuffer.updating attribute equals true, then run the following steps: ... |
| abortIfUpdating(); |
| |
| // 5. Run the reset parser state algorithm. |
| resetParserState(); |
| |
| // 6. Set appendWindowStart to the presentation start time. |
| m_appendWindowStart = MediaTime::zeroTime(); |
| m_private->setAppendWindowStart(m_appendWindowStart); |
| |
| // 7. Set appendWindowEnd to positive Infinity. |
| m_appendWindowEnd = MediaTime::positiveInfiniteTime(); |
| m_private->setAppendWindowEnd(m_appendWindowEnd); |
| |
| return { }; |
| } |
| |
| ExceptionOr<void> SourceBuffer::remove(double start, double end) |
| { |
| return remove(MediaTime::createWithDouble(start), MediaTime::createWithDouble(end)); |
| } |
| |
| ExceptionOr<void> SourceBuffer::remove(const MediaTime& start, const MediaTime& end) |
| { |
| DEBUG_LOG(LOGIDENTIFIER, "start = ", start, ", end = ", end); |
| |
| // https://rawgit.com/w3c/media-source/45627646344eea0170dd1cbc5a3d508ca751abb8/media-source-respec.html#dom-sourcebuffer-remove |
| // Section 3.2 remove() method steps. |
| // 1. If this object has been removed from the sourceBuffers attribute of the parent media source then throw |
| // an InvalidStateError exception and abort these steps. |
| // 2. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps. |
| if (isRemoved() || m_updating) |
| return Exception { InvalidStateError }; |
| |
| // 3. If duration equals NaN, then throw a TypeError exception and abort these steps. |
| // 4. If start is negative or greater than duration, then throw a TypeError exception and abort these steps. |
| // 5. If end is less than or equal to start or end equals NaN, then throw a TypeError exception and abort these steps. |
| if (m_source->duration().isInvalid() |
| || end.isInvalid() |
| || start.isInvalid() |
| || start < MediaTime::zeroTime() |
| || start > m_source->duration() |
| || end <= start) { |
| return Exception { TypeError }; |
| } |
| |
| // 6. If the readyState attribute of the parent media source is in the "ended" state then run the following steps: |
| // 6.1. Set the readyState attribute of the parent media source to "open" |
| // 6.2. Queue a task to fire a simple event named sourceopen at the parent media source . |
| m_source->openIfInEndedState(); |
| |
| // 7. Run the range removal algorithm with start and end as the start and end of the removal range. |
| rangeRemoval(start, end); |
| |
| return { }; |
| } |
| |
| void SourceBuffer::rangeRemoval(const MediaTime& start, const MediaTime& end) |
| { |
| // 3.5.7 Range Removal |
| // https://rawgit.com/w3c/media-source/7bbe4aa33c61ec025bc7acbd80354110f6a000f9/media-source.html#sourcebuffer-range-removal |
| // 1. Let start equal the starting presentation timestamp for the removal range. |
| // 2. Let end equal the end presentation timestamp for the removal range. |
| // 3. Set the updating attribute to true. |
| m_updating = true; |
| |
| // 4. Queue a task to fire a simple event named updatestart at this SourceBuffer object. |
| scheduleEvent(eventNames().updatestartEvent); |
| |
| // 5. Return control to the caller and run the rest of the steps asynchronously. |
| m_pendingRemoveStart = start; |
| m_pendingRemoveEnd = end; |
| m_removeTimer.startOneShot(0_s); |
| } |
| |
| ExceptionOr<void> SourceBuffer::changeType(const String& type) |
| { |
| // changeType() proposed API. See issue #155: <https://github.com/w3c/media-source/issues/155> |
| // https://rawgit.com/wicg/media-source/codec-switching/index.html#dom-sourcebuffer-changetype |
| |
| // 1. If type is an empty string then throw a TypeError exception and abort these steps. |
| if (type.isEmpty()) |
| return Exception { TypeError }; |
| |
| // 2. If this object has been removed from the sourceBuffers attribute of the parent media source, |
| // then throw an InvalidStateError exception and abort these steps. |
| // 3. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps. |
| if (isRemoved() || m_updating) |
| return Exception { InvalidStateError }; |
| |
| // 4. If type contains a MIME type that is not supported or contains a MIME type that is not supported with |
| // the types specified (currently or previously) of SourceBuffer objects in the sourceBuffers attribute of |
| // the parent media source, then throw a NotSupportedError exception and abort these steps. |
| ContentType contentType(type); |
| |
| auto& settings = document().settings(); |
| if (!contentTypeMeetsContainerAndCodecTypeRequirements(contentType, settings.allowedMediaContainerTypes(), settings.allowedMediaCodecTypes())) |
| return Exception { NotSupportedError }; |
| |
| if (!m_private->canSwitchToType(contentType)) |
| return Exception { NotSupportedError }; |
| |
| // 5. If the readyState attribute of the parent media source is in the "ended" state then run the following |
| // steps: |
| // 5.1. Set the readyState attribute of the parent media source to "open" |
| // 5.2. Queue a task to fire a simple event named sourceopen at the parent media source. |
| m_source->openIfInEndedState(); |
| |
| // 6. Run the reset parser state algorithm. |
| resetParserState(); |
| |
| // 7. Update the generate timestamps flag on this SourceBuffer object to the value in the "Generate Timestamps |
| // Flag" column of the byte stream format registry [MSE-REGISTRY] entry that is associated with type. |
| setShouldGenerateTimestamps(MediaSource::contentTypeShouldGenerateTimestamps(contentType)); |
| |
| // ↳ If the generate timestamps flag equals true: |
| // Set the mode attribute on this SourceBuffer object to "sequence", including running the associated steps |
| // for that attribute being set. |
| if (m_shouldGenerateTimestamps) |
| setMode(AppendMode::Sequence); |
| |
| // ↳ Otherwise: |
| // Keep the previous value of the mode attribute on this SourceBuffer object, without running any associated |
| // steps for that attribute being set. |
| // NOTE: No-op. |
| |
| // 9. Set pending initialization segment for changeType flag to true. |
| m_pendingInitializationSegmentForChangeType = true; |
| m_private->startChangingType(); |
| |
| return { }; |
| } |
| |
| void SourceBuffer::abortIfUpdating() |
| { |
| // Section 3.2 abort() method step 4 substeps. |
| // https://rawgit.com/w3c/media-source/45627646344eea0170dd1cbc5a3d508ca751abb8/media-source-respec.html#dom-sourcebuffer-abort |
| |
| if (!m_updating) |
| return; |
| |
| // 4.1. Abort the buffer append algorithm if it is running. |
| m_appendBufferTimer.stop(); |
| m_pendingAppendData = nullptr; |
| m_private->abort(); |
| |
| // 4.2. Set the updating attribute to false. |
| m_updating = false; |
| |
| // 4.3. Queue a task to fire a simple event named abort at this SourceBuffer object. |
| scheduleEvent(eventNames().abortEvent); |
| |
| // 4.4. Queue a task to fire a simple event named updateend at this SourceBuffer object. |
| scheduleEvent(eventNames().updateendEvent); |
| } |
| |
| MediaTime SourceBuffer::highestPresentationTimestamp() const |
| { |
| return m_highestPresentationTimestamp; |
| } |
| |
| void SourceBuffer::readyStateChanged() |
| { |
| if (!isRemoved()) |
| m_private->updateBufferedFromTrackBuffers(m_source->isEnded()); |
| } |
| |
| void SourceBuffer::removedFromMediaSource() |
| { |
| if (isRemoved()) |
| return; |
| |
| abortIfUpdating(); |
| |
| m_private->clearTrackBuffers(); |
| m_private->removedFromMediaSource(); |
| m_private->setIsAttached(false); |
| m_source = nullptr; |
| } |
| |
| void SourceBuffer::seekToTime(const MediaTime& time) |
| { |
| ALWAYS_LOG(LOGIDENTIFIER, time); |
| m_private->seekToTime(time); |
| } |
| |
| bool SourceBuffer::virtualHasPendingActivity() const |
| { |
| return m_source; |
| } |
| |
| void SourceBuffer::stop() |
| { |
| m_appendBufferTimer.stop(); |
| m_removeTimer.stop(); |
| } |
| |
| const char* SourceBuffer::activeDOMObjectName() const |
| { |
| return "SourceBuffer"; |
| } |
| |
| bool SourceBuffer::isRemoved() const |
| { |
| return !m_source; |
| } |
| |
| void SourceBuffer::scheduleEvent(const AtomString& eventName) |
| { |
| queueTaskToDispatchEvent(*this, TaskSource::MediaElement, Event::create(eventName, Event::CanBubble::No, Event::IsCancelable::No)); |
| } |
| |
| ExceptionOr<void> SourceBuffer::appendBufferInternal(const unsigned char* data, unsigned size) |
| { |
| // Section 3.2 appendBuffer() |
| // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-SourceBuffer-appendBuffer-void-ArrayBufferView-data |
| |
| // Step 1 is enforced by the caller. |
| // 2. Run the prepare append algorithm. |
| // Section 3.5.4 Prepare AppendAlgorithm |
| |
| // 1. If the SourceBuffer has been removed from the sourceBuffers attribute of the parent media source |
| // then throw an InvalidStateError exception and abort these steps. |
| // 2. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps. |
| if (isRemoved() || m_updating) |
| return Exception { InvalidStateError }; |
| |
| // 3. If the readyState attribute of the parent media source is in the "ended" state then run the following steps: |
| // 3.1. Set the readyState attribute of the parent media source to "open" |
| // 3.2. Queue a task to fire a simple event named sourceopen at the parent media source . |
| m_source->openIfInEndedState(); |
| |
| // 4. Run the coded frame eviction algorithm. |
| m_private->evictCodedFrames(size, maximumBufferSize(), m_source->currentTime(), m_source->duration(), m_source->isEnded()); |
| |
| // 5. If the buffer full flag equals true, then throw a QuotaExceededError exception and abort these step. |
| if (m_private->isBufferFullFor(size, maximumBufferSize())) { |
| ERROR_LOG(LOGIDENTIFIER, "buffer full, failing with QuotaExceededError error"); |
| return Exception { QuotaExceededError }; |
| } |
| |
| // NOTE: Return to 3.2 appendBuffer() |
| // 3. Add data to the end of the input buffer. |
| ASSERT(!m_pendingAppendData); |
| m_pendingAppendData = SharedBuffer::create(data, size); |
| |
| // 4. Set the updating attribute to true. |
| m_updating = true; |
| |
| // 5. Queue a task to fire a simple event named updatestart at this SourceBuffer object. |
| scheduleEvent(eventNames().updatestartEvent); |
| |
| // 6. Asynchronously run the buffer append algorithm. |
| m_appendBufferTimer.startOneShot(0_s); |
| |
| return { }; |
| } |
| |
| void SourceBuffer::appendBufferTimerFired() |
| { |
| if (isRemoved()) |
| return; |
| |
| ASSERT(m_updating); |
| |
| // Section 3.5.5 Buffer Append Algorithm |
| // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#sourcebuffer-buffer-append |
| |
| // 1. Run the segment parser loop algorithm. |
| |
| // Section 3.5.1 Segment Parser Loop |
| // https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#sourcebuffer-segment-parser-loop |
| // When the segment parser loop algorithm is invoked, run the following steps: |
| |
| RefPtr<SharedBuffer> appendData = WTFMove(m_pendingAppendData); |
| // 1. Loop Top: If the input buffer is empty, then jump to the need more data step below. |
| if (!appendData || !appendData->size()) { |
| sourceBufferPrivateAppendComplete(AppendResult::AppendSucceeded); |
| return; |
| } |
| |
| m_private->append(appendData.releaseNonNull()); |
| } |
| |
| void SourceBuffer::sourceBufferPrivateAppendComplete(AppendResult result) |
| { |
| if (isRemoved()) |
| return; |
| |
| // Section 3.5.5 Buffer Append Algorithm, ctd. |
| // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#sourcebuffer-buffer-append |
| |
| // 2. If the input buffer contains bytes that violate the SourceBuffer byte stream format specification, |
| // then run the append error algorithm with the decode error parameter set to true and abort this algorithm. |
| if (result == AppendResult::ParsingFailed) { |
| ERROR_LOG(LOGIDENTIFIER, "ParsingFailed"); |
| appendError(true); |
| return; |
| } |
| |
| // NOTE: Steps 3 - 6 enforced by sourceBufferPrivateDidReceiveInitializationSegment() and |
| // sourceBufferPrivateDidReceiveSample below. |
| |
| // 7. Need more data: Return control to the calling algorithm. |
| |
| // NOTE: return to Section 3.5.5 |
| // 2.If the segment parser loop algorithm in the previous step was aborted, then abort this algorithm. |
| if (result != AppendResult::AppendSucceeded) |
| return; |
| |
| // 3. Set the updating attribute to false. |
| m_updating = false; |
| |
| // 4. Queue a task to fire a simple event named update at this SourceBuffer object. |
| scheduleEvent(eventNames().updateEvent); |
| |
| // 5. Queue a task to fire a simple event named updateend at this SourceBuffer object. |
| scheduleEvent(eventNames().updateendEvent); |
| |
| m_source->monitorSourceBuffers(); |
| m_private->reenqueueMediaIfNeeded(m_source->currentTime()); |
| |
| DEBUG_LOG(LOGIDENTIFIER); |
| } |
| |
| void SourceBuffer::sourceBufferPrivateDidReceiveRenderingError(int64_t error) |
| { |
| #if RELEASE_LOG_DISABLED |
| UNUSED_PARAM(error); |
| #endif |
| |
| ERROR_LOG(LOGIDENTIFIER, error); |
| |
| if (!isRemoved()) |
| m_source->streamEndedWithError(MediaSource::EndOfStreamError::Decode); |
| } |
| |
| void SourceBuffer::removeTimerFired() |
| { |
| if (isRemoved()) |
| return; |
| |
| ASSERT(m_updating); |
| ASSERT(m_pendingRemoveStart.isValid()); |
| ASSERT(m_pendingRemoveStart < m_pendingRemoveEnd); |
| |
| // Section 3.5.7 Range Removal |
| // http://w3c.github.io/media-source/#sourcebuffer-range-removal |
| |
| // 6. Run the coded frame removal algorithm with start and end as the start and end of the removal range. |
| |
| m_private->removeCodedFrames(m_pendingRemoveStart, m_pendingRemoveEnd, m_source->currentTime(), m_source->isEnded(), [this, protectedThis = Ref { *this }] { |
| // 7. Set the updating attribute to false. |
| m_updating = false; |
| m_pendingRemoveStart = MediaTime::invalidTime(); |
| m_pendingRemoveEnd = MediaTime::invalidTime(); |
| |
| // 8. Queue a task to fire a simple event named update at this SourceBuffer object. |
| scheduleEvent(eventNames().updateEvent); |
| |
| // 9. Queue a task to fire a simple event named updateend at this SourceBuffer object. |
| scheduleEvent(eventNames().updateendEvent); |
| }); |
| } |
| |
| uint64_t SourceBuffer::maximumBufferSize() const |
| { |
| if (isRemoved()) |
| return 0; |
| |
| auto* element = m_source->mediaElement(); |
| if (!element) |
| return 0; |
| |
| size_t platformMaximumBufferSize = m_private->platformMaximumBufferSize(); |
| if (platformMaximumBufferSize) |
| return platformMaximumBufferSize; |
| |
| return element->maximumSourceBufferSize(*this); |
| } |
| |
| VideoTrackList& SourceBuffer::videoTracks() |
| { |
| if (!m_videoTracks) { |
| m_videoTracks = VideoTrackList::create(scriptExecutionContext()); |
| m_videoTracks->setOpaqueRootObserver(m_opaqueRootProvider); |
| } |
| return *m_videoTracks; |
| } |
| |
| AudioTrackList& SourceBuffer::audioTracks() |
| { |
| if (!m_audioTracks) { |
| m_audioTracks = AudioTrackList::create(scriptExecutionContext()); |
| m_audioTracks->setOpaqueRootObserver(m_opaqueRootProvider); |
| } |
| return *m_audioTracks; |
| } |
| |
| TextTrackList& SourceBuffer::textTracks() |
| { |
| if (!m_textTracks) { |
| m_textTracks = TextTrackList::create(scriptExecutionContext()); |
| m_textTracks->setOpaqueRootObserver(m_opaqueRootProvider); |
| } |
| return *m_textTracks; |
| } |
| |
| void SourceBuffer::setActive(bool active) |
| { |
| if (m_active == active) |
| return; |
| |
| m_active = active; |
| m_private->setActive(active); |
| if (!isRemoved()) |
| m_source->sourceBufferDidChangeActiveState(*this, active); |
| } |
| |
| void SourceBuffer::sourceBufferPrivateDidReceiveInitializationSegment(InitializationSegment&& segment, CompletionHandler<void()>&& completionHandler) |
| { |
| if (isRemoved()) { |
| completionHandler(); |
| return; |
| } |
| |
| ALWAYS_LOG(LOGIDENTIFIER); |
| |
| // 3.5.8 Initialization Segment Received (ctd) |
| // https://rawgit.com/w3c/media-source/c3ad59c7a370d04430969ba73d18dc9bcde57a33/index.html#sourcebuffer-init-segment-received [Editor's Draft 09 January 2015] |
| |
| // 1. Update the duration attribute if it currently equals NaN: |
| if (m_source->duration().isInvalid()) { |
| // ↳ If the initialization segment contains a duration: |
| // Run the duration change algorithm with new duration set to the duration in the initialization segment. |
| // ↳ Otherwise: |
| // Run the duration change algorithm with new duration set to positive Infinity. |
| if (segment.duration.isValid() && !segment.duration.isIndefinite()) |
| m_source->setDurationInternal(segment.duration); |
| else |
| m_source->setDurationInternal(MediaTime::positiveInfiniteTime()); |
| } |
| |
| // 2. If the initialization segment has no audio, video, or text tracks, then run the append error algorithm |
| // with the decode error parameter set to true and abort these steps. |
| if (segment.audioTracks.isEmpty() && segment.videoTracks.isEmpty() && segment.textTracks.isEmpty()) { |
| appendError(true); |
| completionHandler(); |
| return; |
| } |
| |
| // 3. If the first initialization segment flag is true, then run the following steps: |
| if (m_receivedFirstInitializationSegment) { |
| // 3.1. Verify the following properties. If any of the checks fail then run the append error algorithm |
| // with the decode error parameter set to true and abort these steps. |
| if (!validateInitializationSegment(segment)) { |
| appendError(true); |
| completionHandler(); |
| return; |
| } |
| |
| Vector<std::pair<AtomString, AtomString>> trackIdPairs; |
| |
| // 3.2 Add the appropriate track descriptions from this initialization segment to each of the track buffers. |
| ASSERT(segment.audioTracks.size() == audioTracks().length()); |
| for (auto& audioTrackInfo : segment.audioTracks) { |
| if (audioTracks().length() == 1) { |
| auto* track = audioTracks().item(0); |
| auto oldId = track->id(); |
| auto newId = audioTrackInfo.track->id(); |
| track->setPrivate(*audioTrackInfo.track); |
| if (newId != oldId) |
| trackIdPairs.append(std::make_pair(oldId, newId)); |
| break; |
| } |
| |
| auto audioTrack = audioTracks().getTrackById(audioTrackInfo.track->id()); |
| ASSERT(audioTrack); |
| audioTrack->setPrivate(*audioTrackInfo.track); |
| } |
| |
| ASSERT(segment.videoTracks.size() == videoTracks().length()); |
| for (auto& videoTrackInfo : segment.videoTracks) { |
| if (videoTracks().length() == 1) { |
| auto* track = videoTracks().item(0); |
| auto oldId = track->id(); |
| auto newId = videoTrackInfo.track->id(); |
| track->setPrivate(*videoTrackInfo.track); |
| if (newId != oldId) |
| trackIdPairs.append(std::make_pair(oldId, newId)); |
| break; |
| } |
| |
| auto videoTrack = videoTracks().getTrackById(videoTrackInfo.track->id()); |
| ASSERT(videoTrack); |
| videoTrack->setPrivate(*videoTrackInfo.track); |
| } |
| |
| ASSERT(segment.textTracks.size() == textTracks().length()); |
| for (auto& textTrackInfo : segment.textTracks) { |
| if (textTracks().length() == 1) { |
| auto* track = downcast<InbandTextTrack>(textTracks().item(0)); |
| auto oldId = track->id(); |
| auto newId = textTrackInfo.track->id(); |
| track->setPrivate(*textTrackInfo.track); |
| if (newId != oldId) |
| trackIdPairs.append(std::make_pair(oldId, newId)); |
| break; |
| } |
| |
| auto textTrack = textTracks().getTrackById(textTrackInfo.track->id()); |
| ASSERT(textTrack); |
| downcast<InbandTextTrack>(*textTrack).setPrivate(*textTrackInfo.track); |
| } |
| |
| if (!trackIdPairs.isEmpty()) |
| m_private->updateTrackIds(WTFMove(trackIdPairs)); |
| |
| // 3.3 Set the need random access point flag on all track buffers to true. |
| m_private->setAllTrackBuffersNeedRandomAccess(); |
| } |
| |
| // 4. Let active track flag equal false. |
| bool activeTrackFlag = false; |
| |
| // 5. If the first initialization segment flag is false, then run the following steps: |
| if (!m_receivedFirstInitializationSegment) { |
| // 5.1 If the initialization segment contains tracks with codecs the user agent does not support, |
| // then run the append error algorithm with the decode error parameter set to true and abort these steps. |
| // NOTE: This check is the responsibility of the SourceBufferPrivate. |
| if (auto& allowedMediaAudioCodecIDs = document().settings().allowedMediaAudioCodecIDs()) { |
| for (auto& audioTrackInfo : segment.audioTracks) { |
| if (audioTrackInfo.description && allowedMediaAudioCodecIDs->contains(FourCC::fromString(audioTrackInfo.description->codec()))) |
| continue; |
| appendError(true); |
| completionHandler(); |
| return; |
| } |
| } |
| |
| if (auto& allowedMediaVideoCodecIDs = document().settings().allowedMediaVideoCodecIDs()) { |
| for (auto& videoTrackInfo : segment.videoTracks) { |
| if (videoTrackInfo.description && allowedMediaVideoCodecIDs->contains(FourCC::fromString(videoTrackInfo.description->codec()))) |
| continue; |
| appendError(true); |
| completionHandler(); |
| return; |
| } |
| } |
| |
| // 5.2 For each audio track in the initialization segment, run following steps: |
| for (auto& audioTrackInfo : segment.audioTracks) { |
| // FIXME: Implement steps 5.2.1-5.2.8.1 as per Editor's Draft 09 January 2015, and reorder this |
| // 5.2.1 Let new audio track be a new AudioTrack object. |
| // 5.2.2 Generate a unique ID and assign it to the id property on new video track. |
| auto newAudioTrack = AudioTrack::create(scriptExecutionContext(), *audioTrackInfo.track); |
| newAudioTrack->addClient(*this); |
| newAudioTrack->setSourceBuffer(this); |
| |
| // 5.2.3 If audioTracks.length equals 0, then run the following steps: |
| if (!audioTracks().length()) { |
| // 5.2.3.1 Set the enabled property on new audio track to true. |
| newAudioTrack->setEnabled(true); |
| |
| // 5.2.3.2 Set active track flag to true. |
| activeTrackFlag = true; |
| } |
| |
| // 5.2.4 Add new audio track to the audioTracks attribute on this SourceBuffer object. |
| // 5.2.5 Queue a task to fire a trusted event named addtrack, that does not bubble and is |
| // not cancelable, and that uses the TrackEvent interface, at the AudioTrackList object |
| // referenced by the audioTracks attribute on this SourceBuffer object. |
| audioTracks().append(newAudioTrack.copyRef()); |
| |
| // 5.2.6 Add new audio track to the audioTracks attribute on the HTMLMediaElement. |
| // 5.2.7 Queue a task to fire a trusted event named addtrack, that does not bubble and is |
| // not cancelable, and that uses the TrackEvent interface, at the AudioTrackList object |
| // referenced by the audioTracks attribute on the HTMLMediaElement. |
| m_source->mediaElement()->addAudioTrack(newAudioTrack.copyRef()); |
| |
| m_audioCodecs.append(audioTrackInfo.description->codec()); |
| |
| // 5.2.8 Create a new track buffer to store coded frames for this track. |
| m_private->addTrackBuffer(newAudioTrack->id(), WTFMove(audioTrackInfo.description)); |
| } |
| |
| // 5.3 For each video track in the initialization segment, run following steps: |
| for (auto& videoTrackInfo : segment.videoTracks) { |
| // FIXME: Implement steps 5.3.1-5.3.8.1 as per Editor's Draft 09 January 2015, and reorder this |
| // 5.3.1 Let new video track be a new VideoTrack object. |
| // 5.3.2 Generate a unique ID and assign it to the id property on new video track. |
| auto newVideoTrack = VideoTrack::create(scriptExecutionContext(), *videoTrackInfo.track); |
| newVideoTrack->addClient(*this); |
| newVideoTrack->setSourceBuffer(this); |
| |
| // 5.3.3 If videoTracks.length equals 0, then run the following steps: |
| if (!videoTracks().length()) { |
| // 5.3.3.1 Set the selected property on new video track to true. |
| newVideoTrack->setSelected(true); |
| |
| // 5.3.3.2 Set active track flag to true. |
| activeTrackFlag = true; |
| } |
| |
| // 5.3.4 Add new video track to the videoTracks attribute on this SourceBuffer object. |
| // 5.3.5 Queue a task to fire a trusted event named addtrack, that does not bubble and is |
| // not cancelable, and that uses the TrackEvent interface, at the VideoTrackList object |
| // referenced by the videoTracks attribute on this SourceBuffer object. |
| videoTracks().append(newVideoTrack.copyRef()); |
| |
| // 5.3.6 Add new video track to the videoTracks attribute on the HTMLMediaElement. |
| // 5.3.7 Queue a task to fire a trusted event named addtrack, that does not bubble and is |
| // not cancelable, and that uses the TrackEvent interface, at the VideoTrackList object |
| // referenced by the videoTracks attribute on the HTMLMediaElement. |
| m_source->mediaElement()->addVideoTrack(newVideoTrack.copyRef()); |
| |
| m_videoCodecs.append(videoTrackInfo.description->codec()); |
| |
| // 5.3.8 Create a new track buffer to store coded frames for this track. |
| m_private->addTrackBuffer(newVideoTrack->id(), WTFMove(videoTrackInfo.description)); |
| } |
| |
| // 5.4 For each text track in the initialization segment, run following steps: |
| for (auto& textTrackInfo : segment.textTracks) { |
| auto& textTrackPrivate = *textTrackInfo.track; |
| |
| // FIXME: Implement steps 5.4.1-5.4.8.1 as per Editor's Draft 09 January 2015, and reorder this |
| // 5.4.1 Let new text track be a new TextTrack object with its properties populated with the |
| // appropriate information from the initialization segment. |
| auto newTextTrack = InbandTextTrack::create(document(), textTrackPrivate); |
| newTextTrack->addClient(*this); |
| |
| // 5.4.2 If the mode property on new text track equals "showing" or "hidden", then set active |
| // track flag to true. |
| if (textTrackPrivate.mode() != InbandTextTrackPrivate::Mode::Disabled) |
| activeTrackFlag = true; |
| |
| // 5.4.3 Add new text track to the textTracks attribute on this SourceBuffer object. |
| // 5.4.4 Queue a task to fire a trusted event named addtrack, that does not bubble and is |
| // not cancelable, and that uses the TrackEvent interface, at textTracks attribute on this |
| // SourceBuffer object. |
| textTracks().append(newTextTrack.get()); |
| |
| // 5.4.5 Add new text track to the textTracks attribute on the HTMLMediaElement. |
| // 5.4.6 Queue a task to fire a trusted event named addtrack, that does not bubble and is |
| // not cancelable, and that uses the TrackEvent interface, at the TextTrackList object |
| // referenced by the textTracks attribute on the HTMLMediaElement. |
| m_source->mediaElement()->addTextTrack(newTextTrack.copyRef()); |
| |
| m_textCodecs.append(textTrackInfo.description->codec()); |
| |
| // 5.4.7 Create a new track buffer to store coded frames for this track. |
| m_private->addTrackBuffer(newTextTrack->id(), WTFMove(textTrackInfo.description)); |
| } |
| |
| // 5.5 If active track flag equals true, then run the following steps: |
| if (activeTrackFlag) { |
| // 5.5.1 Add this SourceBuffer to activeSourceBuffers. |
| // 5.5.2 Queue a task to fire a simple event named addsourcebuffer at activeSourceBuffers |
| setActive(true); |
| } |
| |
| // 5.6 Set first initialization segment flag to true. |
| m_receivedFirstInitializationSegment = true; |
| } |
| |
| // (Note: Issue #155 adds this step after step 5:) |
| // 6. Set pending initialization segment for changeType flag to false. |
| m_pendingInitializationSegmentForChangeType = false; |
| completionHandler(); |
| |
| // 6. If the HTMLMediaElement.readyState attribute is HAVE_NOTHING, then run the following steps: |
| if (m_private->readyState() == MediaPlayer::ReadyState::HaveNothing) { |
| // 6.1 If one or more objects in sourceBuffers have first initialization segment flag set to false, then abort these steps. |
| for (auto& sourceBuffer : *m_source->sourceBuffers()) { |
| if (!sourceBuffer->m_receivedFirstInitializationSegment) |
| return; |
| } |
| |
| // 6.2 Set the HTMLMediaElement.readyState attribute to HAVE_METADATA. |
| // 6.3 Queue a task to fire a simple event named loadedmetadata at the media element. |
| m_private->setReadyState(MediaPlayer::ReadyState::HaveMetadata); |
| } |
| |
| // 7. If the active track flag equals true and the HTMLMediaElement.readyState |
| // attribute is greater than HAVE_CURRENT_DATA, then set the HTMLMediaElement.readyState |
| // attribute to HAVE_METADATA. |
| if (activeTrackFlag && m_private->readyState() > MediaPlayer::ReadyState::HaveCurrentData) |
| m_private->setReadyState(MediaPlayer::ReadyState::HaveMetadata); |
| } |
| |
| bool SourceBuffer::validateInitializationSegment(const InitializationSegment& segment) |
| { |
| // FIXME: ordering of all 3.5.X (X>=7) functions needs to be updated to post-[24 July 2014 Editor's Draft] version |
| // 3.5.8 Initialization Segment Received (ctd) |
| // https://rawgit.com/w3c/media-source/c3ad59c7a370d04430969ba73d18dc9bcde57a33/index.html#sourcebuffer-init-segment-received [Editor's Draft 09 January 2015] |
| |
| // Note: those are checks from step 3.1 |
| // * The number of audio, video, and text tracks match what was in the first initialization segment. |
| if (segment.audioTracks.size() != audioTracks().length() |
| || segment.videoTracks.size() != videoTracks().length() |
| || segment.textTracks.size() != textTracks().length()) |
| return false; |
| |
| // * The codecs for each track, match what was specified in the first initialization segment. |
| // (Note: Issue #155 strikes out this check. For broad compatibility when this experimental feature |
| // is not enabled, only perform this check if the "pending initialization segment for changeType flag" |
| // is not set.) |
| for (auto& audioTrackInfo : segment.audioTracks) { |
| if (m_audioCodecs.contains(audioTrackInfo.description->codec())) |
| continue; |
| |
| if (!m_pendingInitializationSegmentForChangeType) |
| return false; |
| |
| m_audioCodecs.append(audioTrackInfo.description->codec()); |
| } |
| |
| for (auto& videoTrackInfo : segment.videoTracks) { |
| if (m_videoCodecs.contains(videoTrackInfo.description->codec())) |
| continue; |
| |
| if (!m_pendingInitializationSegmentForChangeType) |
| return false; |
| |
| m_videoCodecs.append(videoTrackInfo.description->codec()); |
| } |
| |
| for (auto& textTrackInfo : segment.textTracks) { |
| if (m_textCodecs.contains(textTrackInfo.description->codec())) |
| continue; |
| |
| if (!m_pendingInitializationSegmentForChangeType) |
| return false; |
| |
| m_textCodecs.append(textTrackInfo.description->codec()); |
| } |
| |
| return true; |
| } |
| |
| void SourceBuffer::sourceBufferPrivateAppendError(bool decodeError) |
| { |
| appendError(decodeError); |
| } |
| |
| void SourceBuffer::appendError(bool decodeError) |
| { |
| // 3.5.3 Append Error Algorithm |
| // https://rawgit.com/w3c/media-source/c3ad59c7a370d04430969ba73d18dc9bcde57a33/index.html#sourcebuffer-append-error [Editor's Draft 09 January 2015] |
| |
| ASSERT(m_updating); |
| // 1. Run the reset parser state algorithm. |
| resetParserState(); |
| |
| // 2. Set the updating attribute to false. |
| m_updating = false; |
| |
| // 3. Queue a task to fire a simple event named error at this SourceBuffer object. |
| scheduleEvent(eventNames().errorEvent); |
| |
| // 4. Queue a task to fire a simple event named updateend at this SourceBuffer object. |
| scheduleEvent(eventNames().updateendEvent); |
| |
| // 5. If decode error is true, then run the end of stream algorithm with the error parameter set to "decode". |
| if (decodeError && !isRemoved()) |
| m_source->streamEndedWithError(MediaSource::EndOfStreamError::Decode); |
| } |
| |
| bool SourceBuffer::hasAudio() const |
| { |
| return m_audioTracks && m_audioTracks->length(); |
| } |
| |
| bool SourceBuffer::hasVideo() const |
| { |
| return m_videoTracks && m_videoTracks->length(); |
| } |
| |
| void SourceBuffer::videoTrackSelectedChanged(VideoTrack& track) |
| { |
| // 2.4.5 Changes to selected/enabled track state |
| // If the selected video track changes, then run the following steps: |
| // 1. If the SourceBuffer associated with the previously selected video track is not associated with |
| // any other enabled tracks, run the following steps: |
| if (!track.selected() |
| && (!m_videoTracks || !m_videoTracks->isAnyTrackEnabled()) |
| && (!m_audioTracks || !m_audioTracks->isAnyTrackEnabled()) |
| && (!m_textTracks || !m_textTracks->isAnyTrackEnabled())) { |
| // 1.1 Remove the SourceBuffer from activeSourceBuffers. |
| // 1.2 Queue a task to fire a simple event named removesourcebuffer at activeSourceBuffers |
| setActive(false); |
| } else if (track.selected()) { |
| // 2. If the SourceBuffer associated with the newly selected video track is not already in activeSourceBuffers, |
| // run the following steps: |
| // 2.1 Add the SourceBuffer to activeSourceBuffers. |
| // 2.2 Queue a task to fire a simple event named addsourcebuffer at activeSourceBuffers |
| setActive(true); |
| } |
| |
| if (m_videoTracks && m_videoTracks->contains(track)) |
| m_videoTracks->scheduleChangeEvent(); |
| } |
| |
| void SourceBuffer::videoTrackKindChanged(VideoTrack& track) |
| { |
| if (m_videoTracks && m_videoTracks->contains(track)) |
| m_videoTracks->scheduleChangeEvent(); |
| } |
| |
| void SourceBuffer::videoTrackLabelChanged(VideoTrack& track) |
| { |
| if (m_videoTracks && m_videoTracks->contains(track)) |
| m_videoTracks->scheduleChangeEvent(); |
| } |
| |
| void SourceBuffer::videoTrackLanguageChanged(VideoTrack& track) |
| { |
| if (m_videoTracks && m_videoTracks->contains(track)) |
| m_videoTracks->scheduleChangeEvent(); |
| } |
| |
| void SourceBuffer::audioTrackEnabledChanged(AudioTrack& track) |
| { |
| // 2.4.5 Changes to selected/enabled track state |
| // If an audio track becomes disabled and the SourceBuffer associated with this track is not |
| // associated with any other enabled or selected track, then run the following steps: |
| if (!track.enabled() |
| && (!m_videoTracks || !m_videoTracks->isAnyTrackEnabled()) |
| && (!m_audioTracks || !m_audioTracks->isAnyTrackEnabled()) |
| && (!m_textTracks || !m_textTracks->isAnyTrackEnabled())) { |
| // 1. Remove the SourceBuffer associated with the audio track from activeSourceBuffers |
| // 2. Queue a task to fire a simple event named removesourcebuffer at activeSourceBuffers |
| setActive(false); |
| } else if (track.enabled()) { |
| // If an audio track becomes enabled and the SourceBuffer associated with this track is |
| // not already in activeSourceBuffers, then run the following steps: |
| // 1. Add the SourceBuffer associated with the audio track to activeSourceBuffers |
| // 2. Queue a task to fire a simple event named addsourcebuffer at activeSourceBuffers |
| setActive(true); |
| } |
| |
| if (m_audioTracks && m_audioTracks->contains(track)) |
| m_audioTracks->scheduleChangeEvent(); |
| } |
| |
| void SourceBuffer::audioTrackKindChanged(AudioTrack& track) |
| { |
| if (m_audioTracks && m_audioTracks->contains(track)) |
| m_audioTracks->scheduleChangeEvent(); |
| } |
| |
| void SourceBuffer::audioTrackLabelChanged(AudioTrack& track) |
| { |
| if (m_audioTracks && m_audioTracks->contains(track)) |
| m_audioTracks->scheduleChangeEvent(); |
| } |
| |
| void SourceBuffer::audioTrackLanguageChanged(AudioTrack& track) |
| { |
| if (m_audioTracks && m_audioTracks->contains(track)) |
| m_audioTracks->scheduleChangeEvent(); |
| } |
| |
| void SourceBuffer::textTrackModeChanged(TextTrack& track) |
| { |
| // 2.4.5 Changes to selected/enabled track state |
| // If a text track mode becomes "disabled" and the SourceBuffer associated with this track is not |
| // associated with any other enabled or selected track, then run the following steps: |
| if (track.mode() == TextTrack::Mode::Disabled |
| && (!m_videoTracks || !m_videoTracks->isAnyTrackEnabled()) |
| && (!m_audioTracks || !m_audioTracks->isAnyTrackEnabled()) |
| && (!m_textTracks || !m_textTracks->isAnyTrackEnabled())) { |
| // 1. Remove the SourceBuffer associated with the audio track from activeSourceBuffers |
| // 2. Queue a task to fire a simple event named removesourcebuffer at activeSourceBuffers |
| setActive(false); |
| } else { |
| // If a text track mode becomes "showing" or "hidden" and the SourceBuffer associated with this |
| // track is not already in activeSourceBuffers, then run the following steps: |
| // 1. Add the SourceBuffer associated with the text track to activeSourceBuffers |
| // 2. Queue a task to fire a simple event named addsourcebuffer at activeSourceBuffers |
| setActive(true); |
| } |
| |
| if (m_textTracks && m_textTracks->contains(track)) |
| m_textTracks->scheduleChangeEvent(); |
| } |
| |
| void SourceBuffer::textTrackKindChanged(TextTrack& track) |
| { |
| if (m_textTracks && m_textTracks->contains(track)) |
| m_textTracks->scheduleChangeEvent(); |
| } |
| |
| void SourceBuffer::textTrackLanguageChanged(TextTrack& track) |
| { |
| if (m_textTracks && m_textTracks->contains(track)) |
| m_textTracks->scheduleChangeEvent(); |
| } |
| |
| void SourceBuffer::sourceBufferPrivateDidParseSample(double frameDuration) |
| { |
| m_bufferedSinceLastMonitor += frameDuration; |
| } |
| |
| void SourceBuffer::sourceBufferPrivateDurationChanged(const MediaTime& duration) |
| { |
| if (isRemoved()) |
| return; |
| |
| m_source->setDurationInternal(duration); |
| if (m_textTracks) |
| m_textTracks->setDuration(duration); |
| } |
| |
| void SourceBuffer::sourceBufferPrivateHighestPresentationTimestampChanged(const MediaTime& timestamp) |
| { |
| m_highestPresentationTimestamp = timestamp; |
| } |
| |
| void SourceBuffer::sourceBufferPrivateDidDropSample() |
| { |
| if (!isRemoved()) |
| m_source->mediaElement()->incrementDroppedFrameCount(); |
| } |
| |
| void SourceBuffer::sourceBufferPrivateStreamEndedWithDecodeError() |
| { |
| if (!isRemoved()) |
| m_source->streamEndedWithError(MediaSource::EndOfStreamError::Decode); |
| } |
| |
| void SourceBuffer::monitorBufferingRate() |
| { |
| MonotonicTime now = MonotonicTime::now(); |
| Seconds interval = now - m_timeOfBufferingMonitor; |
| double rateSinceLastMonitor = m_bufferedSinceLastMonitor / interval.seconds(); |
| |
| m_timeOfBufferingMonitor = now; |
| m_bufferedSinceLastMonitor = 0; |
| |
| m_averageBufferRate += (interval.seconds() * ExponentialMovingAverageCoefficient) * (rateSinceLastMonitor - m_averageBufferRate); |
| |
| DEBUG_LOG(LOGIDENTIFIER, m_averageBufferRate); |
| } |
| |
| bool SourceBuffer::canPlayThroughRange(PlatformTimeRanges& ranges) |
| { |
| if (isRemoved()) |
| return false; |
| |
| monitorBufferingRate(); |
| |
| // Assuming no fluctuations in the buffering rate, loading 1 second per second or greater |
| // means indefinite playback. This could be improved by taking jitter into account. |
| if (m_averageBufferRate > 1) |
| return true; |
| |
| // Add up all the time yet to be buffered. |
| MediaTime currentTime = m_source->currentTime(); |
| MediaTime duration = m_source->duration(); |
| |
| PlatformTimeRanges unbufferedRanges = ranges; |
| unbufferedRanges.invert(); |
| unbufferedRanges.intersectWith(PlatformTimeRanges(currentTime, std::max(currentTime, duration))); |
| MediaTime unbufferedTime = unbufferedRanges.totalDuration(); |
| if (!unbufferedTime.isValid()) |
| return true; |
| |
| MediaTime timeRemaining = duration - currentTime; |
| return unbufferedTime.toDouble() / m_averageBufferRate < timeRemaining.toDouble(); |
| } |
| |
| void SourceBuffer::sourceBufferPrivateReportExtraMemoryCost(uint64_t extraMemory) |
| { |
| reportExtraMemoryAllocated(extraMemory); |
| } |
| |
| void SourceBuffer::reportExtraMemoryAllocated(uint64_t extraMemory) |
| { |
| uint64_t extraMemoryCost = extraMemory; |
| if (m_pendingAppendData) |
| extraMemoryCost += m_pendingAppendData->size(); |
| |
| m_extraMemoryCost = extraMemoryCost; |
| |
| if (extraMemoryCost <= m_reportedExtraMemoryCost) |
| return; |
| |
| uint64_t extraMemoryCostDelta = extraMemoryCost - m_reportedExtraMemoryCost; |
| m_reportedExtraMemoryCost = extraMemoryCost; |
| |
| JSC::JSLockHolder lock(scriptExecutionContext()->vm()); |
| // FIXME: Adopt reportExtraMemoryVisited, and switch to reportExtraMemoryAllocated. |
| // https://bugs.webkit.org/show_bug.cgi?id=142595 |
| scriptExecutionContext()->vm().heap.deprecatedReportExtraMemory(extraMemoryCostDelta); |
| } |
| |
| void SourceBuffer::bufferedSamplesForTrackId(const AtomString& trackID, CompletionHandler<void(Vector<String>&&)>&& completionHandler) |
| { |
| m_private->bufferedSamplesForTrackId(trackID, WTFMove(completionHandler)); |
| } |
| |
| void SourceBuffer::enqueuedSamplesForTrackID(const AtomString& trackID, CompletionHandler<void(Vector<String>&&)>&& completionHandler) |
| { |
| return m_private->enqueuedSamplesForTrackID(trackID, WTFMove(completionHandler)); |
| } |
| |
| MediaTime SourceBuffer::minimumUpcomingPresentationTimeForTrackID(const AtomString& trackID) |
| { |
| return m_private->minimumUpcomingPresentationTimeForTrackID(trackID); |
| } |
| |
| void SourceBuffer::setMaximumQueueDepthForTrackID(const AtomString& trackID, uint64_t maxQueueDepth) |
| { |
| m_private->setMaximumQueueDepthForTrackID(trackID, maxQueueDepth); |
| } |
| |
| Document& SourceBuffer::document() const |
| { |
| ASSERT(scriptExecutionContext()); |
| return downcast<Document>(*scriptExecutionContext()); |
| } |
| |
| ExceptionOr<void> SourceBuffer::setMode(AppendMode newMode) |
| { |
| // 3.1 Attributes - mode |
| // http://www.w3.org/TR/media-source/#widl-SourceBuffer-mode |
| |
| // On setting, run the following steps: |
| |
| // 1. Let new mode equal the new value being assigned to this attribute. |
| // 2. If generate timestamps flag equals true and new mode equals "segments", then throw an InvalidAccessError exception and abort these steps. |
| if (m_shouldGenerateTimestamps && newMode == AppendMode::Segments) |
| return Exception { TypeError }; |
| |
| // 3. If this object has been removed from the sourceBuffers attribute of the parent media source, then throw an InvalidStateError exception and abort these steps. |
| // 4. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps. |
| if (isRemoved() || m_updating) |
| return Exception { InvalidStateError }; |
| |
| // 5. If the readyState attribute of the parent media source is in the "ended" state then run the following steps: |
| if (m_source->isEnded()) { |
| // 5.1. Set the readyState attribute of the parent media source to "open" |
| // 5.2. Queue a task to fire a simple event named sourceopen at the parent media source. |
| m_source->openIfInEndedState(); |
| } |
| |
| // 6. If the append state equals PARSING_MEDIA_SEGMENT, then throw an InvalidStateError and abort these steps. |
| if (m_appendState == ParsingMediaSegment) |
| return Exception { InvalidStateError }; |
| |
| // 7. If the new mode equals "sequence", then set the group start timestamp to the group end timestamp. |
| if (newMode == AppendMode::Sequence) |
| m_private->setGroupStartTimestampToEndTimestamp(); |
| |
| // 8. Update the attribute to new mode. |
| m_mode = newMode; |
| |
| if (newMode == AppendMode::Segments) |
| m_private->setMode(SourceBufferAppendMode::Segments); |
| else |
| m_private->setMode(SourceBufferAppendMode::Sequence); |
| |
| return { }; |
| } |
| |
| void SourceBuffer::setShouldGenerateTimestamps(bool flag) |
| { |
| m_shouldGenerateTimestamps = flag; |
| m_private->setShouldGenerateTimestamps(flag); |
| } |
| |
| void SourceBuffer::sourceBufferPrivateBufferedDirtyChanged(bool flag) |
| { |
| m_bufferedDirty = flag; |
| if (!isRemoved()) |
| m_source->sourceBufferDidChangeBufferedDirty(*this, flag); |
| } |
| |
| bool SourceBuffer::isBufferedDirty() const |
| { |
| return m_bufferedDirty; |
| } |
| |
| void SourceBuffer::setBufferedDirty(bool flag) |
| { |
| m_bufferedDirty = flag; |
| } |
| |
| void SourceBuffer::setMediaSourceEnded(bool isEnded) |
| { |
| m_private->setMediaSourceEnded(isEnded); |
| } |
| |
| size_t SourceBuffer::memoryCost() const |
| { |
| return sizeof(SourceBuffer) + m_extraMemoryCost; |
| } |
| |
| WebCoreOpaqueRoot SourceBuffer::opaqueRoot() |
| { |
| return WebCoreOpaqueRoot { this }; |
| } |
| |
| #if !RELEASE_LOG_DISABLED |
| WTFLogChannel& SourceBuffer::logChannel() const |
| { |
| return LogMediaSource; |
| } |
| #endif |
| |
| } // namespace WebCore |
| |
| #endif |