/* * 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 "config.h" #include "modules/mediasource/SourceBuffer.h" #include "bindings/v8/ExceptionState.h" #include "core/dom/ExceptionCode.h" #include "core/dom/ExecutionContext.h" #include "core/events/Event.h" #include "core/events/GenericEventQueue.h" #include "core/fileapi/FileReaderLoader.h" #include "core/fileapi/Stream.h" #include "core/html/TimeRanges.h" #include "modules/mediasource/MediaSource.h" #include "platform/Logging.h" #include "platform/TraceEvent.h" #include "public/platform/WebSourceBuffer.h" #include "wtf/ArrayBuffer.h" #include "wtf/ArrayBufferView.h" #include "wtf/MathExtras.h" #include using blink::WebSourceBuffer; namespace WebCore { PassRefPtr SourceBuffer::create(PassOwnPtr webSourceBuffer, MediaSource* source, GenericEventQueue* asyncEventQueue) { RefPtr sourceBuffer(adoptRef(new SourceBuffer(webSourceBuffer, source, asyncEventQueue))); sourceBuffer->suspendIfNeeded(); return sourceBuffer.release(); } SourceBuffer::SourceBuffer(PassOwnPtr webSourceBuffer, MediaSource* source, GenericEventQueue* asyncEventQueue) : ActiveDOMObject(source->executionContext()) , m_webSourceBuffer(webSourceBuffer) , m_source(source) , m_asyncEventQueue(asyncEventQueue) , m_updating(false) , m_timestampOffset(0) , m_appendWindowStart(0) , m_appendWindowEnd(std::numeric_limits::infinity()) , m_appendBufferAsyncPartRunner(this, &SourceBuffer::appendBufferAsyncPart) , m_pendingRemoveStart(-1) , m_pendingRemoveEnd(-1) , m_removeAsyncPartRunner(this, &SourceBuffer::removeAsyncPart) , m_streamMaxSizeValid(false) , m_streamMaxSize(0) , m_appendStreamAsyncPartRunner(this, &SourceBuffer::appendStreamAsyncPart) { ASSERT(m_webSourceBuffer); ASSERT(m_source); ScriptWrappable::init(this); } SourceBuffer::~SourceBuffer() { ASSERT(isRemoved()); ASSERT(!m_loader); ASSERT(!m_stream); } PassRefPtr SourceBuffer::buffered(ExceptionState& exceptionState) 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()) { exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError); return 0; } // 2. Return a new static normalized TimeRanges object for the media segments buffered. return TimeRanges::create(m_webSourceBuffer->buffered()); } double SourceBuffer::timestampOffset() const { return m_timestampOffset; } void SourceBuffer::setTimestampOffset(double offset, ExceptionState& exceptionState) { // Section 3.1 timestampOffset attribute setter steps. // 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); 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. m_source->openIfInEndedState(); // 5. If this object is waiting for the end of a media segment to be appended, then throw an InvalidStateError // and abort these steps. // // FIXME: Add step 6 text when mode attribute is implemented. if (!m_webSourceBuffer->setTimestampOffset(offset)) { exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError); return; } // 7. Update the attribute to new timestamp offset. m_timestampOffset = offset; } double SourceBuffer::appendWindowStart() const { return m_appendWindowStart; } void SourceBuffer::setAppendWindowStart(double start, ExceptionState& exceptionState) { // Enforce throwing an exception on restricted double values. if (std::isnan(start) || start == std::numeric_limits::infinity() || start == -std::numeric_limits::infinity()) { exceptionState.throwUninformativeAndGenericDOMException(TypeMismatchError); return; } // Section 3.1 appendWindowStart attribute setter 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) { exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError); 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); return; } m_webSourceBuffer->setAppendWindowStart(start); // 4. Update the attribute to the new value. m_appendWindowStart = start; } double SourceBuffer::appendWindowEnd() const { return m_appendWindowEnd; } void SourceBuffer::setAppendWindowEnd(double end, ExceptionState& exceptionState) { // Section 3.1 appendWindowEnd attribute setter 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) { exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError); return; } // 3. If the new value equals NaN, then throw an InvalidAccessError and abort these steps. // 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); return; } m_webSourceBuffer->setAppendWindowEnd(end); // 5. Update the attribute to the new value. m_appendWindowEnd = end; } void SourceBuffer::appendBuffer(PassRefPtr data, ExceptionState& 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(data->data()), data->byteLength(), exceptionState); } void SourceBuffer::appendBuffer(PassRefPtr data, ExceptionState& 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(data->baseAddress()), data->byteLength(), exceptionState); } void SourceBuffer::appendStream(PassRefPtr stream, ExceptionState& exceptionState) { m_streamMaxSizeValid = false; appendStreamInternal(stream, exceptionState); } void SourceBuffer::appendStream(PassRefPtr stream, unsigned long long maxSize, ExceptionState& exceptionState) { m_streamMaxSizeValid = maxSize > 0; if (m_streamMaxSizeValid) m_streamMaxSize = maxSize; appendStreamInternal(stream, exceptionState); } void SourceBuffer::abort(ExceptionState& exceptionState) { // Section 3.2 abort() method steps. // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#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() || !m_source->isOpen()) { exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError); return; } // 3. If the sourceBuffer.updating attribute equals true, then run the following steps: ... abortIfUpdating(); // 4. Run the reset parser state algorithm. m_webSourceBuffer->abort(); // 5. Set appendWindowStart to 0. setAppendWindowStart(0, exceptionState); // 6. Set appendWindowEnd to positive Infinity. setAppendWindowEnd(std::numeric_limits::infinity(), exceptionState); } void SourceBuffer::remove(double start, double end, ExceptionState& exceptionState) { // 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); 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); return; } TRACE_EVENT_ASYNC_BEGIN0("media", "SourceBuffer::remove", this); // 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. Set the updating attribute to true. m_updating = true; // 7. Queue a task to fire a simple event named updatestart at this SourceBuffer object. scheduleEvent(EventTypeNames::updatestart); // 8. Return control to the caller and run the rest of the steps asynchronously. m_pendingRemoveStart = start; m_pendingRemoveEnd = end; m_removeAsyncPartRunner.runAsync(); } void SourceBuffer::abortIfUpdating() { // Section 3.2 abort() method step 3 substeps. // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-SourceBuffer-abort-void if (!m_updating) return; const char* traceEventName = 0; if (!m_pendingAppendData.isEmpty()) { traceEventName = "SourceBuffer::appendBuffer"; } else if (m_stream) { traceEventName = "SourceBuffer::appendStream"; } else if (m_pendingRemoveStart != -1) { traceEventName = "SourceBuffer::remove"; } else { ASSERT_NOT_REACHED(); } // 3.1. Abort the buffer append and stream append loop algorithms if they are running. m_appendBufferAsyncPartRunner.stop(); m_pendingAppendData.clear(); m_removeAsyncPartRunner.stop(); m_pendingRemoveStart = -1; m_pendingRemoveEnd = -1; m_appendStreamAsyncPartRunner.stop(); clearAppendStreamState(); // 3.2. Set the updating attribute to false. m_updating = false; // 3.3. Queue a task to fire a simple event named abort at this SourceBuffer object. scheduleEvent(EventTypeNames::abort); // 3.4. Queue a task to fire a simple event named updateend at this SourceBuffer object. scheduleEvent(EventTypeNames::updateend); TRACE_EVENT_ASYNC_END0("media", traceEventName, this); } void SourceBuffer::removedFromMediaSource() { if (isRemoved()) return; abortIfUpdating(); m_webSourceBuffer->removedFromMediaSource(); m_webSourceBuffer.clear(); m_source = 0; m_asyncEventQueue = 0; } bool SourceBuffer::hasPendingActivity() const { return m_source; } void SourceBuffer::suspend() { m_appendBufferAsyncPartRunner.suspend(); m_removeAsyncPartRunner.suspend(); m_appendStreamAsyncPartRunner.suspend(); } void SourceBuffer::resume() { m_appendBufferAsyncPartRunner.resume(); m_removeAsyncPartRunner.resume(); m_appendStreamAsyncPartRunner.resume(); } void SourceBuffer::stop() { m_appendBufferAsyncPartRunner.stop(); m_removeAsyncPartRunner.stop(); m_appendStreamAsyncPartRunner.stop(); } ExecutionContext* SourceBuffer::executionContext() const { return ActiveDOMObject::executionContext(); } const AtomicString& SourceBuffer::interfaceName() const { return EventTargetNames::SourceBuffer; } bool SourceBuffer::isRemoved() const { return !m_source; } void SourceBuffer::scheduleEvent(const AtomicString& eventName) { ASSERT(m_asyncEventQueue); RefPtr event = Event::create(eventName); event->setTarget(this); m_asyncEventQueue->enqueueEvent(event.release()); } void SourceBuffer::appendBufferInternal(const unsigned char* data, unsigned size, ExceptionState& 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 // 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); return; } TRACE_EVENT_ASYNC_BEGIN0("media", "SourceBuffer::appendBuffer", this); // 4. 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 // 7. Add data to the end of the input buffer. m_pendingAppendData.append(data, size); // 8. Set the updating attribute to true. m_updating = true; // 9. Queue a task to fire a simple event named updatestart at this SourceBuffer object. scheduleEvent(EventTypeNames::updatestart); // 10. Asynchronously run the buffer append algorithm. m_appendBufferAsyncPartRunner.runAsync(); TRACE_EVENT_ASYNC_STEP_INTO0("media", "SourceBuffer::appendBuffer", this, "waiting"); } 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); } m_webSourceBuffer->append(m_pendingAppendData.data(), appendSize); // 3. Set the updating attribute to false. m_updating = false; m_pendingAppendData.clear(); // 4. Queue a task to fire a simple event named update at this SourceBuffer object. scheduleEvent(EventTypeNames::update); // 5. Queue a task to fire a simple event named updateend at this SourceBuffer object. scheduleEvent(EventTypeNames::updateend); TRACE_EVENT_ASYNC_END0("media", "SourceBuffer::appendBuffer", this); } void SourceBuffer::removeAsyncPart() { ASSERT(m_updating); ASSERT(m_pendingRemoveStart >= 0); ASSERT(m_pendingRemoveStart < m_pendingRemoveEnd); // 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. m_webSourceBuffer->remove(m_pendingRemoveStart, m_pendingRemoveEnd); // 10. Set the updating attribute to false. m_updating = false; m_pendingRemoveStart = -1; m_pendingRemoveEnd = -1; // 11. Queue a task to fire a simple event named update at this SourceBuffer object. scheduleEvent(EventTypeNames::update); // 12. Queue a task to fire a simple event named updateend at this SourceBuffer object. scheduleEvent(EventTypeNames::updateend); } void SourceBuffer::appendStreamInternal(PassRefPtr 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); return; } // 2. 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); return; } TRACE_EVENT_ASYNC_BEGIN0("media", "SourceBuffer::appendStream", this); // 3. If the readyState attribute of the parent media source is in the "ended" state then run the following steps: ... m_source->openIfInEndedState(); // Steps 4-5 of the prepare append algorithm are handled by m_webSourceBuffer. // 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(EventTypeNames::updatestart); // 5. Asynchronously run the stream append loop algorithm with stream and maxSize. stream->neuter(); m_loader = adoptPtr(new FileReaderLoader(FileReaderLoader::ReadByClient, this)); m_stream = stream; m_appendStreamAsyncPartRunner.runAsync(); } void SourceBuffer::appendStreamAsyncPart() { ASSERT(m_updating); ASSERT(m_loader); ASSERT(m_stream); // Section 3.5.6 Stream Append Loop // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#sourcebuffer-stream-append-loop // 1. If maxSize is set, then let bytesLeft equal maxSize. // 2. Loop Top: If maxSize is set and bytesLeft equals 0, then jump to the loop done step below. if (m_streamMaxSizeValid && !m_streamMaxSize) { appendStreamDone(true); return; } // Steps 3-11 are handled by m_loader. // Note: Passing 0 here signals that maxSize was not set. (i.e. Read all the data in the stream). m_loader->start(executionContext(), *m_stream, m_streamMaxSizeValid ? m_streamMaxSize : 0); } void SourceBuffer::appendStreamDone(bool success) { ASSERT(m_updating); ASSERT(m_loader); ASSERT(m_stream); clearAppendStreamState(); if (!success) { // 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. (Handled by caller) // 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(EventTypeNames::error); // 4. Queue a task to fire a simple event named updateend at this SourceBuffer object. scheduleEvent(EventTypeNames::updateend); TRACE_EVENT_ASYNC_END0("media", "SourceBuffer::appendStream", this); return; } // Section 3.5.6 Stream Append Loop // Steps 1-11 are handled by appendStreamAsyncPart(), |m_loader|, and |m_webSourceBuffer|. // 12. Loop Done: Set the updating attribute to false. m_updating = false; // 13. Queue a task to fire a simple event named update at this SourceBuffer object. scheduleEvent(EventTypeNames::update); // 14. Queue a task to fire a simple event named updateend at this SourceBuffer object. scheduleEvent(EventTypeNames::updateend); TRACE_EVENT_ASYNC_END0("media", "SourceBuffer::appendStream", this); } void SourceBuffer::clearAppendStreamState() { m_streamMaxSizeValid = false; m_streamMaxSize = 0; m_loader.clear(); m_stream = 0; } void SourceBuffer::didStartLoading() { WTF_LOG(Media, "SourceBuffer::didStartLoading() %p", this); } void SourceBuffer::didReceiveDataForClient(const char* data, unsigned dataLength) { WTF_LOG(Media, "SourceBuffer::didReceiveDataForClient(%d) %p", dataLength, this); ASSERT(m_updating); ASSERT(m_loader); m_webSourceBuffer->append(reinterpret_cast(data), dataLength); } void SourceBuffer::didFinishLoading() { WTF_LOG(Media, "SourceBuffer::didFinishLoading() %p", this); appendStreamDone(true); } void SourceBuffer::didFail(FileError::ErrorCode errorCode) { WTF_LOG(Media, "SourceBuffer::didFail(%d) %p", errorCode, this); appendStreamDone(false); } } // namespace WebCore