2 * Copyright (C) 2013 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 #include "modules/mediasource/SourceBuffer.h"
34 #include "bindings/v8/ExceptionState.h"
35 #include "core/dom/ExceptionCode.h"
36 #include "core/dom/ExecutionContext.h"
37 #include "core/events/Event.h"
38 #include "core/events/GenericEventQueue.h"
39 #include "core/fileapi/FileReaderLoader.h"
40 #include "core/fileapi/Stream.h"
41 #include "core/html/TimeRanges.h"
42 #include "core/platform/graphics/SourceBufferPrivate.h"
43 #include "modules/mediasource/MediaSource.h"
44 #include "platform/Logging.h"
45 #include "platform/TraceEvent.h"
46 #include "wtf/ArrayBuffer.h"
47 #include "wtf/ArrayBufferView.h"
48 #include "wtf/MathExtras.h"
54 PassRefPtr<SourceBuffer> SourceBuffer::create(PassOwnPtr<SourceBufferPrivate> sourceBufferPrivate, MediaSource* source, GenericEventQueue* asyncEventQueue)
56 RefPtr<SourceBuffer> sourceBuffer(adoptRef(new SourceBuffer(sourceBufferPrivate, source, asyncEventQueue)));
57 sourceBuffer->suspendIfNeeded();
58 return sourceBuffer.release();
61 SourceBuffer::SourceBuffer(PassOwnPtr<SourceBufferPrivate> sourceBufferPrivate, MediaSource* source, GenericEventQueue* asyncEventQueue)
62 : ActiveDOMObject(source->executionContext())
63 , m_private(sourceBufferPrivate)
65 , m_asyncEventQueue(asyncEventQueue)
67 , m_timestampOffset(0)
68 , m_appendWindowStart(0)
69 , m_appendWindowEnd(std::numeric_limits<double>::infinity())
70 , m_appendBufferAsyncPartRunner(this, &SourceBuffer::appendBufferAsyncPart)
71 , m_pendingRemoveStart(-1)
72 , m_pendingRemoveEnd(-1)
73 , m_removeAsyncPartRunner(this, &SourceBuffer::removeAsyncPart)
74 , m_streamMaxSizeValid(false)
76 , m_appendStreamAsyncPartRunner(this, &SourceBuffer::appendStreamAsyncPart)
80 ScriptWrappable::init(this);
83 SourceBuffer::~SourceBuffer()
90 PassRefPtr<TimeRanges> SourceBuffer::buffered(ExceptionState& es) const
92 // Section 3.1 buffered attribute steps.
93 // 1. If this object has been removed from the sourceBuffers attribute of the parent media source then throw an
94 // InvalidStateError exception and abort these steps.
96 es.throwUninformativeAndGenericDOMException(InvalidStateError);
100 // 2. Return a new static normalized TimeRanges object for the media segments buffered.
101 return m_private->buffered();
104 double SourceBuffer::timestampOffset() const
106 return m_timestampOffset;
109 void SourceBuffer::setTimestampOffset(double offset, ExceptionState& es)
111 // Section 3.1 timestampOffset attribute setter steps.
112 // 1. Let new timestamp offset equal the new value being assigned to this attribute.
113 // 2. If this object has been removed from the sourceBuffers attribute of the parent media source, then throw an
114 // InvalidStateError exception and abort these steps.
115 // 3. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps.
116 if (isRemoved() || m_updating) {
117 es.throwUninformativeAndGenericDOMException(InvalidStateError);
121 // 4. If the readyState attribute of the parent media source is in the "ended" state then run the following steps:
122 // 4.1 Set the readyState attribute of the parent media source to "open"
123 // 4.2 Queue a task to fire a simple event named sourceopen at the parent media source.
124 m_source->openIfInEndedState();
126 // 5. If this object is waiting for the end of a media segment to be appended, then throw an InvalidStateError
127 // and abort these steps.
129 // FIXME: Add step 6 text when mode attribute is implemented.
130 if (!m_private->setTimestampOffset(offset)) {
131 es.throwUninformativeAndGenericDOMException(InvalidStateError);
135 // 7. Update the attribute to new timestamp offset.
136 m_timestampOffset = offset;
139 double SourceBuffer::appendWindowStart() const
141 return m_appendWindowStart;
144 void SourceBuffer::setAppendWindowStart(double start, ExceptionState& es)
146 // Enforce throwing an exception on restricted double values.
147 if (std::isnan(start)
148 || start == std::numeric_limits<double>::infinity()
149 || start == -std::numeric_limits<double>::infinity()) {
150 es.throwUninformativeAndGenericDOMException(TypeMismatchError);
154 // Section 3.1 appendWindowStart attribute setter steps.
155 // 1. If this object has been removed from the sourceBuffers attribute of the parent media source then throw an
156 // InvalidStateError exception and abort these steps.
157 // 2. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps.
158 if (isRemoved() || m_updating) {
159 es.throwUninformativeAndGenericDOMException(InvalidStateError);
163 // 3. If the new value is less than 0 or greater than or equal to appendWindowEnd then throw an InvalidAccessError
164 // exception and abort these steps.
165 if (start < 0 || start >= m_appendWindowEnd) {
166 es.throwUninformativeAndGenericDOMException(InvalidAccessError);
170 m_private->setAppendWindowStart(start);
172 // 4. Update the attribute to the new value.
173 m_appendWindowStart = start;
176 double SourceBuffer::appendWindowEnd() const
178 return m_appendWindowEnd;
181 void SourceBuffer::setAppendWindowEnd(double end, ExceptionState& es)
183 // Section 3.1 appendWindowEnd attribute setter steps.
184 // 1. If this object has been removed from the sourceBuffers attribute of the parent media source then throw an
185 // InvalidStateError exception and abort these steps.
186 // 2. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps.
187 if (isRemoved() || m_updating) {
188 es.throwUninformativeAndGenericDOMException(InvalidStateError);
192 // 3. If the new value equals NaN, then throw an InvalidAccessError and abort these steps.
193 // 4. If the new value is less than or equal to appendWindowStart then throw an InvalidAccessError
194 // exception and abort these steps.
195 if (std::isnan(end) || end <= m_appendWindowStart) {
196 es.throwUninformativeAndGenericDOMException(InvalidAccessError);
200 m_private->setAppendWindowEnd(end);
202 // 5. Update the attribute to the new value.
203 m_appendWindowEnd = end;
206 void SourceBuffer::appendBuffer(PassRefPtr<ArrayBuffer> data, ExceptionState& es)
208 // Section 3.2 appendBuffer()
209 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-SourceBuffer-appendBuffer-void-ArrayBufferView-data
210 // 1. If data is null then throw an InvalidAccessError exception and abort these steps.
212 es.throwUninformativeAndGenericDOMException(InvalidAccessError);
216 appendBufferInternal(static_cast<const unsigned char*>(data->data()), data->byteLength(), es);
219 void SourceBuffer::appendBuffer(PassRefPtr<ArrayBufferView> data, ExceptionState& es)
221 // Section 3.2 appendBuffer()
222 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-SourceBuffer-appendBuffer-void-ArrayBufferView-data
223 // 1. If data is null then throw an InvalidAccessError exception and abort these steps.
225 es.throwUninformativeAndGenericDOMException(InvalidAccessError);
229 appendBufferInternal(static_cast<const unsigned char*>(data->baseAddress()), data->byteLength(), es);
232 void SourceBuffer::appendStream(PassRefPtr<Stream> stream, ExceptionState& es)
234 m_streamMaxSizeValid = false;
235 appendStreamInternal(stream, es);
238 void SourceBuffer::appendStream(PassRefPtr<Stream> stream, unsigned long long maxSize, ExceptionState& es)
240 m_streamMaxSizeValid = maxSize > 0;
241 if (m_streamMaxSizeValid)
242 m_streamMaxSize = maxSize;
243 appendStreamInternal(stream, es);
246 void SourceBuffer::abort(ExceptionState& es)
248 // Section 3.2 abort() method steps.
249 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-SourceBuffer-abort-void
250 // 1. If this object has been removed from the sourceBuffers attribute of the parent media source
251 // then throw an InvalidStateError exception and abort these steps.
252 // 2. If the readyState attribute of the parent media source is not in the "open" state
253 // then throw an InvalidStateError exception and abort these steps.
254 if (isRemoved() || !m_source->isOpen()) {
255 es.throwUninformativeAndGenericDOMException(InvalidStateError);
259 // 3. If the sourceBuffer.updating attribute equals true, then run the following steps: ...
262 // 4. Run the reset parser state algorithm.
265 // 5. Set appendWindowStart to 0.
266 setAppendWindowStart(0, es);
268 // 6. Set appendWindowEnd to positive Infinity.
269 setAppendWindowEnd(std::numeric_limits<double>::infinity(), es);
272 void SourceBuffer::remove(double start, double end, ExceptionState& es)
274 // Section 3.2 remove() method steps.
275 // 1. If start is negative or greater than duration, then throw an InvalidAccessError exception and abort these steps.
276 // 2. If end is less than or equal to start, then throw an InvalidAccessError exception and abort these steps.
277 if (start < 0 || (m_source && (std::isnan(m_source->duration()) || start > m_source->duration())) || end <= start) {
278 es.throwUninformativeAndGenericDOMException(InvalidAccessError);
282 // 3. If this object has been removed from the sourceBuffers attribute of the parent media source then throw an
283 // InvalidStateError exception and abort these steps.
284 // 4. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps.
285 if (isRemoved() || m_updating) {
286 es.throwUninformativeAndGenericDOMException(InvalidStateError);
290 TRACE_EVENT_ASYNC_BEGIN0("media", "SourceBuffer::remove", this);
292 // 5. If the readyState attribute of the parent media source is in the "ended" state then run the following steps:
293 // 5.1. Set the readyState attribute of the parent media source to "open"
294 // 5.2. Queue a task to fire a simple event named sourceopen at the parent media source .
295 m_source->openIfInEndedState();
297 // 6. Set the updating attribute to true.
300 // 7. Queue a task to fire a simple event named updatestart at this SourceBuffer object.
301 scheduleEvent(EventTypeNames::updatestart);
303 // 8. Return control to the caller and run the rest of the steps asynchronously.
304 m_pendingRemoveStart = start;
305 m_pendingRemoveEnd = end;
306 m_removeAsyncPartRunner.runAsync();
309 void SourceBuffer::abortIfUpdating()
311 // Section 3.2 abort() method step 3 substeps.
312 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-SourceBuffer-abort-void
317 const char* traceEventName = 0;
318 if (!m_pendingAppendData.isEmpty()) {
319 traceEventName = "SourceBuffer::appendBuffer";
320 } else if (m_stream) {
321 traceEventName = "SourceBuffer::appendStream";
322 } else if (m_pendingRemoveStart != -1) {
323 traceEventName = "SourceBuffer::remove";
325 ASSERT_NOT_REACHED();
328 // 3.1. Abort the buffer append and stream append loop algorithms if they are running.
329 m_appendBufferAsyncPartRunner.stop();
330 m_pendingAppendData.clear();
332 m_removeAsyncPartRunner.stop();
333 m_pendingRemoveStart = -1;
334 m_pendingRemoveEnd = -1;
336 m_appendStreamAsyncPartRunner.stop();
337 clearAppendStreamState();
339 // 3.2. Set the updating attribute to false.
342 // 3.3. Queue a task to fire a simple event named abort at this SourceBuffer object.
343 scheduleEvent(EventTypeNames::abort);
345 // 3.4. Queue a task to fire a simple event named updateend at this SourceBuffer object.
346 scheduleEvent(EventTypeNames::updateend);
348 TRACE_EVENT_ASYNC_END0("media", traceEventName, this);
351 void SourceBuffer::removedFromMediaSource()
358 m_private->removedFromMediaSource();
360 m_asyncEventQueue = 0;
363 bool SourceBuffer::hasPendingActivity() const
368 void SourceBuffer::suspend()
370 m_appendBufferAsyncPartRunner.suspend();
371 m_removeAsyncPartRunner.suspend();
372 m_appendStreamAsyncPartRunner.suspend();
375 void SourceBuffer::resume()
377 m_appendBufferAsyncPartRunner.resume();
378 m_removeAsyncPartRunner.resume();
379 m_appendStreamAsyncPartRunner.resume();
382 void SourceBuffer::stop()
384 m_appendBufferAsyncPartRunner.stop();
385 m_removeAsyncPartRunner.stop();
386 m_appendStreamAsyncPartRunner.stop();
389 ExecutionContext* SourceBuffer::executionContext() const
391 return ActiveDOMObject::executionContext();
394 const AtomicString& SourceBuffer::interfaceName() const
396 return EventTargetNames::SourceBuffer;
399 bool SourceBuffer::isRemoved() const
404 void SourceBuffer::scheduleEvent(const AtomicString& eventName)
406 ASSERT(m_asyncEventQueue);
408 RefPtr<Event> event = Event::create(eventName);
409 event->setTarget(this);
411 m_asyncEventQueue->enqueueEvent(event.release());
414 void SourceBuffer::appendBufferInternal(const unsigned char* data, unsigned size, ExceptionState& es)
416 // Section 3.2 appendBuffer()
417 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-SourceBuffer-appendBuffer-void-ArrayBufferView-data
419 // Step 1 is enforced by the caller.
420 // 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.
421 // 3. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps.
422 if (isRemoved() || m_updating) {
423 es.throwUninformativeAndGenericDOMException(InvalidStateError);
427 TRACE_EVENT_ASYNC_BEGIN0("media", "SourceBuffer::appendBuffer", this);
429 // 4. If the readyState attribute of the parent media source is in the "ended" state then run the following steps: ...
430 m_source->openIfInEndedState();
434 // 7. Add data to the end of the input buffer.
435 m_pendingAppendData.append(data, size);
437 // 8. Set the updating attribute to true.
440 // 9. Queue a task to fire a simple event named updatestart at this SourceBuffer object.
441 scheduleEvent(EventTypeNames::updatestart);
443 // 10. Asynchronously run the buffer append algorithm.
444 m_appendBufferAsyncPartRunner.runAsync();
446 TRACE_EVENT_ASYNC_STEP0("media", "SourceBuffer::appendBuffer", this, "waiting");
449 void SourceBuffer::appendBufferAsyncPart()
453 TRACE_EVENT_ASYNC_STEP0("media", "SourceBuffer::appendBuffer", this, "appending");
455 // Section 3.5.4 Buffer Append Algorithm
456 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#sourcebuffer-buffer-append
458 // 1. Run the segment parser loop algorithm.
459 // Step 2 doesn't apply since we run Step 1 synchronously here.
460 size_t appendSize = m_pendingAppendData.size();
462 // Resize buffer for 0 byte appends so we always have a valid pointer.
463 // We need to convey all appends, even 0 byte ones to |m_private| so
464 // that it can clear its end of stream state if necessary.
465 m_pendingAppendData.resize(1);
467 m_private->append(m_pendingAppendData.data(), appendSize);
469 // 3. Set the updating attribute to false.
471 m_pendingAppendData.clear();
473 // 4. Queue a task to fire a simple event named update at this SourceBuffer object.
474 scheduleEvent(EventTypeNames::update);
476 // 5. Queue a task to fire a simple event named updateend at this SourceBuffer object.
477 scheduleEvent(EventTypeNames::updateend);
478 TRACE_EVENT_ASYNC_END0("media", "SourceBuffer::appendBuffer", this);
481 void SourceBuffer::removeAsyncPart()
484 ASSERT(m_pendingRemoveStart >= 0);
485 ASSERT(m_pendingRemoveStart < m_pendingRemoveEnd);
487 // Section 3.2 remove() method steps
488 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-SourceBuffer-remove-void-double-start-double-end
490 // 9. Run the coded frame removal algorithm with start and end as the start and end of the removal range.
491 m_private->remove(m_pendingRemoveStart, m_pendingRemoveEnd);
493 // 10. Set the updating attribute to false.
495 m_pendingRemoveStart = -1;
496 m_pendingRemoveEnd = -1;
498 // 11. Queue a task to fire a simple event named update at this SourceBuffer object.
499 scheduleEvent(EventTypeNames::update);
501 // 12. Queue a task to fire a simple event named updateend at this SourceBuffer object.
502 scheduleEvent(EventTypeNames::updateend);
505 void SourceBuffer::appendStreamInternal(PassRefPtr<Stream> stream, ExceptionState& es)
507 // Section 3.2 appendStream()
508 // 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
509 // 1. If stream is null then throw an InvalidAccessError exception and abort these steps.
510 if (!stream || stream->isNeutered()) {
511 es.throwUninformativeAndGenericDOMException(InvalidAccessError);
515 // 2. Run the prepare append algorithm.
516 // Section 3.5.4 Prepare Append Algorithm.
517 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#sourcebuffer-prepare-append
518 // 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.
519 // 2. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps.
520 if (isRemoved() || m_updating) {
521 es.throwUninformativeAndGenericDOMException(InvalidStateError);
525 TRACE_EVENT_ASYNC_BEGIN0("media", "SourceBuffer::appendStream", this);
527 // 3. If the readyState attribute of the parent media source is in the "ended" state then run the following steps: ...
528 m_source->openIfInEndedState();
530 // Steps 4-5 of the prepare append algorithm are handled by m_private.
532 // 3. Set the updating attribute to true.
535 // 4. Queue a task to fire a simple event named updatestart at this SourceBuffer object.
536 scheduleEvent(EventTypeNames::updatestart);
538 // 5. Asynchronously run the stream append loop algorithm with stream and maxSize.
541 m_loader = adoptPtr(new FileReaderLoader(FileReaderLoader::ReadByClient, this));
543 m_appendStreamAsyncPartRunner.runAsync();
546 void SourceBuffer::appendStreamAsyncPart()
552 // Section 3.5.6 Stream Append Loop
553 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#sourcebuffer-stream-append-loop
555 // 1. If maxSize is set, then let bytesLeft equal maxSize.
556 // 2. Loop Top: If maxSize is set and bytesLeft equals 0, then jump to the loop done step below.
557 if (m_streamMaxSizeValid && !m_streamMaxSize) {
558 appendStreamDone(true);
562 // Steps 3-11 are handled by m_loader.
563 // Note: Passing 0 here signals that maxSize was not set. (i.e. Read all the data in the stream).
564 m_loader->start(executionContext(), *m_stream, m_streamMaxSizeValid ? m_streamMaxSize : 0);
567 void SourceBuffer::appendStreamDone(bool success)
573 clearAppendStreamState();
576 // Section 3.5.3 Append Error Algorithm
577 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#sourcebuffer-append-error
579 // 1. Run the reset parser state algorithm. (Handled by caller)
580 // 2. Set the updating attribute to false.
583 // 3. Queue a task to fire a simple event named error at this SourceBuffer object.
584 scheduleEvent(EventTypeNames::error);
586 // 4. Queue a task to fire a simple event named updateend at this SourceBuffer object.
587 scheduleEvent(EventTypeNames::updateend);
588 TRACE_EVENT_ASYNC_END0("media", "SourceBuffer::appendStream", this);
592 // Section 3.5.6 Stream Append Loop
593 // Steps 1-11 are handled by appendStreamAsyncPart(), |m_loader|, and |m_private|.
594 // 12. Loop Done: Set the updating attribute to false.
597 // 13. Queue a task to fire a simple event named update at this SourceBuffer object.
598 scheduleEvent(EventTypeNames::update);
600 // 14. Queue a task to fire a simple event named updateend at this SourceBuffer object.
601 scheduleEvent(EventTypeNames::updateend);
602 TRACE_EVENT_ASYNC_END0("media", "SourceBuffer::appendStream", this);
605 void SourceBuffer::clearAppendStreamState()
607 m_streamMaxSizeValid = false;
613 void SourceBuffer::didStartLoading()
615 LOG(Media, "SourceBuffer::didStartLoading() %p", this);
618 void SourceBuffer::didReceiveDataForClient(const char* data, unsigned dataLength)
620 LOG(Media, "SourceBuffer::didReceiveDataForClient(%d) %p", dataLength, this);
624 m_private->append(reinterpret_cast<const unsigned char*>(data), dataLength);
627 void SourceBuffer::didFinishLoading()
629 LOG(Media, "SourceBuffer::didFinishLoading() %p", this);
630 appendStreamDone(true);
633 void SourceBuffer::didFail(FileError::ErrorCode errorCode)
635 LOG(Media, "SourceBuffer::didFail(%d) %p", errorCode, this);
636 appendStreamDone(false);
639 } // namespace WebCore