1/*
2 * Copyright (C) 2013 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
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
13 * distribution.
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.
17 *
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.
29 */
30
31#include "config.h"
32#include "modules/mediasource/SourceBuffer.h"
33
34#include "bindings/core/v8/ExceptionMessages.h"
35#include "bindings/core/v8/ExceptionState.h"
36#include "core/dom/ExceptionCode.h"
37#include "core/dom/ExecutionContext.h"
38#include "core/events/Event.h"
39#include "core/events/GenericEventQueue.h"
40#include "core/fileapi/FileReaderLoader.h"
41#include "core/html/TimeRanges.h"
42#include "core/streams/Stream.h"
43#include "modules/mediasource/MediaSource.h"
44#include "platform/Logging.h"
45#include "platform/TraceEvent.h"
46#include "public/platform/WebSourceBuffer.h"
47#include "wtf/ArrayBuffer.h"
48#include "wtf/ArrayBufferView.h"
49#include "wtf/MathExtras.h"
50
51#include <limits>
52
53using blink::WebSourceBuffer;
54
55namespace blink {
56
57namespace {
58
59static bool throwExceptionIfRemovedOrUpdating(bool isRemoved, bool isUpdating, ExceptionState& exceptionState)
60{
61    if (isRemoved) {
62        exceptionState.throwDOMException(InvalidStateError, "This SourceBuffer has been removed from the parent media source.");
63        return true;
64    }
65    if (isUpdating) {
66        exceptionState.throwDOMException(InvalidStateError, "This SourceBuffer is still processing an 'appendBuffer', 'appendStream', or 'remove' operation.");
67        return true;
68    }
69
70    return false;
71}
72
73} // namespace
74
75SourceBuffer* SourceBuffer::create(PassOwnPtr<WebSourceBuffer> webSourceBuffer, MediaSource* source, GenericEventQueue* asyncEventQueue)
76{
77    SourceBuffer* sourceBuffer(adoptRefCountedGarbageCollectedWillBeNoop(new SourceBuffer(webSourceBuffer, source, asyncEventQueue)));
78    sourceBuffer->suspendIfNeeded();
79    return sourceBuffer;
80}
81
82SourceBuffer::SourceBuffer(PassOwnPtr<WebSourceBuffer> webSourceBuffer, MediaSource* source, GenericEventQueue* asyncEventQueue)
83    : ActiveDOMObject(source->executionContext())
84    , m_webSourceBuffer(webSourceBuffer)
85    , m_source(source)
86    , m_asyncEventQueue(asyncEventQueue)
87    , m_mode(segmentsKeyword())
88    , m_updating(false)
89    , m_timestampOffset(0)
90    , m_appendWindowStart(0)
91    , m_appendWindowEnd(std::numeric_limits<double>::infinity())
92    , m_firstInitializationSegmentReceived(false)
93    , m_pendingAppendDataOffset(0)
94    , m_appendBufferAsyncPartRunner(this, &SourceBuffer::appendBufferAsyncPart)
95    , m_pendingRemoveStart(-1)
96    , m_pendingRemoveEnd(-1)
97    , m_removeAsyncPartRunner(this, &SourceBuffer::removeAsyncPart)
98    , m_streamMaxSizeValid(false)
99    , m_streamMaxSize(0)
100    , m_appendStreamAsyncPartRunner(this, &SourceBuffer::appendStreamAsyncPart)
101{
102    ASSERT(m_webSourceBuffer);
103    ASSERT(m_source);
104    m_webSourceBuffer->setClient(this);
105}
106
107SourceBuffer::~SourceBuffer()
108{
109    // Oilpan: a SourceBuffer might be finalized without having been
110    // explicitly removed first, hence the asserts below will not
111    // hold.
112#if !ENABLE(OILPAN)
113    ASSERT(isRemoved());
114    ASSERT(!m_loader);
115    ASSERT(!m_stream);
116    ASSERT(!m_webSourceBuffer);
117#endif
118}
119
120const AtomicString& SourceBuffer::segmentsKeyword()
121{
122    DEFINE_STATIC_LOCAL(const AtomicString, segments, ("segments", AtomicString::ConstructFromLiteral));
123    return segments;
124}
125
126const AtomicString& SourceBuffer::sequenceKeyword()
127{
128    DEFINE_STATIC_LOCAL(const AtomicString, sequence, ("sequence", AtomicString::ConstructFromLiteral));
129    return sequence;
130}
131
132void SourceBuffer::setMode(const AtomicString& newMode, ExceptionState& exceptionState)
133{
134    // Section 3.1 On setting mode attribute steps.
135    // 1. Let new mode equal the new value being assigned to this attribute.
136    // 2. If this object has been removed from the sourceBuffers attribute of the parent media source, then throw
137    //    an INVALID_STATE_ERR exception and abort these steps.
138    // 3. If the updating attribute equals true, then throw an INVALID_STATE_ERR exception and abort these steps.
139    if (throwExceptionIfRemovedOrUpdating(isRemoved(), m_updating, exceptionState))
140        return;
141
142    // 4. If the readyState attribute of the parent media source is in the "ended" state then run the following steps:
143    // 4.1 Set the readyState attribute of the parent media source to "open"
144    // 4.2 Queue a task to fire a simple event named sourceopen at the parent media source.
145    m_source->openIfInEndedState();
146
147    // 5. If the append state equals PARSING_MEDIA_SEGMENT, then throw an INVALID_STATE_ERR and abort these steps.
148    // 6. If the new mode equals "sequence", then set the group start timestamp to the highest presentation end timestamp.
149    WebSourceBuffer::AppendMode appendMode = WebSourceBuffer::AppendModeSegments;
150    if (newMode == sequenceKeyword())
151        appendMode = WebSourceBuffer::AppendModeSequence;
152    if (!m_webSourceBuffer->setMode(appendMode)) {
153        exceptionState.throwDOMException(InvalidStateError, "The mode may not be set while the SourceBuffer's append state is 'PARSING_MEDIA_SEGMENT'.");
154        return;
155    }
156
157    // 7. Update the attribute to new mode.
158    m_mode = newMode;
159}
160
161PassRefPtrWillBeRawPtr<TimeRanges> SourceBuffer::buffered(ExceptionState& exceptionState) const
162{
163    // Section 3.1 buffered attribute steps.
164    // 1. If this object has been removed from the sourceBuffers attribute of the parent media source then throw an
165    //    InvalidStateError exception and abort these steps.
166    if (isRemoved()) {
167        exceptionState.throwDOMException(InvalidStateError, "This SourceBuffer has been removed from the parent media source.");
168        return nullptr;
169    }
170
171    // 2. Return a new static normalized TimeRanges object for the media segments buffered.
172    return TimeRanges::create(m_webSourceBuffer->buffered());
173}
174
175double SourceBuffer::timestampOffset() const
176{
177    return m_timestampOffset;
178}
179
180void SourceBuffer::setTimestampOffset(double offset, ExceptionState& exceptionState)
181{
182    // Section 3.1 timestampOffset attribute setter steps.
183    // https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#widl-SourceBuffer-timestampOffset
184    // 1. Let new timestamp offset equal the new value being assigned to this attribute.
185    // 2. If this object has been removed from the sourceBuffers attribute of the parent media source, then throw an
186    //    InvalidStateError exception and abort these steps.
187    // 3. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps.
188    if (throwExceptionIfRemovedOrUpdating(isRemoved(), m_updating, exceptionState))
189        return;
190
191    // 4. If the readyState attribute of the parent media source is in the "ended" state then run the following steps:
192    // 4.1 Set the readyState attribute of the parent media source to "open"
193    // 4.2 Queue a task to fire a simple event named sourceopen at the parent media source.
194    m_source->openIfInEndedState();
195
196    // 5. If the append state equals PARSING_MEDIA_SEGMENT, then throw an INVALID_STATE_ERR and abort these steps.
197    // 6. If the mode attribute equals "sequence", then set the group start timestamp to new timestamp offset.
198    if (!m_webSourceBuffer->setTimestampOffset(offset)) {
199        exceptionState.throwDOMException(InvalidStateError, "The timestamp offset may not be set while the SourceBuffer's append state is 'PARSING_MEDIA_SEGMENT'.");
200        return;
201    }
202
203    // 7. Update the attribute to new timestamp offset.
204    m_timestampOffset = offset;
205}
206
207double SourceBuffer::appendWindowStart() const
208{
209    return m_appendWindowStart;
210}
211
212void SourceBuffer::setAppendWindowStart(double start, ExceptionState& exceptionState)
213{
214    // Section 3.1 appendWindowStart attribute setter steps.
215    // https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#widl-SourceBuffer-appendWindowStart
216    // 1. If this object has been removed from the sourceBuffers attribute of the parent media source then throw an
217    //    InvalidStateError exception and abort these steps.
218    // 2. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps.
219    if (throwExceptionIfRemovedOrUpdating(isRemoved(), m_updating, exceptionState))
220        return;
221
222    // 3. If the new value is less than 0 or greater than or equal to appendWindowEnd then throw an InvalidAccessError
223    //    exception and abort these steps.
224    if (start < 0 || start >= m_appendWindowEnd) {
225        exceptionState.throwDOMException(InvalidAccessError, ExceptionMessages::indexOutsideRange("value", start, 0.0, ExceptionMessages::ExclusiveBound, m_appendWindowEnd, ExceptionMessages::InclusiveBound));
226        return;
227    }
228
229    m_webSourceBuffer->setAppendWindowStart(start);
230
231    // 4. Update the attribute to the new value.
232    m_appendWindowStart = start;
233}
234
235double SourceBuffer::appendWindowEnd() const
236{
237    return m_appendWindowEnd;
238}
239
240void SourceBuffer::setAppendWindowEnd(double end, ExceptionState& exceptionState)
241{
242    // Section 3.1 appendWindowEnd attribute setter steps.
243    // https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#widl-SourceBuffer-appendWindowEnd
244    // 1. If this object has been removed from the sourceBuffers attribute of the parent media source then throw an
245    //    InvalidStateError exception and abort these steps.
246    // 2. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps.
247    if (throwExceptionIfRemovedOrUpdating(isRemoved(), m_updating, exceptionState))
248        return;
249
250    // 3. If the new value equals NaN, then throw an InvalidAccessError and abort these steps.
251    if (std::isnan(end)) {
252        exceptionState.throwDOMException(InvalidAccessError, ExceptionMessages::notAFiniteNumber(end));
253        return;
254    }
255    // 4. If the new value is less than or equal to appendWindowStart then throw an InvalidAccessError
256    //    exception and abort these steps.
257    if (end <= m_appendWindowStart) {
258        exceptionState.throwDOMException(InvalidAccessError, ExceptionMessages::indexExceedsMinimumBound("value", end, m_appendWindowStart));
259        return;
260    }
261
262    m_webSourceBuffer->setAppendWindowEnd(end);
263
264    // 5. Update the attribute to the new value.
265    m_appendWindowEnd = end;
266}
267
268void SourceBuffer::appendBuffer(PassRefPtr<ArrayBuffer> data, ExceptionState& exceptionState)
269{
270    // Section 3.2 appendBuffer()
271    // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-SourceBuffer-appendBuffer-void-ArrayBufferView-data
272    appendBufferInternal(static_cast<const unsigned char*>(data->data()), data->byteLength(), exceptionState);
273}
274
275void SourceBuffer::appendBuffer(PassRefPtr<ArrayBufferView> data, ExceptionState& exceptionState)
276{
277    // Section 3.2 appendBuffer()
278    // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-SourceBuffer-appendBuffer-void-ArrayBufferView-data
279    appendBufferInternal(static_cast<const unsigned char*>(data->baseAddress()), data->byteLength(), exceptionState);
280}
281
282void SourceBuffer::appendStream(PassRefPtrWillBeRawPtr<Stream> stream, ExceptionState& exceptionState)
283{
284    m_streamMaxSizeValid = false;
285    appendStreamInternal(stream, exceptionState);
286}
287
288void SourceBuffer::appendStream(PassRefPtrWillBeRawPtr<Stream> stream, unsigned long long maxSize, ExceptionState& exceptionState)
289{
290    m_streamMaxSizeValid = maxSize > 0;
291    if (m_streamMaxSizeValid)
292        m_streamMaxSize = maxSize;
293    appendStreamInternal(stream, exceptionState);
294}
295
296void SourceBuffer::abort(ExceptionState& exceptionState)
297{
298    // Section 3.2 abort() method steps.
299    // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-SourceBuffer-abort-void
300    // 1. If this object has been removed from the sourceBuffers attribute of the parent media source
301    //    then throw an InvalidStateError exception and abort these steps.
302    // 2. If the readyState attribute of the parent media source is not in the "open" state
303    //    then throw an InvalidStateError exception and abort these steps.
304    if (isRemoved()) {
305        exceptionState.throwDOMException(InvalidStateError, "This SourceBuffer has been removed from the parent media source.");
306        return;
307    }
308    if (!m_source->isOpen()) {
309        exceptionState.throwDOMException(InvalidStateError, "The parent media source's readyState is not 'open'.");
310        return;
311    }
312
313    // 3. If the sourceBuffer.updating attribute equals true, then run the following steps: ...
314    abortIfUpdating();
315
316    // 4. Run the reset parser state algorithm.
317    m_webSourceBuffer->abort();
318
319    // 5. Set appendWindowStart to 0.
320    setAppendWindowStart(0, exceptionState);
321
322    // 6. Set appendWindowEnd to positive Infinity.
323    setAppendWindowEnd(std::numeric_limits<double>::infinity(), exceptionState);
324}
325
326void SourceBuffer::remove(double start, double end, ExceptionState& exceptionState)
327{
328    // Section 3.2 remove() method steps.
329    // 1. If duration equals NaN, then throw an InvalidAccessError exception and abort these steps.
330    // 2. If start is negative or greater than duration, then throw an InvalidAccessError exception and abort these steps.
331
332    if (start < 0 || (m_source && (std::isnan(m_source->duration()) || start > m_source->duration()))) {
333        exceptionState.throwDOMException(InvalidAccessError, ExceptionMessages::indexOutsideRange("start", start, 0.0, ExceptionMessages::ExclusiveBound, !m_source || std::isnan(m_source->duration()) ? 0 : m_source->duration(), ExceptionMessages::ExclusiveBound));
334        return;
335    }
336
337    // 3. If end is less than or equal to start or end equals NaN, then throw an InvalidAccessError exception and abort these steps.
338    if (end <= start || std::isnan(end)) {
339        exceptionState.throwDOMException(InvalidAccessError, "The end value provided (" + String::number(end) + ") must be greater than the start value provided (" + String::number(start) + ").");
340        return;
341    }
342
343    // 4. If this object has been removed from the sourceBuffers attribute of the parent media source then throw an
344    //    InvalidStateError exception and abort these steps.
345    // 5. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps.
346    if (throwExceptionIfRemovedOrUpdating(isRemoved(), m_updating, exceptionState))
347        return;
348
349    TRACE_EVENT_ASYNC_BEGIN0("media", "SourceBuffer::remove", this);
350
351    // 6. If the readyState attribute of the parent media source is in the "ended" state then run the following steps:
352    // 6.1. Set the readyState attribute of the parent media source to "open"
353    // 6.2. Queue a task to fire a simple event named sourceopen at the parent media source .
354    m_source->openIfInEndedState();
355
356    // 7. Run the range removal algorithm with start and end as the start and end of the removal range.
357    // 7.3. Set the updating attribute to true.
358    m_updating = true;
359
360    // 7.4. Queue a task to fire a simple event named updatestart at this SourceBuffer object.
361    scheduleEvent(EventTypeNames::updatestart);
362
363    // 7.5. Return control to the caller and run the rest of the steps asynchronously.
364    m_pendingRemoveStart = start;
365    m_pendingRemoveEnd = end;
366    m_removeAsyncPartRunner.runAsync();
367}
368
369void SourceBuffer::abortIfUpdating()
370{
371    // Section 3.2 abort() method step 3 substeps.
372    // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-SourceBuffer-abort-void
373
374    if (!m_updating)
375        return;
376
377    const char* traceEventName = 0;
378    if (!m_pendingAppendData.isEmpty()) {
379        traceEventName = "SourceBuffer::appendBuffer";
380    } else if (m_stream) {
381        traceEventName = "SourceBuffer::appendStream";
382    } else if (m_pendingRemoveStart != -1) {
383        traceEventName = "SourceBuffer::remove";
384    } else {
385        ASSERT_NOT_REACHED();
386    }
387
388    // 3.1. Abort the buffer append and stream append loop algorithms if they are running.
389    m_appendBufferAsyncPartRunner.stop();
390    m_pendingAppendData.clear();
391    m_pendingAppendDataOffset = 0;
392
393    m_removeAsyncPartRunner.stop();
394    m_pendingRemoveStart = -1;
395    m_pendingRemoveEnd = -1;
396
397    m_appendStreamAsyncPartRunner.stop();
398    clearAppendStreamState();
399
400    // 3.2. Set the updating attribute to false.
401    m_updating = false;
402
403    // 3.3. Queue a task to fire a simple event named abort at this SourceBuffer object.
404    scheduleEvent(EventTypeNames::abort);
405
406    // 3.4. Queue a task to fire a simple event named updateend at this SourceBuffer object.
407    scheduleEvent(EventTypeNames::updateend);
408
409    TRACE_EVENT_ASYNC_END0("media", traceEventName, this);
410}
411
412void SourceBuffer::removedFromMediaSource()
413{
414    if (isRemoved())
415        return;
416
417    abortIfUpdating();
418
419    m_webSourceBuffer->removedFromMediaSource();
420    m_webSourceBuffer.clear();
421    m_source = nullptr;
422    m_asyncEventQueue = 0;
423}
424
425void SourceBuffer::initializationSegmentReceived()
426{
427    ASSERT(m_source);
428    ASSERT(m_updating);
429
430    // https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#sourcebuffer-init-segment-received
431    // FIXME: Make steps 1-7 synchronous with this call.
432    // FIXME: Augment the interface to this method to implement compliant steps 4-7 here.
433    // Step 3 (if the first initialization segment received flag is true) is
434    // implemented by caller.
435
436    if (!m_firstInitializationSegmentReceived) {
437        // 5. If active track flag equals true, then run the following steps:
438        // 5.1. Add this SourceBuffer to activeSourceBuffers.
439        // 5.2. Queue a task to fire a simple event named addsourcebuffer at
440        // activesourcebuffers.
441        m_source->setSourceBufferActive(this);
442
443        // 6. Set first initialization segment received flag to true.
444        m_firstInitializationSegmentReceived = true;
445    }
446}
447
448bool SourceBuffer::hasPendingActivity() const
449{
450    return m_source;
451}
452
453void SourceBuffer::suspend()
454{
455    m_appendBufferAsyncPartRunner.suspend();
456    m_removeAsyncPartRunner.suspend();
457    m_appendStreamAsyncPartRunner.suspend();
458}
459
460void SourceBuffer::resume()
461{
462    m_appendBufferAsyncPartRunner.resume();
463    m_removeAsyncPartRunner.resume();
464    m_appendStreamAsyncPartRunner.resume();
465}
466
467void SourceBuffer::stop()
468{
469    m_appendBufferAsyncPartRunner.stop();
470    m_removeAsyncPartRunner.stop();
471    m_appendStreamAsyncPartRunner.stop();
472}
473
474ExecutionContext* SourceBuffer::executionContext() const
475{
476    return ActiveDOMObject::executionContext();
477}
478
479const AtomicString& SourceBuffer::interfaceName() const
480{
481    return EventTargetNames::SourceBuffer;
482}
483
484bool SourceBuffer::isRemoved() const
485{
486    return !m_source;
487}
488
489void SourceBuffer::scheduleEvent(const AtomicString& eventName)
490{
491    ASSERT(m_asyncEventQueue);
492
493    RefPtrWillBeRawPtr<Event> event = Event::create(eventName);
494    event->setTarget(this);
495
496    m_asyncEventQueue->enqueueEvent(event.release());
497}
498
499void SourceBuffer::appendBufferInternal(const unsigned char* data, unsigned size, ExceptionState& exceptionState)
500{
501    // Section 3.2 appendBuffer()
502    // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-SourceBuffer-appendBuffer-void-ArrayBufferView-data
503
504    // 1. Run the prepare append algorithm.
505    // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#sourcebuffer-prepare-append
506    //  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.
507    //  2. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps.
508    if (throwExceptionIfRemovedOrUpdating(isRemoved(), m_updating, exceptionState))
509        return;
510
511    TRACE_EVENT_ASYNC_BEGIN1("media", "SourceBuffer::appendBuffer", this, "size", size);
512
513    //  3. If the readyState attribute of the parent media source is in the "ended" state then run the following steps: ...
514    m_source->openIfInEndedState();
515
516    //  Steps 4-5 - end "prepare append" algorithm.
517
518    // 2. Add data to the end of the input buffer.
519    ASSERT(data || size == 0);
520    if (data)
521        m_pendingAppendData.append(data, size);
522    m_pendingAppendDataOffset = 0;
523
524    // 3. Set the updating attribute to true.
525    m_updating = true;
526
527    // 4. Queue a task to fire a simple event named updatestart at this SourceBuffer object.
528    scheduleEvent(EventTypeNames::updatestart);
529
530    // 5. Asynchronously run the buffer append algorithm.
531    m_appendBufferAsyncPartRunner.runAsync();
532
533    TRACE_EVENT_ASYNC_STEP_INTO0("media", "SourceBuffer::appendBuffer", this, "initialDelay");
534}
535
536void SourceBuffer::appendBufferAsyncPart()
537{
538    ASSERT(m_updating);
539
540    // Section 3.5.4 Buffer Append Algorithm
541    // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#sourcebuffer-buffer-append
542
543    // 1. Run the segment parser loop algorithm.
544    // Step 2 doesn't apply since we run Step 1 synchronously here.
545    ASSERT(m_pendingAppendData.size() >= m_pendingAppendDataOffset);
546    size_t appendSize = m_pendingAppendData.size() - m_pendingAppendDataOffset;
547
548    // Impose an arbitrary max size for a single append() call so that an append
549    // doesn't block the renderer event loop very long. This value was selected
550    // by looking at YouTube SourceBuffer usage across a variety of bitrates.
551    // This value allows relatively large appends while keeping append() call
552    // duration in the  ~5-15ms range.
553    const size_t MaxAppendSize = 128 * 1024;
554    if (appendSize > MaxAppendSize)
555        appendSize = MaxAppendSize;
556
557    TRACE_EVENT_ASYNC_STEP_INTO1("media", "SourceBuffer::appendBuffer", this, "appending", "appendSize", static_cast<unsigned>(appendSize));
558
559    // |zero| is used for 0 byte appends so we always have a valid pointer.
560    // We need to convey all appends, even 0 byte ones to |m_webSourceBuffer|
561    // so that it can clear its end of stream state if necessary.
562    unsigned char zero = 0;
563    unsigned char* appendData = &zero;
564    if (appendSize)
565        appendData = m_pendingAppendData.data() + m_pendingAppendDataOffset;
566
567    m_webSourceBuffer->append(appendData, appendSize, &m_timestampOffset);
568
569    m_pendingAppendDataOffset += appendSize;
570
571    if (m_pendingAppendDataOffset < m_pendingAppendData.size()) {
572        m_appendBufferAsyncPartRunner.runAsync();
573        TRACE_EVENT_ASYNC_STEP_INTO0("media", "SourceBuffer::appendBuffer", this, "nextPieceDelay");
574        return;
575    }
576
577    // 3. Set the updating attribute to false.
578    m_updating = false;
579    m_pendingAppendData.clear();
580    m_pendingAppendDataOffset = 0;
581
582    // 4. Queue a task to fire a simple event named update at this SourceBuffer object.
583    scheduleEvent(EventTypeNames::update);
584
585    // 5. Queue a task to fire a simple event named updateend at this SourceBuffer object.
586    scheduleEvent(EventTypeNames::updateend);
587    TRACE_EVENT_ASYNC_END0("media", "SourceBuffer::appendBuffer", this);
588}
589
590void SourceBuffer::removeAsyncPart()
591{
592    ASSERT(m_updating);
593    ASSERT(m_pendingRemoveStart >= 0);
594    ASSERT(m_pendingRemoveStart < m_pendingRemoveEnd);
595
596    // Section 3.2 remove() method steps
597    // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-SourceBuffer-remove-void-double-start-double-end
598
599    // 9. Run the coded frame removal algorithm with start and end as the start and end of the removal range.
600    m_webSourceBuffer->remove(m_pendingRemoveStart, m_pendingRemoveEnd);
601
602    // 10. Set the updating attribute to false.
603    m_updating = false;
604    m_pendingRemoveStart = -1;
605    m_pendingRemoveEnd = -1;
606
607    // 11. Queue a task to fire a simple event named update at this SourceBuffer object.
608    scheduleEvent(EventTypeNames::update);
609
610    // 12. Queue a task to fire a simple event named updateend at this SourceBuffer object.
611    scheduleEvent(EventTypeNames::updateend);
612}
613
614void SourceBuffer::appendStreamInternal(PassRefPtrWillBeRawPtr<Stream> stream, ExceptionState& exceptionState)
615{
616    // Section 3.2 appendStream()
617    // 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
618    // (0. If the stream has been neutered, then throw an InvalidAccessError exception and abort these steps.)
619    if (stream->isNeutered()) {
620        exceptionState.throwDOMException(InvalidAccessError, "The stream provided has been neutered.");
621        return;
622    }
623
624    // 1. Run the prepare append algorithm.
625    //  Section 3.5.4 Prepare Append Algorithm.
626    //  https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#sourcebuffer-prepare-append
627    //  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.
628    //  2. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps.
629    if (throwExceptionIfRemovedOrUpdating(isRemoved(), m_updating, exceptionState))
630        return;
631
632    TRACE_EVENT_ASYNC_BEGIN0("media", "SourceBuffer::appendStream", this);
633
634    //  3. If the readyState attribute of the parent media source is in the "ended" state then run the following steps: ...
635    m_source->openIfInEndedState();
636
637    // Steps 4-5 of the prepare append algorithm are handled by m_webSourceBuffer.
638
639    // 2. Set the updating attribute to true.
640    m_updating = true;
641
642    // 3. Queue a task to fire a simple event named updatestart at this SourceBuffer object.
643    scheduleEvent(EventTypeNames::updatestart);
644
645    // 4. Asynchronously run the stream append loop algorithm with stream and maxSize.
646
647    stream->neuter();
648    m_loader = adoptPtr(new FileReaderLoader(FileReaderLoader::ReadByClient, this));
649    m_stream = stream;
650    m_appendStreamAsyncPartRunner.runAsync();
651}
652
653void SourceBuffer::appendStreamAsyncPart()
654{
655    ASSERT(m_updating);
656    ASSERT(m_loader);
657    ASSERT(m_stream);
658
659    // Section 3.5.6 Stream Append Loop
660    // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#sourcebuffer-stream-append-loop
661
662    // 1. If maxSize is set, then let bytesLeft equal maxSize.
663    // 2. Loop Top: If maxSize is set and bytesLeft equals 0, then jump to the loop done step below.
664    if (m_streamMaxSizeValid && !m_streamMaxSize) {
665        appendStreamDone(true);
666        return;
667    }
668
669    // Steps 3-11 are handled by m_loader.
670    // Note: Passing 0 here signals that maxSize was not set. (i.e. Read all the data in the stream).
671    m_loader->start(executionContext(), *m_stream, m_streamMaxSizeValid ? m_streamMaxSize : 0);
672}
673
674void SourceBuffer::appendStreamDone(bool success)
675{
676    ASSERT(m_updating);
677    ASSERT(m_loader);
678    ASSERT(m_stream);
679
680    clearAppendStreamState();
681
682    if (!success) {
683        // Section 3.5.3 Append Error Algorithm
684        // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#sourcebuffer-append-error
685        //
686        // 1. Run the reset parser state algorithm. (Handled by caller)
687        // 2. Set the updating attribute to false.
688        m_updating = false;
689
690        // 3. Queue a task to fire a simple event named error at this SourceBuffer object.
691        scheduleEvent(EventTypeNames::error);
692
693        // 4. Queue a task to fire a simple event named updateend at this SourceBuffer object.
694        scheduleEvent(EventTypeNames::updateend);
695        TRACE_EVENT_ASYNC_END0("media", "SourceBuffer::appendStream", this);
696        return;
697    }
698
699    // Section 3.5.6 Stream Append Loop
700    // Steps 1-11 are handled by appendStreamAsyncPart(), |m_loader|, and |m_webSourceBuffer|.
701    // 12. Loop Done: Set the updating attribute to false.
702    m_updating = false;
703
704    // 13. Queue a task to fire a simple event named update at this SourceBuffer object.
705    scheduleEvent(EventTypeNames::update);
706
707    // 14. Queue a task to fire a simple event named updateend at this SourceBuffer object.
708    scheduleEvent(EventTypeNames::updateend);
709    TRACE_EVENT_ASYNC_END0("media", "SourceBuffer::appendStream", this);
710}
711
712void SourceBuffer::clearAppendStreamState()
713{
714    m_streamMaxSizeValid = false;
715    m_streamMaxSize = 0;
716    m_loader.clear();
717    m_stream = nullptr;
718}
719
720void SourceBuffer::didStartLoading()
721{
722    WTF_LOG(Media, "SourceBuffer::didStartLoading() %p", this);
723}
724
725void SourceBuffer::didReceiveDataForClient(const char* data, unsigned dataLength)
726{
727    WTF_LOG(Media, "SourceBuffer::didReceiveDataForClient(%d) %p", dataLength, this);
728    ASSERT(m_updating);
729    ASSERT(m_loader);
730    m_webSourceBuffer->append(reinterpret_cast<const unsigned char*>(data), dataLength, &m_timestampOffset);
731}
732
733void SourceBuffer::didFinishLoading()
734{
735    WTF_LOG(Media, "SourceBuffer::didFinishLoading() %p", this);
736    appendStreamDone(true);
737}
738
739void SourceBuffer::didFail(FileError::ErrorCode errorCode)
740{
741    WTF_LOG(Media, "SourceBuffer::didFail(%d) %p", errorCode, this);
742    appendStreamDone(false);
743}
744
745void SourceBuffer::trace(Visitor* visitor)
746{
747    visitor->trace(m_source);
748    visitor->trace(m_stream);
749    EventTargetWithInlineData::trace(visitor);
750}
751
752} // namespace blink
753