#include "config.h"
#include "modules/mediasource/SourceBuffer.h"
+#include "bindings/v8/ExceptionMessages.h"
#include "bindings/v8/ExceptionState.h"
#include "core/dom/ExceptionCode.h"
#include "core/dom/ExecutionContext.h"
namespace WebCore {
-PassRefPtr<SourceBuffer> SourceBuffer::create(PassOwnPtr<WebSourceBuffer> webSourceBuffer, MediaSource* source, GenericEventQueue* asyncEventQueue)
+namespace {
+
+static bool throwExceptionIfRemovedOrUpdating(bool isRemoved, bool isUpdating, ExceptionState& exceptionState)
+{
+ if (isRemoved) {
+ exceptionState.throwDOMException(InvalidStateError, "This SourceBuffer has been removed from the parent media source.");
+ return true;
+ }
+ if (isUpdating) {
+ exceptionState.throwDOMException(InvalidStateError, "This SourceBuffer is still processing an 'appendBuffer', 'appendStream', or 'remove' operation.");
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace
+
+SourceBuffer* SourceBuffer::create(PassOwnPtr<WebSourceBuffer> webSourceBuffer, MediaSource* source, GenericEventQueue* asyncEventQueue)
{
- RefPtr<SourceBuffer> sourceBuffer(adoptRef(new SourceBuffer(webSourceBuffer, source, asyncEventQueue)));
+ SourceBuffer* sourceBuffer(adoptRefCountedGarbageCollected(new SourceBuffer(webSourceBuffer, source, asyncEventQueue)));
sourceBuffer->suspendIfNeeded();
- return sourceBuffer.release();
+ return sourceBuffer;
}
SourceBuffer::SourceBuffer(PassOwnPtr<WebSourceBuffer> webSourceBuffer, MediaSource* source, GenericEventQueue* asyncEventQueue)
, m_timestampOffset(0)
, m_appendWindowStart(0)
, m_appendWindowEnd(std::numeric_limits<double>::infinity())
+ , m_pendingAppendDataOffset(0)
, m_appendBufferAsyncPartRunner(this, &SourceBuffer::appendBufferAsyncPart)
, m_pendingRemoveStart(-1)
, m_pendingRemoveEnd(-1)
{
// Section 3.1 On setting mode attribute steps.
// 1. Let new mode equal the new value being assigned to this attribute.
- // 2. If new mode does not equal "segments" or "sequence", then throw an INVALID_ACCESS_ERR exception and abort
- // these steps.
- if (newMode != segmentsKeyword() && newMode != sequenceKeyword()) {
- exceptionState.throwUninformativeAndGenericDOMException(InvalidAccessError);
- return;
- }
-
- // 3. If this object has been removed from the sourceBuffers attribute of the parent media source, then throw
+ // 2. 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.
- // 4. If the updating attribute equals true, then throw an INVALID_STATE_ERR exception and abort these steps.
- if (isRemoved() || m_updating) {
- exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError);
+ // 3. If the updating attribute equals true, then throw an INVALID_STATE_ERR exception and abort these steps.
+ if (throwExceptionIfRemovedOrUpdating(isRemoved(), m_updating, exceptionState))
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.
+ // 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();
- // 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.
+ // 5. If the append state equals PARSING_MEDIA_SEGMENT, then throw an INVALID_STATE_ERR and abort these steps.
+ // 6. If the new mode equals "sequence", then set the group start timestamp to the highest presentation end timestamp.
WebSourceBuffer::AppendMode appendMode = WebSourceBuffer::AppendModeSegments;
if (newMode == sequenceKeyword())
appendMode = WebSourceBuffer::AppendModeSequence;
if (!m_webSourceBuffer->setMode(appendMode)) {
- exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError);
+ exceptionState.throwDOMException(InvalidStateError, "The mode may not be set while the SourceBuffer's append state is 'PARSING_MEDIA_SEGMENT'.");
return;
}
- // 8. Update the attribute to new mode.
+ // 7. Update the attribute to new mode.
m_mode = newMode;
}
// 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()) {
- exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError);
- return 0;
+ exceptionState.throwDOMException(InvalidStateError, "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.
void SourceBuffer::setTimestampOffset(double offset, ExceptionState& exceptionState)
{
// 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 (isRemoved() || m_updating) {
- exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError);
+ if (throwExceptionIfRemovedOrUpdating(isRemoved(), m_updating, exceptionState))
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"
// 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 (!m_webSourceBuffer->setTimestampOffset(offset)) {
- exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError);
+ exceptionState.throwDOMException(InvalidStateError, "The timestamp offset may not be set while the SourceBuffer's append state is 'PARSING_MEDIA_SEGMENT'.");
return;
}
void SourceBuffer::setAppendWindowStart(double start, ExceptionState& exceptionState)
{
- // Enforce throwing an exception on restricted double values.
- if (std::isnan(start)
- || start == std::numeric_limits<double>::infinity()
- || start == -std::numeric_limits<double>::infinity()) {
- exceptionState.throwUninformativeAndGenericDOMException(TypeMismatchError);
- return;
- }
-
// Section 3.1 appendWindowStart attribute setter steps.
+ // https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#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 (isRemoved() || m_updating) {
- exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError);
+ if (throwExceptionIfRemovedOrUpdating(isRemoved(), m_updating, exceptionState))
return;
- }
// 3. If the new value is less than 0 or greater than or equal to appendWindowEnd then throw an InvalidAccessError
// exception and abort these steps.
if (start < 0 || start >= m_appendWindowEnd) {
- exceptionState.throwUninformativeAndGenericDOMException(InvalidAccessError);
+ exceptionState.throwDOMException(InvalidAccessError, ExceptionMessages::indexOutsideRange("value", start, 0.0, ExceptionMessages::ExclusiveBound, m_appendWindowEnd, ExceptionMessages::InclusiveBound));
return;
}
void SourceBuffer::setAppendWindowEnd(double end, ExceptionState& exceptionState)
{
// Section 3.1 appendWindowEnd attribute setter steps.
+ // https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#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 (isRemoved() || m_updating) {
- exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError);
+ if (throwExceptionIfRemovedOrUpdating(isRemoved(), m_updating, exceptionState))
return;
- }
// 3. If the new value equals NaN, then throw an InvalidAccessError and abort these steps.
+ if (std::isnan(end)) {
+ exceptionState.throwDOMException(InvalidAccessError, ExceptionMessages::notAFiniteNumber(end));
+ return;
+ }
// 4. If the new value is less than or equal to appendWindowStart then throw an InvalidAccessError
// exception and abort these steps.
- if (std::isnan(end) || end <= m_appendWindowStart) {
- exceptionState.throwUninformativeAndGenericDOMException(InvalidAccessError);
+ if (end <= m_appendWindowStart) {
+ // FIXME: Use ExceptionState::indexExceedsMinimumBound() once it lands.
+ exceptionState.throwDOMException(InvalidAccessError, "The value provided ('" + String::number(end) + "') is less than or equal to the minimum value (" + String::number(m_appendWindowStart) + ").");
return;
}
{
// 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. If data is null then throw an InvalidAccessError exception and abort these steps.
- if (!data) {
- exceptionState.throwUninformativeAndGenericDOMException(InvalidAccessError);
- return;
- }
-
appendBufferInternal(static_cast<const unsigned char*>(data->data()), data->byteLength(), exceptionState);
}
{
// 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. If data is null then throw an InvalidAccessError exception and abort these steps.
- if (!data) {
- exceptionState.throwUninformativeAndGenericDOMException(InvalidAccessError);
- return;
- }
-
appendBufferInternal(static_cast<const unsigned char*>(data->baseAddress()), data->byteLength(), exceptionState);
}
-void SourceBuffer::appendStream(PassRefPtr<Stream> stream, ExceptionState& exceptionState)
+void SourceBuffer::appendStream(PassRefPtrWillBeRawPtr<Stream> stream, ExceptionState& exceptionState)
{
m_streamMaxSizeValid = false;
appendStreamInternal(stream, exceptionState);
}
-void SourceBuffer::appendStream(PassRefPtr<Stream> stream, unsigned long long maxSize, ExceptionState& exceptionState)
+void SourceBuffer::appendStream(PassRefPtrWillBeRawPtr<Stream> stream, unsigned long long maxSize, ExceptionState& exceptionState)
{
m_streamMaxSizeValid = maxSize > 0;
if (m_streamMaxSizeValid)
// 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()) {
- exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError);
+ if (isRemoved()) {
+ exceptionState.throwDOMException(InvalidStateError, "This SourceBuffer has been removed from the parent media source.");
+ return;
+ }
+ if (!m_source->isOpen()) {
+ exceptionState.throwDOMException(InvalidStateError, "The parent media source's readyState is not 'open'.");
return;
}
// Section 3.2 remove() method steps.
// 1. If start is negative or greater than duration, then throw an InvalidAccessError exception and abort these steps.
// 2. If end is less than or equal to start, then throw an InvalidAccessError exception and abort these steps.
- if (start < 0 || (m_source && (std::isnan(m_source->duration()) || start > m_source->duration())) || end <= start) {
- exceptionState.throwUninformativeAndGenericDOMException(InvalidAccessError);
+
+ if (start < 0 || (m_source && (std::isnan(m_source->duration()) || start > m_source->duration()))) {
+ exceptionState.throwDOMException(InvalidAccessError, ExceptionMessages::indexOutsideRange("start", start, 0.0, ExceptionMessages::ExclusiveBound, !m_source || std::isnan(m_source->duration()) ? 0 : m_source->duration(), ExceptionMessages::ExclusiveBound));
+ return;
+ }
+ if (end <= start) {
+ exceptionState.throwDOMException(InvalidAccessError, "The end value provided (" + String::number(end) + ") must be greater than the start value provided (" + String::number(start) + ").");
return;
}
// 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) {
- exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError);
+ if (throwExceptionIfRemovedOrUpdating(isRemoved(), m_updating, exceptionState))
return;
- }
TRACE_EVENT_ASYNC_BEGIN0("media", "SourceBuffer::remove", this);
// 3.1. Abort the buffer append and stream append loop algorithms if they are running.
m_appendBufferAsyncPartRunner.stop();
m_pendingAppendData.clear();
+ m_pendingAppendDataOffset = 0;
m_removeAsyncPartRunner.stop();
m_pendingRemoveStart = -1;
m_webSourceBuffer->removedFromMediaSource();
m_webSourceBuffer.clear();
- m_source = 0;
+ m_source = nullptr;
m_asyncEventQueue = 0;
}
{
ASSERT(m_asyncEventQueue);
- RefPtr<Event> event = Event::create(eventName);
+ RefPtrWillBeRawPtr<Event> event = Event::create(eventName);
event->setTarget(this);
m_asyncEventQueue->enqueueEvent(event.release());
// 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. 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) {
- exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError);
+ // 1. Run the prepare append algorithm.
+ // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#sourcebuffer-prepare-append
+ // 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(), m_updating, exceptionState))
return;
- }
- TRACE_EVENT_ASYNC_BEGIN0("media", "SourceBuffer::appendBuffer", this);
+ TRACE_EVENT_ASYNC_BEGIN1("media", "SourceBuffer::appendBuffer", this, "size", size);
- // 4. If the readyState attribute of the parent media source is in the "ended" state then run the following steps: ...
+ // 3. If the readyState attribute of the parent media source is in the "ended" state then run the following steps: ...
m_source->openIfInEndedState();
- // Steps 5-6
+ // Steps 4-5 - end "prepare append" algorithm.
- // 7. Add data to the end of the input buffer.
- m_pendingAppendData.append(data, size);
+ // 2. Add data to the end of the input buffer.
+ ASSERT(data || size == 0);
+ if (data)
+ m_pendingAppendData.append(data, size);
+ m_pendingAppendDataOffset = 0;
- // 8. Set the updating attribute to true.
+ // 3. Set the updating attribute to true.
m_updating = true;
- // 9. Queue a task to fire a simple event named updatestart at this SourceBuffer object.
+ // 4. Queue a task to fire a simple event named updatestart at this SourceBuffer object.
scheduleEvent(EventTypeNames::updatestart);
- // 10. Asynchronously run the buffer append algorithm.
+ // 5. Asynchronously run the buffer append algorithm.
m_appendBufferAsyncPartRunner.runAsync();
- TRACE_EVENT_ASYNC_STEP_INTO0("media", "SourceBuffer::appendBuffer", this, "waiting");
+ TRACE_EVENT_ASYNC_STEP_INTO0("media", "SourceBuffer::appendBuffer", this, "initialDelay");
}
void SourceBuffer::appendBufferAsyncPart()
{
ASSERT(m_updating);
- TRACE_EVENT_ASYNC_STEP_INTO0("media", "SourceBuffer::appendBuffer", this, "appending");
-
// 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.
- size_t appendSize = m_pendingAppendData.size();
- if (!appendSize) {
- // Resize buffer 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.
- m_pendingAppendData.resize(1);
+ ASSERT(m_pendingAppendData.size() >= m_pendingAppendDataOffset);
+ size_t appendSize = m_pendingAppendData.size() - m_pendingAppendDataOffset;
+
+ // 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 size_t MaxAppendSize = 128 * 1024;
+ if (appendSize > MaxAppendSize)
+ appendSize = MaxAppendSize;
+
+ TRACE_EVENT_ASYNC_STEP_INTO1("media", "SourceBuffer::appendBuffer", this, "appending", "appendSize", static_cast<unsigned>(appendSize));
+
+ // |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* appendData = &zero;
+ if (appendSize)
+ appendData = m_pendingAppendData.data() + m_pendingAppendDataOffset;
+
+ m_webSourceBuffer->append(appendData, appendSize, &m_timestampOffset);
+
+ m_pendingAppendDataOffset += appendSize;
+
+ if (m_pendingAppendDataOffset < m_pendingAppendData.size()) {
+ m_appendBufferAsyncPartRunner.runAsync();
+ TRACE_EVENT_ASYNC_STEP_INTO0("media", "SourceBuffer::appendBuffer", this, "nextPieceDelay");
+ return;
}
- m_webSourceBuffer->append(m_pendingAppendData.data(), appendSize);
// 3. Set the updating attribute to false.
m_updating = false;
m_pendingAppendData.clear();
+ m_pendingAppendDataOffset = 0;
// 4. Queue a task to fire a simple event named update at this SourceBuffer object.
scheduleEvent(EventTypeNames::update);
scheduleEvent(EventTypeNames::updateend);
}
-void SourceBuffer::appendStreamInternal(PassRefPtr<Stream> stream, ExceptionState& exceptionState)
+void SourceBuffer::appendStreamInternal(PassRefPtrWillBeRawPtr<Stream> stream, ExceptionState& exceptionState)
{
// Section 3.2 appendStream()
// https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-SourceBuffer-appendStream-void-Stream-stream-unsigned-long-long-maxSize
- // 1. If stream is null then throw an InvalidAccessError exception and abort these steps.
- if (!stream || stream->isNeutered()) {
- exceptionState.throwUninformativeAndGenericDOMException(InvalidAccessError);
+ // (0. If the stream has been neutered, then throw an InvalidAccessError exception and abort these steps.)
+ if (stream->isNeutered()) {
+ exceptionState.throwDOMException(InvalidAccessError, "The stream provided has been neutered.");
return;
}
- // 2. Run the prepare append algorithm.
+ // 1. Run the prepare append algorithm.
// Section 3.5.4 Prepare Append Algorithm.
// https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#sourcebuffer-prepare-append
// 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) {
- exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError);
+ if (throwExceptionIfRemovedOrUpdating(isRemoved(), m_updating, exceptionState))
return;
- }
TRACE_EVENT_ASYNC_BEGIN0("media", "SourceBuffer::appendStream", this);
// Steps 4-5 of the prepare append algorithm are handled by m_webSourceBuffer.
- // 3. Set the updating attribute to true.
+ // 2. Set the updating attribute to true.
m_updating = true;
- // 4. Queue a task to fire a simple event named updatestart at this SourceBuffer object.
+ // 3. Queue a task to fire a simple event named updatestart at this SourceBuffer object.
scheduleEvent(EventTypeNames::updatestart);
- // 5. Asynchronously run the stream append loop algorithm with stream and maxSize.
+ // 4. Asynchronously run the stream append loop algorithm with stream and maxSize.
stream->neuter();
m_loader = adoptPtr(new FileReaderLoader(FileReaderLoader::ReadByClient, this));
m_streamMaxSizeValid = false;
m_streamMaxSize = 0;
m_loader.clear();
- m_stream = 0;
+ m_stream = nullptr;
}
void SourceBuffer::didStartLoading()
WTF_LOG(Media, "SourceBuffer::didReceiveDataForClient(%d) %p", dataLength, this);
ASSERT(m_updating);
ASSERT(m_loader);
-
- m_webSourceBuffer->append(reinterpret_cast<const unsigned char*>(data), dataLength);
+ m_webSourceBuffer->append(reinterpret_cast<const unsigned char*>(data), dataLength, &m_timestampOffset);
}
void SourceBuffer::didFinishLoading()
appendStreamDone(false);
}
+void SourceBuffer::trace(Visitor* visitor)
+{
+ visitor->trace(m_source);
+ visitor->trace(m_stream);
+}
+
} // namespace WebCore