| /* |
| * Copyright (C) 2013 Google 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 "third_party/blink/renderer/modules/mediasource/source_buffer.h" |
| |
| #include <limits> |
| #include <memory> |
| #include <sstream> |
| |
| #include "media/base/logging_override_if_enabled.h" |
| #include "third_party/blink/public/platform/task_type.h" |
| #include "third_party/blink/public/platform/web_source_buffer.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/dom/events/event.h" |
| #include "third_party/blink/renderer/core/dom/events/event_queue.h" |
| #include "third_party/blink/renderer/core/execution_context/execution_context.h" |
| #include "third_party/blink/renderer/core/frame/deprecation.h" |
| #include "third_party/blink/renderer/core/html/media/html_media_element.h" |
| #include "third_party/blink/renderer/core/html/time_ranges.h" |
| #include "third_party/blink/renderer/core/html/track/audio_track.h" |
| #include "third_party/blink/renderer/core/html/track/audio_track_list.h" |
| #include "third_party/blink/renderer/core/html/track/video_track.h" |
| #include "third_party/blink/renderer/core/html/track/video_track_list.h" |
| #include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h" |
| #include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer_view.h" |
| #include "third_party/blink/renderer/modules/mediasource/media_source.h" |
| #include "third_party/blink/renderer/modules/mediasource/source_buffer_track_base_supplement.h" |
| #include "third_party/blink/renderer/platform/bindings/exception_messages.h" |
| #include "third_party/blink/renderer/platform/bindings/exception_state.h" |
| #include "third_party/blink/renderer/platform/heap/heap.h" |
| #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" |
| #include "third_party/blink/renderer/platform/instrumentation/use_counter.h" |
| #include "third_party/blink/renderer/platform/network/mime/content_type.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #include "third_party/blink/renderer/platform/wtf/functional.h" |
| #include "third_party/blink/renderer/platform/wtf/math_extras.h" |
| |
| using blink::WebSourceBuffer; |
| |
| namespace blink { |
| |
| namespace { |
| |
| static bool ThrowExceptionIfRemovedOrUpdating(bool is_removed, |
| bool is_updating, |
| ExceptionState& exception_state) { |
| if (is_removed) { |
| MediaSource::LogAndThrowDOMException( |
| exception_state, DOMExceptionCode::kInvalidStateError, |
| "This SourceBuffer has been removed from the parent media source."); |
| return true; |
| } |
| if (is_updating) { |
| MediaSource::LogAndThrowDOMException( |
| exception_state, DOMExceptionCode::kInvalidStateError, |
| "This SourceBuffer is still processing an 'appendBuffer' or " |
| "'remove' operation."); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| WTF::String WebTimeRangesToString(const WebTimeRanges& ranges) { |
| StringBuilder string_builder; |
| string_builder.Append('{'); |
| for (auto& r : ranges) { |
| string_builder.Append(" ["); |
| string_builder.AppendNumber(r.start); |
| string_builder.Append(';'); |
| string_builder.AppendNumber(r.end); |
| string_builder.Append(']'); |
| } |
| string_builder.Append(" }"); |
| return string_builder.ToString(); |
| } |
| |
| } // namespace |
| |
| SourceBuffer* SourceBuffer::Create( |
| std::unique_ptr<WebSourceBuffer> web_source_buffer, |
| MediaSource* source, |
| EventQueue* async_event_queue) { |
| return MakeGarbageCollected<SourceBuffer>(std::move(web_source_buffer), |
| source, async_event_queue); |
| } |
| |
| SourceBuffer::SourceBuffer(std::unique_ptr<WebSourceBuffer> web_source_buffer, |
| MediaSource* source, |
| EventQueue* async_event_queue) |
| : ContextLifecycleObserver(source->GetExecutionContext()), |
| web_source_buffer_(std::move(web_source_buffer)), |
| source_(source), |
| track_defaults_(MakeGarbageCollected<TrackDefaultList>()), |
| async_event_queue_(async_event_queue), |
| mode_(SegmentsKeyword()), |
| updating_(false), |
| timestamp_offset_(0), |
| append_window_start_(0), |
| append_window_end_(std::numeric_limits<double>::infinity()), |
| first_initialization_segment_received_(false), |
| pending_append_data_offset_(0), |
| pending_remove_start_(-1), |
| pending_remove_end_(-1) { |
| DVLOG(1) << __func__ << " this=" << this; |
| |
| DCHECK(web_source_buffer_); |
| DCHECK(source_); |
| DCHECK(source_->MediaElement()); |
| audio_tracks_ = |
| MakeGarbageCollected<AudioTrackList>(*source_->MediaElement()); |
| video_tracks_ = |
| MakeGarbageCollected<VideoTrackList>(*source_->MediaElement()); |
| web_source_buffer_->SetClient(this); |
| } |
| |
| SourceBuffer::~SourceBuffer() { |
| DVLOG(1) << __func__ << " this=" << this; |
| } |
| |
| void SourceBuffer::Dispose() { |
| // Promptly clears a raw reference from content/ to an on-heap object |
| // so that content/ doesn't access it in a lazy sweeping phase. |
| web_source_buffer_.reset(); |
| } |
| |
| const AtomicString& SourceBuffer::SegmentsKeyword() { |
| DEFINE_STATIC_LOCAL(const AtomicString, segments, ("segments")); |
| return segments; |
| } |
| |
| const AtomicString& SourceBuffer::SequenceKeyword() { |
| DEFINE_STATIC_LOCAL(const AtomicString, sequence, ("sequence")); |
| return sequence; |
| } |
| |
| void SourceBuffer::setMode(const AtomicString& new_mode, |
| ExceptionState& exception_state) { |
| DVLOG(3) << __func__ << " this=" << this << " new_mode=" << new_mode; |
| // Section 3.1 On setting mode attribute steps. |
| // https://www.w3.org/TR/media-source/#dom-sourcebuffer-mode |
| // 1. If this object has been removed from the sourceBuffers attribute of the |
| // parent media source, then throw an INVALID_STATE_ERR exception and abort |
| // these steps. |
| // 2. If the updating attribute equals true, then throw an INVALID_STATE_ERR |
| // exception and abort these steps. |
| // 3. Let new mode equal the new value being assigned to this attribute. |
| if (ThrowExceptionIfRemovedOrUpdating(IsRemoved(), updating_, |
| exception_state)) { |
| return; |
| } |
| |
| // 4. If generate timestamps flag equals true and new mode equals "segments", |
| // then throw a TypeError exception and abort these steps. |
| if (web_source_buffer_->GetGenerateTimestampsFlag() && |
| new_mode == SegmentsKeyword()) { |
| MediaSource::LogAndThrowTypeError( |
| exception_state, "The mode value provided (" + SegmentsKeyword() + |
| ") is invalid for a byte stream format that uses " |
| "generated timestamps."); |
| return; |
| } |
| |
| // 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. |
| source_->OpenIfInEndedState(); |
| |
| // 6. If the append state equals PARSING_MEDIA_SEGMENT, then throw an |
| // INVALID_STATE_ERR and abort these steps. |
| // 7. If the new mode equals "sequence", then set the group start timestamp to |
| // the highest presentation end timestamp. |
| WebSourceBuffer::AppendMode append_mode = |
| WebSourceBuffer::kAppendModeSegments; |
| if (new_mode == SequenceKeyword()) |
| append_mode = WebSourceBuffer::kAppendModeSequence; |
| if (!web_source_buffer_->SetMode(append_mode)) { |
| MediaSource::LogAndThrowDOMException(exception_state, |
| DOMExceptionCode::kInvalidStateError, |
| "The mode may not be set while the " |
| "SourceBuffer's append state is " |
| "'PARSING_MEDIA_SEGMENT'."); |
| return; |
| } |
| |
| // 8. Update the attribute to new mode. |
| mode_ = new_mode; |
| } |
| |
| TimeRanges* SourceBuffer::buffered(ExceptionState& exception_state) const { |
| // Section 3.1 buffered attribute 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. |
| if (IsRemoved()) { |
| MediaSource::LogAndThrowDOMException( |
| exception_state, DOMExceptionCode::kInvalidStateError, |
| "This SourceBuffer has been removed from the parent media source."); |
| return nullptr; |
| } |
| |
| // 2. Return a new static normalized TimeRanges object for the media segments |
| // buffered. |
| return MakeGarbageCollected<TimeRanges>(web_source_buffer_->Buffered()); |
| } |
| |
| WebTimeRanges SourceBuffer::buffered() const { |
| return web_source_buffer_->Buffered(); |
| } |
| |
| double SourceBuffer::timestampOffset() const { |
| return timestamp_offset_; |
| } |
| |
| void SourceBuffer::setTimestampOffset(double offset, |
| ExceptionState& exception_state) { |
| DVLOG(3) << __func__ << " this=" << this << " offset=" << offset; |
| // Section 3.1 timestampOffset attribute setter steps. |
| // https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#widl-SourceBuffer-timestampOffset |
| // 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 (ThrowExceptionIfRemovedOrUpdating(IsRemoved(), updating_, |
| exception_state)) |
| return; |
| |
| // 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. |
| source_->OpenIfInEndedState(); |
| |
| // 5. If the append state equals PARSING_MEDIA_SEGMENT, then throw an |
| // INVALID_STATE_ERR and abort these steps. |
| // 6. If the mode attribute equals "sequence", then set the group start |
| // timestamp to new timestamp offset. |
| if (!web_source_buffer_->SetTimestampOffset(offset)) { |
| MediaSource::LogAndThrowDOMException(exception_state, |
| DOMExceptionCode::kInvalidStateError, |
| "The timestamp offset may not be set " |
| "while the SourceBuffer's append " |
| "state is 'PARSING_MEDIA_SEGMENT'."); |
| return; |
| } |
| |
| // 7. Update the attribute to new timestamp offset. |
| timestamp_offset_ = offset; |
| } |
| |
| AudioTrackList& SourceBuffer::audioTracks() { |
| DCHECK(HTMLMediaElement::MediaTracksEnabledInternally()); |
| return *audio_tracks_; |
| } |
| |
| VideoTrackList& SourceBuffer::videoTracks() { |
| DCHECK(HTMLMediaElement::MediaTracksEnabledInternally()); |
| return *video_tracks_; |
| } |
| |
| double SourceBuffer::appendWindowStart() const { |
| return append_window_start_; |
| } |
| |
| void SourceBuffer::setAppendWindowStart(double start, |
| ExceptionState& exception_state) { |
| DVLOG(3) << __func__ << " this=" << this << " start=" << start; |
| // Section 3.1 appendWindowStart attribute setter steps. |
| // https://www.w3.org/TR/media-source/#widl-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 (ThrowExceptionIfRemovedOrUpdating(IsRemoved(), updating_, |
| exception_state)) |
| return; |
| |
| // 3. If the new value is less than 0 or greater than or equal to |
| // appendWindowEnd then throw a TypeError exception and abort these steps. |
| if (start < 0 || start >= append_window_end_) { |
| MediaSource::LogAndThrowTypeError( |
| exception_state, |
| ExceptionMessages::IndexOutsideRange( |
| "value", start, 0.0, ExceptionMessages::kExclusiveBound, |
| append_window_end_, ExceptionMessages::kInclusiveBound)); |
| return; |
| } |
| |
| web_source_buffer_->SetAppendWindowStart(start); |
| |
| // 4. Update the attribute to the new value. |
| append_window_start_ = start; |
| } |
| |
| double SourceBuffer::appendWindowEnd() const { |
| return append_window_end_; |
| } |
| |
| void SourceBuffer::setAppendWindowEnd(double end, |
| ExceptionState& exception_state) { |
| DVLOG(3) << __func__ << " this=" << this << " end=" << end; |
| // Section 3.1 appendWindowEnd attribute setter steps. |
| // https://www.w3.org/TR/media-source/#widl-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 (ThrowExceptionIfRemovedOrUpdating(IsRemoved(), updating_, |
| exception_state)) |
| return; |
| |
| // 3. If the new value equals NaN, then throw a TypeError and abort these |
| // steps. |
| if (std::isnan(end)) { |
| MediaSource::LogAndThrowTypeError(exception_state, |
| ExceptionMessages::NotAFiniteNumber(end)); |
| return; |
| } |
| // 4. If the new value is less than or equal to appendWindowStart then throw a |
| // TypeError exception and abort these steps. |
| if (end <= append_window_start_) { |
| MediaSource::LogAndThrowTypeError( |
| exception_state, ExceptionMessages::IndexExceedsMinimumBound( |
| "value", end, append_window_start_)); |
| return; |
| } |
| |
| web_source_buffer_->SetAppendWindowEnd(end); |
| |
| // 5. Update the attribute to the new value. |
| append_window_end_ = end; |
| } |
| |
| void SourceBuffer::appendBuffer(DOMArrayBuffer* data, |
| ExceptionState& exception_state) { |
| double media_time = GetMediaTime(); |
| DVLOG(2) << __func__ << " this=" << this << " media_time=" << media_time |
| << " size=" << data->ByteLength(); |
| // 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 |
| AppendBufferInternal(media_time, |
| static_cast<const unsigned char*>(data->Data()), |
| data->ByteLength(), exception_state); |
| } |
| |
| void SourceBuffer::appendBuffer(NotShared<DOMArrayBufferView> data, |
| ExceptionState& exception_state) { |
| double media_time = GetMediaTime(); |
| DVLOG(3) << __func__ << " this=" << this << " media_time=" << media_time |
| << " size=" << data.View()->byteLength(); |
| // 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 |
| AppendBufferInternal( |
| media_time, static_cast<const unsigned char*>(data.View()->BaseAddress()), |
| data.View()->byteLength(), exception_state); |
| } |
| |
| void SourceBuffer::abort(ExceptionState& exception_state) { |
| DVLOG(2) << __func__ << " this=" << this; |
| // http://w3c.github.io/media-source/#widl-SourceBuffer-abort-void |
| // 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()) { |
| MediaSource::LogAndThrowDOMException( |
| exception_state, DOMExceptionCode::kInvalidStateError, |
| "This SourceBuffer has been removed from the parent media source."); |
| return; |
| } |
| if (!source_->IsOpen()) { |
| MediaSource::LogAndThrowDOMException( |
| exception_state, DOMExceptionCode::kInvalidStateError, |
| "The parent media source's readyState is not 'open'."); |
| return; |
| } |
| |
| // 3. If the range removal algorithm is running, then throw an |
| // InvalidStateError exception and abort these steps. |
| if (pending_remove_start_ != -1) { |
| DCHECK(updating_); |
| // Throwing the exception and aborting these steps is new behavior that |
| // is implemented behind the MediaSourceNewAbortAndDuration |
| // RuntimeEnabledFeature. |
| if (RuntimeEnabledFeatures::MediaSourceNewAbortAndDurationEnabled()) { |
| MediaSource::LogAndThrowDOMException( |
| exception_state, DOMExceptionCode::kInvalidStateError, |
| "Aborting asynchronous remove() operation is disallowed."); |
| return; |
| } |
| |
| Deprecation::CountDeprecation(source_->MediaElement()->GetDocument(), |
| WebFeature::kMediaSourceAbortRemove); |
| CancelRemove(); |
| } |
| |
| // 4. If the sourceBuffer.updating attribute equals true, then run the |
| // following steps: ... |
| AbortIfUpdating(); |
| |
| // 5. Run the reset parser state algorithm. |
| web_source_buffer_->ResetParserState(); |
| |
| // 6. Set appendWindowStart to 0. |
| setAppendWindowStart(0, exception_state); |
| |
| // 7. Set appendWindowEnd to positive Infinity. |
| setAppendWindowEnd(std::numeric_limits<double>::infinity(), exception_state); |
| } |
| |
| void SourceBuffer::remove(double start, |
| double end, |
| ExceptionState& exception_state) { |
| DVLOG(2) << __func__ << " this=" << this << " start=" << start |
| << " end=" << end; |
| |
| // Section 3.2 remove() method steps. |
| // https://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end |
| // 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 (ThrowExceptionIfRemovedOrUpdating(IsRemoved(), updating_, |
| exception_state)) |
| return; |
| |
| // 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. |
| if (start < 0 || std::isnan(source_->duration()) || |
| start > source_->duration()) { |
| MediaSource::LogAndThrowTypeError( |
| exception_state, |
| ExceptionMessages::IndexOutsideRange( |
| "start", start, 0.0, ExceptionMessages::kExclusiveBound, |
| std::isnan(source_->duration()) ? 0 : source_->duration(), |
| ExceptionMessages::kExclusiveBound)); |
| return; |
| } |
| |
| // 5. If end is less than or equal to start or end equals NaN, then throw a |
| // TypeError exception and abort these steps. |
| if (end <= start || std::isnan(end)) { |
| MediaSource::LogAndThrowTypeError( |
| exception_state, |
| "The end value provided (" + String::Number(end) + |
| ") must be greater than the start value provided (" + |
| String::Number(start) + ")."); |
| return; |
| } |
| |
| TRACE_EVENT_ASYNC_BEGIN0("media", "SourceBuffer::remove", this); |
| |
| // 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 . |
| source_->OpenIfInEndedState(); |
| |
| // 7. Run the range removal algorithm with start and end as the start and end |
| // of the removal range. |
| // 7.3. Set the updating attribute to true. |
| updating_ = true; |
| |
| // 7.4. Queue a task to fire a simple event named updatestart at this |
| // SourceBuffer object. |
| ScheduleEvent(event_type_names::kUpdatestart); |
| |
| // 7.5. Return control to the caller and run the rest of the steps |
| // asynchronously. |
| pending_remove_start_ = start; |
| pending_remove_end_ = end; |
| remove_async_task_handle_ = PostCancellableTask( |
| *GetExecutionContext()->GetTaskRunner(TaskType::kMediaElementEvent), |
| FROM_HERE, |
| WTF::Bind(&SourceBuffer::RemoveAsyncPart, WrapPersistent(this))); |
| } |
| |
| void SourceBuffer::changeType(const String& type, |
| ExceptionState& exception_state) { |
| DVLOG(2) << __func__ << " this=" << this << " type=" << type; |
| |
| // Per 30 May 2018 Codec Switching feature incubation spec: |
| // https://rawgit.com/WICG/media-source/3b3742ea788999bb7ae4a4553ac7d574b0547dbe/index.html#dom-sourcebuffer-changetype |
| // 1. If type is an empty string then throw a TypeError exception and abort |
| // these steps. |
| if (type.IsEmpty()) { |
| MediaSource::LogAndThrowTypeError(exception_state, |
| "The type provided is empty"); |
| return; |
| } |
| |
| // 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 (ThrowExceptionIfRemovedOrUpdating(IsRemoved(), updating_, |
| exception_state)) |
| return; |
| |
| // 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 content_type(type); |
| String codecs = content_type.Parameter("codecs"); |
| // TODO(wolenetz): Refactor and use a less-strict version of isTypeSupported |
| // here. As part of that, CanChangeType in Chromium should inherit relaxation |
| // of impl's StreamParserFactory (since it returns true iff a stream parser |
| // can be constructed with |type|). See https://crbug.com/535738. |
| if (!MediaSource::isTypeSupported(type) || |
| !web_source_buffer_->CanChangeType(content_type.GetType(), codecs)) { |
| MediaSource::LogAndThrowDOMException( |
| exception_state, DOMExceptionCode::kNotSupportedError, |
| "Changing to the type provided ('" + type + "') is not supported."); |
| return; |
| } |
| |
| // 5. If the readyState attribute of the parent media source is in the "ended" |
| // state then run the following steps: |
| // 1. Set the readyState attribute of the parent media source to "open" |
| // 2. Queue a task to fire a simple event named sourceopen at the parent |
| // media source. |
| source_->OpenIfInEndedState(); |
| |
| // 6. Run the reset parser state algorithm. |
| web_source_buffer_->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 entry that is associated with type. |
| // This call also updates the pipeline to switch bytestream parser and codecs. |
| web_source_buffer_->ChangeType(content_type.GetType(), codecs); |
| |
| // 8. 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. Otherwise: keep the previous value |
| // of the mode attribute on this SourceBuffer object, without running any |
| // associated steps for that attribute being set. |
| if (web_source_buffer_->GetGenerateTimestampsFlag()) |
| setMode(SequenceKeyword(), exception_state); |
| |
| // 9. Set pending initialization segment for changeType flag to true. |
| // The logic for this flag is handled by the pipeline (the new bytestream |
| // parser will expect an initialization segment first). |
| } |
| |
| void SourceBuffer::setTrackDefaults(TrackDefaultList* track_defaults, |
| ExceptionState& exception_state) { |
| // Per 02 Dec 2014 Editor's Draft |
| // http://w3c.github.io/media-source/#widl-SourceBuffer-trackDefaults |
| // 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 (ThrowExceptionIfRemovedOrUpdating(IsRemoved(), updating_, |
| exception_state)) |
| return; |
| |
| // 3. Update the attribute to the new value. |
| track_defaults_ = track_defaults; |
| } |
| |
| void SourceBuffer::CancelRemove() { |
| DCHECK(updating_); |
| DCHECK_NE(pending_remove_start_, -1); |
| remove_async_task_handle_.Cancel(); |
| pending_remove_start_ = -1; |
| pending_remove_end_ = -1; |
| updating_ = false; |
| |
| if (!RuntimeEnabledFeatures::MediaSourceNewAbortAndDurationEnabled()) { |
| ScheduleEvent(event_type_names::kAbort); |
| ScheduleEvent(event_type_names::kUpdateend); |
| } |
| |
| TRACE_EVENT_ASYNC_END0("media", "SourceBuffer::remove", this); |
| } |
| |
| void SourceBuffer::AbortIfUpdating() { |
| // Section 3.2 abort() method step 4 substeps. |
| // http://w3c.github.io/media-source/#widl-SourceBuffer-abort-void |
| |
| if (!updating_) |
| return; |
| |
| DCHECK_EQ(pending_remove_start_, -1); |
| |
| const char* trace_event_name = "SourceBuffer::appendBuffer"; |
| |
| // 4.1. Abort the buffer append and stream append loop algorithms if they are |
| // running. |
| append_buffer_async_task_handle_.Cancel(); |
| pending_append_data_.clear(); |
| pending_append_data_offset_ = 0; |
| |
| // 4.2. Set the updating attribute to false. |
| updating_ = false; |
| |
| // 4.3. Queue a task to fire a simple event named abort at this SourceBuffer |
| // object. |
| ScheduleEvent(event_type_names::kAbort); |
| |
| // 4.4. Queue a task to fire a simple event named updateend at this |
| // SourceBuffer object. |
| ScheduleEvent(event_type_names::kUpdateend); |
| |
| TRACE_EVENT_ASYNC_END0("media", trace_event_name, this); |
| } |
| |
| void SourceBuffer::RemovedFromMediaSource() { |
| if (IsRemoved()) |
| return; |
| |
| DVLOG(3) << __func__ << " this=" << this; |
| if (pending_remove_start_ != -1) { |
| CancelRemove(); |
| } else { |
| AbortIfUpdating(); |
| } |
| |
| if (HTMLMediaElement::MediaTracksEnabledInternally()) { |
| DCHECK(source_); |
| if (source_->MediaElement()->audioTracks().length() > 0 || |
| source_->MediaElement()->videoTracks().length() > 0) { |
| RemoveMediaTracks(); |
| } |
| } |
| |
| web_source_buffer_->RemovedFromMediaSource(); |
| web_source_buffer_.reset(); |
| source_ = nullptr; |
| async_event_queue_ = nullptr; |
| } |
| |
| double SourceBuffer::HighestPresentationTimestamp() { |
| DCHECK(!IsRemoved()); |
| |
| double pts = web_source_buffer_->HighestPresentationTimestamp(); |
| DVLOG(3) << __func__ << " this=" << this << ", pts=" << pts; |
| return pts; |
| } |
| |
| void SourceBuffer::RemoveMediaTracks() { |
| DCHECK(HTMLMediaElement::MediaTracksEnabledInternally()); |
| // Spec: |
| // http://w3c.github.io/media-source/#widl-MediaSource-removeSourceBuffer-void-SourceBuffer-sourceBuffer |
| DCHECK(source_); |
| |
| HTMLMediaElement* media_element = source_->MediaElement(); |
| DCHECK(media_element); |
| // 3. Let SourceBuffer audioTracks list equal the AudioTrackList object |
| // returned by sourceBuffer.audioTracks. |
| // 4. If the SourceBuffer audioTracks list is not empty, then run the |
| // following steps: |
| // 4.1 Let HTMLMediaElement audioTracks list equal the AudioTrackList object |
| // returned by the audioTracks attribute on the HTMLMediaElement. |
| // 4.2 Let the removed enabled audio track flag equal false. |
| bool removed_enabled_audio_track = false; |
| // 4.3 For each AudioTrack object in the SourceBuffer audioTracks list, run |
| // the following steps: |
| while (audioTracks().length() > 0) { |
| AudioTrack* audio_track = audioTracks().AnonymousIndexedGetter(0); |
| // 4.3.1 Set the sourceBuffer attribute on the AudioTrack object to null. |
| SourceBufferTrackBaseSupplement::SetSourceBuffer(*audio_track, nullptr); |
| // 4.3.2 If the enabled attribute on the AudioTrack object is true, then set |
| // the removed enabled audio track flag to true. |
| if (audio_track->enabled()) |
| removed_enabled_audio_track = true; |
| // 4.3.3 Remove the AudioTrack object from the HTMLMediaElement audioTracks |
| // list. |
| // 4.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. |
| media_element->audioTracks().Remove(audio_track->id()); |
| // 4.3.5 Remove the AudioTrack object from the SourceBuffer audioTracks |
| // list. |
| // 4.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(audio_track->id()); |
| } |
| // 4.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 (removed_enabled_audio_track) { |
| Event* event = Event::Create(event_type_names::kChange); |
| event->SetTarget(&media_element->audioTracks()); |
| media_element->ScheduleEvent(event); |
| } |
| |
| // 5. Let SourceBuffer videoTracks list equal the VideoTrackList object |
| // returned by sourceBuffer.videoTracks. |
| // 6. If the SourceBuffer videoTracks list is not empty, then run the |
| // following steps: |
| // 6.1 Let HTMLMediaElement videoTracks list equal the VideoTrackList object |
| // returned by the videoTracks attribute on the HTMLMediaElement. |
| // 6.2 Let the removed selected video track flag equal false. |
| bool removed_selected_video_track = false; |
| // 6.3 For each VideoTrack object in the SourceBuffer videoTracks list, run |
| // the following steps: |
| while (videoTracks().length() > 0) { |
| VideoTrack* video_track = videoTracks().AnonymousIndexedGetter(0); |
| // 6.3.1 Set the sourceBuffer attribute on the VideoTrack object to null. |
| SourceBufferTrackBaseSupplement::SetSourceBuffer(*video_track, nullptr); |
| // 6.3.2 If the selected attribute on the VideoTrack object is true, then |
| // set the removed selected video track flag to true. |
| if (video_track->selected()) |
| removed_selected_video_track = true; |
| // 6.3.3 Remove the VideoTrack object from the HTMLMediaElement videoTracks |
| // list. |
| // 6.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. |
| media_element->videoTracks().Remove(video_track->id()); |
| // 6.3.5 Remove the VideoTrack object from the SourceBuffer videoTracks |
| // list. |
| // 6.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(video_track->id()); |
| } |
| // 6.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 (removed_selected_video_track) { |
| Event* event = Event::Create(event_type_names::kChange); |
| event->SetTarget(&media_element->videoTracks()); |
| media_element->ScheduleEvent(event); |
| } |
| |
| // 7-8. TODO(servolk): Remove text tracks once SourceBuffer has text tracks. |
| } |
| |
| double SourceBuffer::GetMediaTime() { |
| double media_time = std::numeric_limits<float>::quiet_NaN(); |
| if (source_ && source_->MediaElement()) |
| media_time = source_->MediaElement()->currentTime(); |
| return media_time; |
| } |
| |
| template <class T> |
| T* FindExistingTrackById(const TrackListBase<T>& track_list, const String& id) { |
| // According to MSE specification |
| // (https://w3c.github.io/media-source/#sourcebuffer-init-segment-received) |
| // step 3.1: |
| // > If more than one track for a single type are present (ie 2 audio tracks), |
| // then the Track IDs match the ones in the first initialization segment. |
| // I.e. we only need to search by TrackID if there is more than one track, |
| // otherwise we can assume that the only track of the given type is the same |
| // one that we had in previous init segments. |
| if (track_list.length() == 1) |
| return track_list.AnonymousIndexedGetter(0); |
| return track_list.getTrackById(id); |
| } |
| |
| const TrackDefault* SourceBuffer::GetTrackDefault( |
| const AtomicString& track_type, |
| const AtomicString& byte_stream_track_id) const { |
| // This is a helper for implementation of default track label and default |
| // track language algorithms. |
| // defaultTrackLabel spec: |
| // https://w3c.github.io/media-source/#sourcebuffer-default-track-label |
| // defaultTrackLanguage spec: |
| // https://w3c.github.io/media-source/#sourcebuffer-default-track-language |
| |
| // 1. If trackDefaults contains a TrackDefault object with a type attribute |
| // equal to type and a byteStreamTrackID attribute equal to |
| // byteStreamTrackID, then return the value of the label/language attribute |
| // on this matching object and abort these steps. |
| // 2. If trackDefaults contains a TrackDefault object with a type attribute |
| // equal to type and a byteStreamTrackID attribute equal to an empty |
| // string, then return the value of the label/language attribute on this |
| // matching object and abort these steps. |
| // 3. Return an empty string to the caller |
| const TrackDefault* track_default_with_empty_bytestream_id = nullptr; |
| for (unsigned i = 0; i < track_defaults_->length(); ++i) { |
| const TrackDefault* track_default = track_defaults_->item(i); |
| if (track_default->type() != track_type) |
| continue; |
| if (track_default->byteStreamTrackID() == byte_stream_track_id) |
| return track_default; |
| if (!track_default_with_empty_bytestream_id && |
| track_default->byteStreamTrackID() == "") |
| track_default_with_empty_bytestream_id = track_default; |
| } |
| return track_default_with_empty_bytestream_id; |
| } |
| |
| AtomicString SourceBuffer::DefaultTrackLabel( |
| const AtomicString& track_type, |
| const AtomicString& byte_stream_track_id) const { |
| // Spec: https://w3c.github.io/media-source/#sourcebuffer-default-track-label |
| const TrackDefault* track_default = |
| GetTrackDefault(track_type, byte_stream_track_id); |
| return track_default ? AtomicString(track_default->label()) : ""; |
| } |
| |
| AtomicString SourceBuffer::DefaultTrackLanguage( |
| const AtomicString& track_type, |
| const AtomicString& byte_stream_track_id) const { |
| // Spec: |
| // https://w3c.github.io/media-source/#sourcebuffer-default-track-language |
| const TrackDefault* track_default = |
| GetTrackDefault(track_type, byte_stream_track_id); |
| return track_default ? AtomicString(track_default->language()) : ""; |
| } |
| |
| bool SourceBuffer::InitializationSegmentReceived( |
| const WebVector<MediaTrackInfo>& new_tracks) { |
| DVLOG(3) << __func__ << " this=" << this << " tracks=" << new_tracks.size(); |
| DCHECK(source_); |
| DCHECK(source_->MediaElement()); |
| DCHECK(updating_); |
| |
| if (!HTMLMediaElement::MediaTracksEnabledInternally()) { |
| if (!first_initialization_segment_received_) { |
| source_->SetSourceBufferActive(this, true); |
| first_initialization_segment_received_ = true; |
| } |
| return true; |
| } |
| |
| // Implementation of Initialization Segment Received, see |
| // https://w3c.github.io/media-source/#sourcebuffer-init-segment-received |
| |
| // Sort newTracks into audio and video tracks to facilitate implementation |
| // of subsequent steps of this algorithm. |
| Vector<MediaTrackInfo> new_audio_tracks; |
| Vector<MediaTrackInfo> new_video_tracks; |
| for (const MediaTrackInfo& track_info : new_tracks) { |
| const TrackBase* track = nullptr; |
| if (track_info.track_type == WebMediaPlayer::kAudioTrack) { |
| new_audio_tracks.push_back(track_info); |
| if (first_initialization_segment_received_) |
| track = FindExistingTrackById(audioTracks(), track_info.id); |
| } else if (track_info.track_type == WebMediaPlayer::kVideoTrack) { |
| new_video_tracks.push_back(track_info); |
| if (first_initialization_segment_received_) |
| track = FindExistingTrackById(videoTracks(), track_info.id); |
| } else { |
| DVLOG(3) << __func__ << " this=" << this |
| << " failed: unsupported track type " << track_info.track_type; |
| // TODO(servolk): Add handling of text tracks. |
| NOTREACHED(); |
| } |
| if (first_initialization_segment_received_ && !track) { |
| DVLOG(3) << __func__ << " this=" << this |
| << " failed: tracks mismatch the first init segment."; |
| return false; |
| } |
| #if DCHECK_IS_ON() |
| const char* log_track_type_str = |
| (track_info.track_type == WebMediaPlayer::kAudioTrack) ? "audio" |
| : "video"; |
| DVLOG(3) << __func__ << " this=" << this << " : " << log_track_type_str |
| << " track " |
| << " id=" << String(track_info.id) |
| << " byteStreamTrackID=" << String(track_info.byte_stream_track_id) |
| << " kind=" << String(track_info.kind) |
| << " label=" << String(track_info.label) |
| << " language=" << String(track_info.language); |
| #endif |
| } |
| |
| // 1. Update the duration attribute if it currently equals NaN: |
| // TODO(servolk): Pass also stream duration into initSegmentReceived. |
| |
| // 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 (new_tracks.empty()) { |
| DVLOG(3) << __func__ << " this=" << this |
| << " failed: no tracks found in the init segment."; |
| // The append error algorithm will be called at the top level after we |
| // return false here to indicate failure. |
| return false; |
| } |
| |
| // 3. If the first initialization segment received flag is true, then run the |
| // following steps: |
| if (first_initialization_segment_received_) { |
| // 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. |
| bool tracks_match_first_init_segment = true; |
| // - The number of audio, video, and text tracks match what was in the first |
| // initialization segment. |
| if (new_audio_tracks.size() != audioTracks().length() || |
| new_video_tracks.size() != videoTracks().length()) { |
| tracks_match_first_init_segment = false; |
| } |
| // - The codecs for each track, match what was specified in the first |
| // initialization segment. |
| // This is currently done in MediaSourceState::OnNewConfigs. |
| // - If more than one track for a single type are present (ie 2 audio |
| // tracks), then the Track IDs match the ones in the first initialization |
| // segment. |
| if (tracks_match_first_init_segment && new_audio_tracks.size() > 1) { |
| for (wtf_size_t i = 0; i < new_audio_tracks.size(); ++i) { |
| const String& new_track_id = new_video_tracks[i].id; |
| if (new_track_id != |
| String(audioTracks().AnonymousIndexedGetter(i)->id())) { |
| tracks_match_first_init_segment = false; |
| break; |
| } |
| } |
| } |
| |
| if (tracks_match_first_init_segment && new_video_tracks.size() > 1) { |
| for (wtf_size_t i = 0; i < new_video_tracks.size(); ++i) { |
| const String& new_track_id = new_video_tracks[i].id; |
| if (new_track_id != |
| String(videoTracks().AnonymousIndexedGetter(i)->id())) { |
| tracks_match_first_init_segment = false; |
| break; |
| } |
| } |
| } |
| |
| if (!tracks_match_first_init_segment) { |
| DVLOG(3) << __func__ << " this=" << this |
| << " failed: tracks mismatch the first init segment."; |
| // The append error algorithm will be called at the top level after we |
| // return false here to indicate failure. |
| return false; |
| } |
| |
| // 3.2 Add the appropriate track descriptions from this initialization |
| // segment to each of the track buffers. This is done in Chromium code |
| // in stream parsers and demuxer implementations. |
| |
| // 3.3 Set the need random access point flag on all track buffers to true. |
| // This is done in Chromium code, see MediaSourceState::OnNewConfigs. |
| } |
| |
| // 4. Let active track flag equal false. |
| bool active_track = false; |
| |
| // 5. If the first initialization segment received flag is false, then run the |
| // following steps: |
| if (!first_initialization_segment_received_) { |
| // 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. |
| // This is done in Chromium code, see MediaSourceState::OnNewConfigs. |
| |
| // 5.2 For each audio track in the initialization segment, run following |
| // steps: |
| for (const MediaTrackInfo& track_info : new_audio_tracks) { |
| // 5.2.1 Let audio byte stream track ID be the Track ID for the current |
| // track being processed. |
| const auto& byte_stream_track_id = track_info.byte_stream_track_id; |
| // 5.2.2 Let audio language be a BCP 47 language tag for the language |
| // specified in the initialization segment for this track or an |
| // empty string if no language info is present. |
| WebString language = track_info.language; |
| // 5.2.3 If audio language equals an empty string or the 'und' BCP 47 |
| // value, then run the default track language algorithm with |
| // byteStreamTrackID set to audio byte stream track ID and type set |
| // to "audio" and assign the value returned by the algorithm to |
| // audio language. |
| if (language.IsEmpty() || language == "und") |
| language = DefaultTrackLanguage(TrackDefault::AudioKeyword(), |
| byte_stream_track_id); |
| // 5.2.4 Let audio label be a label specified in the initialization |
| // segment for this track or an empty string if no label info is |
| // present. |
| WebString label = track_info.label; |
| // 5.3.5 If audio label equals an empty string, then run the default track |
| // label algorithm with byteStreamTrackID set to audio byte stream |
| // track ID and type set to "audio" and assign the value returned by |
| // the algorithm to audio label. |
| if (label.IsEmpty()) |
| label = DefaultTrackLabel(TrackDefault::AudioKeyword(), |
| byte_stream_track_id); |
| // 5.2.6 Let audio kinds be an array of kind strings specified in the |
| // initialization segment for this track or an empty array if no |
| // kind information is provided. |
| const auto& kind = track_info.kind; |
| // 5.2.7 TODO(servolk): Implement track kind processing. |
| // 5.2.8.2 Let new audio track be a new AudioTrack object. |
| auto* audio_track = MakeGarbageCollected<AudioTrack>( |
| track_info.id, kind, label, language, false); |
| SourceBufferTrackBaseSupplement::SetSourceBuffer(*audio_track, this); |
| // 5.2.8.7 If audioTracks.length equals 0, then run the following steps: |
| if (audioTracks().length() == 0) { |
| // 5.2.8.7.1 Set the enabled property on new audio track to true. |
| audio_track->setEnabled(true); |
| // 5.2.8.7.2 Set active track flag to true. |
| active_track = true; |
| } |
| // 5.2.8.8 Add new audio track to the audioTracks attribute on this |
| // SourceBuffer object. |
| // 5.2.8.9 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().Add(audio_track); |
| // 5.2.8.10 Add new audio track to the audioTracks attribute on the |
| // HTMLMediaElement. |
| // 5.2.8.11 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. |
| source_->MediaElement()->audioTracks().Add(audio_track); |
| } |
| |
| // 5.3. For each video track in the initialization segment, run following |
| // steps: |
| for (const MediaTrackInfo& track_info : new_video_tracks) { |
| // 5.3.1 Let video byte stream track ID be the Track ID for the current |
| // track being processed. |
| const auto& byte_stream_track_id = track_info.byte_stream_track_id; |
| // 5.3.2 Let video language be a BCP 47 language tag for the language |
| // specified in the initialization segment for this track or an |
| // empty string if no language info is present. |
| WebString language = track_info.language; |
| // 5.3.3 If video language equals an empty string or the 'und' BCP 47 |
| // value, then run the default track language algorithm with |
| // byteStreamTrackID set to video byte stream track ID and type set |
| // to "video" and assign the value returned by the algorithm to |
| // video language. |
| if (language.IsEmpty() || language == "und") |
| language = DefaultTrackLanguage(TrackDefault::VideoKeyword(), |
| byte_stream_track_id); |
| // 5.3.4 Let video label be a label specified in the initialization |
| // segment for this track or an empty string if no label info is |
| // present. |
| WebString label = track_info.label; |
| // 5.3.5 If video label equals an empty string, then run the default track |
| // label algorithm with byteStreamTrackID set to video byte stream |
| // track ID and type set to "video" and assign the value returned by |
| // the algorithm to video label. |
| if (label.IsEmpty()) |
| label = DefaultTrackLabel(TrackDefault::VideoKeyword(), |
| byte_stream_track_id); |
| // 5.3.6 Let video kinds be an array of kind strings specified in the |
| // initialization segment for this track or an empty array if no |
| // kind information is provided. |
| const auto& kind = track_info.kind; |
| // 5.3.7 TODO(servolk): Implement track kind processing. |
| // 5.3.8.2 Let new video track be a new VideoTrack object. |
| auto* video_track = MakeGarbageCollected<VideoTrack>( |
| track_info.id, kind, label, language, false); |
| SourceBufferTrackBaseSupplement::SetSourceBuffer(*video_track, this); |
| // 5.3.8.7 If videoTracks.length equals 0, then run the following steps: |
| if (videoTracks().length() == 0) { |
| // 5.3.8.7.1 Set the selected property on new audio track to true. |
| video_track->setSelected(true); |
| // 5.3.8.7.2 Set active track flag to true. |
| active_track = true; |
| } |
| // 5.3.8.8 Add new video track to the videoTracks attribute on this |
| // SourceBuffer object. |
| // 5.3.8.9 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().Add(video_track); |
| // 5.3.8.10 Add new video track to the videoTracks attribute on the |
| // HTMLMediaElement. |
| // 5.3.8.11 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. |
| source_->MediaElement()->videoTracks().Add(video_track); |
| } |
| |
| // 5.4 TODO(servolk): Add text track processing here. |
| |
| // 5.5 If active track flag equals true, then run the following steps: |
| // activesourcebuffers. |
| if (active_track) { |
| // 5.5.1 Add this SourceBuffer to activeSourceBuffers. |
| // 5.5.2 Queue a task to fire a simple event named addsourcebuffer at |
| // activeSourceBuffers |
| source_->SetSourceBufferActive(this, true); |
| } |
| |
| // 5.6. Set first initialization segment received flag to true. |
| first_initialization_segment_received_ = true; |
| } |
| |
| return true; |
| } |
| |
| void SourceBuffer::NotifyParseWarning(const ParseWarning warning) { |
| switch (warning) { |
| case WebSourceBufferClient::kKeyframeTimeGreaterThanDependant: |
| // Report this problematic GOP structure to help inform follow-up work. |
| // Media engine also records RAPPOR for these, up to once per track, at |
| // Media.OriginUrl.MSE.KeyframeTimeGreaterThanDependant. |
| // TODO(wolenetz): Use the data to scope additional work. See |
| // https://crbug.com/739931. |
| UseCounter::Count( |
| source_->MediaElement()->GetDocument(), |
| WebFeature::kMediaSourceKeyframeTimeGreaterThanDependant); |
| break; |
| case WebSourceBufferClient::kMuxedSequenceMode: |
| // Report this problematic API usage to help inform follow-up work. |
| // Media engine also records RAPPOR for these, up to once per |
| // SourceBuffer, at Media.OriginUrl.MSE.MuxedSequenceModeSourceBuffer. |
| // TODO(wolenetz): Use the data to scope additional work. See |
| // https://crbug.com/737757. |
| UseCounter::Count(source_->MediaElement()->GetDocument(), |
| WebFeature::kMediaSourceMuxedSequenceMode); |
| break; |
| case WebSourceBufferClient::kGroupEndTimestampDecreaseWithinMediaSegment: |
| // Report this problematic Media Segment structure usage to help inform |
| // follow-up work. |
| // TODO(wolenetz): Use the data to scope additional work. See |
| // https://crbug.com/920853 and |
| // https://github.com/w3c/media-source/issues/203. |
| UseCounter::Count( |
| source_->MediaElement()->GetDocument(), |
| WebFeature::kMediaSourceGroupEndTimestampDecreaseWithinMediaSegment); |
| break; |
| } |
| } |
| |
| bool SourceBuffer::HasPendingActivity() const { |
| return updating_ || append_buffer_async_task_handle_.IsActive() || |
| remove_async_task_handle_.IsActive() || |
| (async_event_queue_ && async_event_queue_->HasPendingEvents()); |
| } |
| |
| void SourceBuffer::ContextDestroyed(ExecutionContext*) { |
| append_buffer_async_task_handle_.Cancel(); |
| remove_async_task_handle_.Cancel(); |
| updating_ = false; |
| } |
| |
| ExecutionContext* SourceBuffer::GetExecutionContext() const { |
| return ContextLifecycleObserver::GetExecutionContext(); |
| } |
| |
| const AtomicString& SourceBuffer::InterfaceName() const { |
| return event_target_names::kSourceBuffer; |
| } |
| |
| bool SourceBuffer::IsRemoved() const { |
| return !source_; |
| } |
| |
| void SourceBuffer::ScheduleEvent(const AtomicString& event_name) { |
| DCHECK(async_event_queue_); |
| |
| Event* event = Event::Create(event_name); |
| event->SetTarget(this); |
| |
| async_event_queue_->EnqueueEvent(FROM_HERE, *event); |
| } |
| |
| bool SourceBuffer::PrepareAppend(double media_time, |
| size_t new_data_size, |
| ExceptionState& exception_state) { |
| TRACE_EVENT_ASYNC_BEGIN0("media", "SourceBuffer::prepareAppend", this); |
| // http://w3c.github.io/media-source/#sourcebuffer-prepare-append |
| // 3.5.4 Prepare Append Algorithm |
| // 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 (ThrowExceptionIfRemovedOrUpdating(IsRemoved(), updating_, |
| exception_state)) { |
| TRACE_EVENT_ASYNC_END0("media", "SourceBuffer::prepareAppend", this); |
| return false; |
| } |
| |
| // 3. If the HTMLMediaElement.error attribute is not null, then throw an |
| // InvalidStateError exception and abort these steps. |
| DCHECK(source_); |
| DCHECK(source_->MediaElement()); |
| if (source_->MediaElement()->error()) { |
| MediaSource::LogAndThrowDOMException( |
| exception_state, DOMExceptionCode::kInvalidStateError, |
| "The HTMLMediaElement.error attribute is not null."); |
| TRACE_EVENT_ASYNC_END0("media", "SourceBuffer::prepareAppend", this); |
| return false; |
| } |
| |
| // 4. If the readyState attribute of the parent media source is in the "ended" |
| // state then run the following steps: |
| // 1. Set the readyState attribute of the parent media source to "open" |
| // 2. Queue a task to fire a simple event named sourceopen at the parent |
| // media source. |
| source_->OpenIfInEndedState(); |
| |
| // 5. Run the coded frame eviction algorithm. |
| if (!EvictCodedFrames(media_time, new_data_size)) { |
| // 6. If the buffer full flag equals true, then throw a QUOTA_EXCEEDED_ERR |
| // exception and abort these steps. |
| DVLOG(3) << __func__ << " this=" << this << " -> throw QuotaExceededError"; |
| MediaSource::LogAndThrowDOMException(exception_state, |
| DOMExceptionCode::kQuotaExceededError, |
| "The SourceBuffer is full, and cannot " |
| "free space to append additional " |
| "buffers."); |
| TRACE_EVENT_ASYNC_END0("media", "SourceBuffer::prepareAppend", this); |
| return false; |
| } |
| |
| TRACE_EVENT_ASYNC_END0("media", "SourceBuffer::prepareAppend", this); |
| return true; |
| } |
| |
| bool SourceBuffer::EvictCodedFrames(double media_time, size_t new_data_size) { |
| DCHECK(source_); |
| DCHECK(source_->MediaElement()); |
| |
| // Nothing to do if the mediaElement does not yet have frames to evict. |
| if (source_->MediaElement()->getReadyState() < |
| HTMLMediaElement::kHaveMetadata) { |
| return true; |
| } |
| |
| bool result = web_source_buffer_->EvictCodedFrames(media_time, new_data_size); |
| if (!result) { |
| DVLOG(3) << __func__ << " this=" << this |
| << " failed. newDataSize=" << new_data_size |
| << " media_time=" << media_time << " buffered=" |
| << WebTimeRangesToString(web_source_buffer_->Buffered()); |
| } |
| return result; |
| } |
| |
| void SourceBuffer::AppendBufferInternal(double media_time, |
| const unsigned char* data, |
| unsigned size, |
| ExceptionState& exception_state) { |
| TRACE_EVENT_ASYNC_BEGIN1("media", "SourceBuffer::appendBuffer", this, "size", |
| 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 |
| |
| // 1. Run the prepare append algorithm. |
| if (!PrepareAppend(media_time, size, exception_state)) { |
| TRACE_EVENT_ASYNC_END0("media", "SourceBuffer::appendBuffer", this); |
| return; |
| } |
| TRACE_EVENT_ASYNC_STEP_INTO0("media", "SourceBuffer::appendBuffer", this, |
| "prepareAppend"); |
| |
| // 2. Add data to the end of the input buffer. |
| DCHECK(data || size == 0); |
| if (data) |
| pending_append_data_.Append(data, size); |
| pending_append_data_offset_ = 0; |
| |
| // 3. Set the updating attribute to true. |
| updating_ = true; |
| |
| // 4. Queue a task to fire a simple event named updatestart at this |
| // SourceBuffer object. |
| ScheduleEvent(event_type_names::kUpdatestart); |
| |
| // 5. Asynchronously run the buffer append algorithm. |
| append_buffer_async_task_handle_ = PostCancellableTask( |
| *GetExecutionContext()->GetTaskRunner(TaskType::kMediaElementEvent), |
| FROM_HERE, |
| WTF::Bind(&SourceBuffer::AppendBufferAsyncPart, WrapPersistent(this))); |
| |
| TRACE_EVENT_ASYNC_STEP_INTO0("media", "SourceBuffer::appendBuffer", this, |
| "initialDelay"); |
| } |
| |
| void SourceBuffer::AppendBufferAsyncPart() { |
| DCHECK(updating_); |
| |
| // Section 3.5.4 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. |
| // Step 2 doesn't apply since we run Step 1 synchronously here. |
| DCHECK_GE(pending_append_data_.size(), pending_append_data_offset_); |
| wtf_size_t append_size = |
| pending_append_data_.size() - pending_append_data_offset_; |
| |
| // Impose an arbitrary max size for a single append() call so that an append |
| // doesn't block the renderer event loop very long. This value was selected |
| // by looking at YouTube SourceBuffer usage across a variety of bitrates. |
| // This value allows relatively large appends while keeping append() call |
| // duration in the ~5-15ms range. |
| const wtf_size_t kMaxAppendSize = 128 * 1024; |
| if (append_size > kMaxAppendSize) |
| append_size = kMaxAppendSize; |
| |
| TRACE_EVENT_ASYNC_STEP_INTO1("media", "SourceBuffer::appendBuffer", this, |
| "appending", "appendSize", append_size); |
| |
| // |zero| is used for 0 byte appends so we always have a valid pointer. |
| // We need to convey all appends, even 0 byte ones to |m_webSourceBuffer| |
| // so that it can clear its end of stream state if necessary. |
| unsigned char zero = 0; |
| unsigned char* append_data = &zero; |
| if (append_size) |
| append_data = pending_append_data_.data() + pending_append_data_offset_; |
| |
| bool append_success = |
| web_source_buffer_->Append(append_data, append_size, ×tamp_offset_); |
| |
| if (!append_success) { |
| pending_append_data_.clear(); |
| pending_append_data_offset_ = 0; |
| AppendError(); |
| } else { |
| pending_append_data_offset_ += append_size; |
| |
| if (pending_append_data_offset_ < pending_append_data_.size()) { |
| append_buffer_async_task_handle_ = PostCancellableTask( |
| *GetExecutionContext()->GetTaskRunner(TaskType::kMediaElementEvent), |
| FROM_HERE, |
| WTF::Bind(&SourceBuffer::AppendBufferAsyncPart, |
| WrapPersistent(this))); |
| TRACE_EVENT_ASYNC_STEP_INTO0("media", "SourceBuffer::appendBuffer", this, |
| "nextPieceDelay"); |
| return; |
| } |
| |
| // 3. Set the updating attribute to false. |
| updating_ = false; |
| pending_append_data_.clear(); |
| pending_append_data_offset_ = 0; |
| |
| // 4. Queue a task to fire a simple event named update at this SourceBuffer |
| // object. |
| ScheduleEvent(event_type_names::kUpdate); |
| |
| // 5. Queue a task to fire a simple event named updateend at this |
| // SourceBuffer object. |
| ScheduleEvent(event_type_names::kUpdateend); |
| } |
| |
| TRACE_EVENT_ASYNC_END0("media", "SourceBuffer::appendBuffer", this); |
| double media_time = GetMediaTime(); |
| DVLOG(3) << __func__ << " done. this=" << this << " media_time=" << media_time |
| << " buffered=" |
| << WebTimeRangesToString(web_source_buffer_->Buffered()); |
| } |
| |
| void SourceBuffer::RemoveAsyncPart() { |
| DCHECK(updating_); |
| DCHECK_GE(pending_remove_start_, 0); |
| DCHECK_LT(pending_remove_start_, pending_remove_end_); |
| |
| // Section 3.2 remove() method steps |
| // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-SourceBuffer-remove-void-double-start-double-end |
| |
| // 9. Run the coded frame removal algorithm with start and end as the start |
| // and end of the removal range. |
| web_source_buffer_->Remove(pending_remove_start_, pending_remove_end_); |
| |
| // 10. Set the updating attribute to false. |
| updating_ = false; |
| pending_remove_start_ = -1; |
| pending_remove_end_ = -1; |
| |
| // 11. Queue a task to fire a simple event named update at this SourceBuffer |
| // object. |
| ScheduleEvent(event_type_names::kUpdate); |
| |
| // 12. Queue a task to fire a simple event named updateend at this |
| // SourceBuffer object. |
| ScheduleEvent(event_type_names::kUpdateend); |
| } |
| |
| void SourceBuffer::AppendError() { |
| DVLOG(3) << __func__ << " this=" << this; |
| // Section 3.5.3 Append Error Algorithm |
| // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#sourcebuffer-append-error |
| |
| // 1. Run the reset parser state algorithm. |
| web_source_buffer_->ResetParserState(); |
| |
| // 2. Set the updating attribute to false. |
| updating_ = false; |
| |
| // 3. Queue a task to fire a simple event named error at this SourceBuffer |
| // object. |
| ScheduleEvent(event_type_names::kError); |
| |
| // 4. Queue a task to fire a simple event named updateend at this SourceBuffer |
| // object. |
| ScheduleEvent(event_type_names::kUpdateend); |
| |
| // 5. If decode error is true, then run the end of stream algorithm with the |
| // error parameter set to "decode". |
| source_->EndOfStreamAlgorithm(WebMediaSource::kEndOfStreamStatusDecodeError); |
| } |
| |
| void SourceBuffer::Trace(blink::Visitor* visitor) { |
| visitor->Trace(source_); |
| visitor->Trace(track_defaults_); |
| visitor->Trace(async_event_queue_); |
| visitor->Trace(audio_tracks_); |
| visitor->Trace(video_tracks_); |
| EventTargetWithInlineData::Trace(visitor); |
| ContextLifecycleObserver::Trace(visitor); |
| } |
| |
| } // namespace blink |