1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "media/filters/chunk_demuxer.h"
11 #include "base/bind.h"
12 #include "base/callback_helpers.h"
13 #include "base/location.h"
14 #include "base/message_loop/message_loop_proxy.h"
15 #include "media/base/audio_decoder_config.h"
16 #include "media/base/bind_to_loop.h"
17 #include "media/base/stream_parser_buffer.h"
18 #include "media/base/video_decoder_config.h"
19 #include "media/filters/stream_parser_factory.h"
20 #include "media/webm/webm_webvtt_parser.h"
22 using base::TimeDelta
;
26 // Contains state belonging to a source id.
29 // Callback signature used to create ChunkDemuxerStreams.
30 typedef base::Callback
<ChunkDemuxerStream
*(
31 DemuxerStream::Type
)> CreateDemuxerStreamCB
;
33 // Callback signature used to notify ChunkDemuxer of timestamps
34 // that may cause the duration to be updated.
35 typedef base::Callback
<void(
36 TimeDelta
, ChunkDemuxerStream
*)> IncreaseDurationCB
;
38 SourceState(scoped_ptr
<StreamParser
> stream_parser
, const LogCB
& log_cb
,
39 const CreateDemuxerStreamCB
& create_demuxer_stream_cb
,
40 const IncreaseDurationCB
& increase_duration_cb
);
42 void Init(const StreamParser::InitCB
& init_cb
,
45 const StreamParser::NewTextBuffersCB
& text_cb
,
46 const StreamParser::NeedKeyCB
& need_key_cb
,
47 const AddTextTrackCB
& add_text_track_cb
);
49 // Appends new data to the StreamParser.
50 // Returns true if the data was successfully appended. Returns false if an
52 bool Append(const uint8
* data
, size_t length
);
54 // Aborts the current append sequence and resets the parser.
57 // Sets |timestamp_offset_| if possible.
58 // Returns if the offset was set. Returns false if the offset could not be
59 // updated at this time.
60 bool SetTimestampOffset(TimeDelta timestamp_offset
);
62 TimeDelta
timestamp_offset() const { return timestamp_offset_
; }
64 void set_append_window_start(TimeDelta start
) {
65 append_window_start_
= start
;
67 void set_append_window_end(TimeDelta end
) { append_window_end_
= end
; }
70 // Called by the |stream_parser_| when a new initialization segment is
72 // Returns true on a successful call. Returns false if an error occured while
73 // processing decoder configurations.
74 bool OnNewConfigs(bool allow_audio
, bool allow_video
,
75 const AudioDecoderConfig
& audio_config
,
76 const VideoDecoderConfig
& video_config
);
78 // Called by the |stream_parser_| at the beginning of a new media segment.
79 void OnNewMediaSegment();
81 // Called by the |stream_parser_| at the end of a media segment.
82 void OnEndOfMediaSegment();
84 // Called by the |stream_parser_| when new buffers have been parsed. It
85 // applies |timestamp_offset_| to all buffers in |audio_buffers| and
86 // |video_buffers| and then calls Append() on |audio_| and/or
87 // |video_| with the modified buffers.
88 // Returns true on a successful call. Returns false if an error occured while
89 // processing the buffers.
90 bool OnNewBuffers(const StreamParser::BufferQueue
& audio_buffers
,
91 const StreamParser::BufferQueue
& video_buffers
);
93 // Called by the |stream_parser_| when new text buffers have been parsed. It
94 // applies |timestamp_offset_| to all buffers in |buffers| and then calls
95 // |new_buffers_cb| with the modified buffers.
96 // Returns true on a successful call. Returns false if an error occured while
97 // processing the buffers.
98 bool OnTextBuffers(const StreamParser::NewTextBuffersCB
& new_buffers_cb
,
99 TextTrack
* text_track
,
100 const StreamParser::BufferQueue
& buffers
);
102 // Helper function that adds |timestamp_offset_| to each buffer in |buffers|.
103 void AdjustBufferTimestamps(const StreamParser::BufferQueue
& buffers
);
105 // Filters out buffers that are outside of the append window
106 // [|append_window_start_|, |append_window_end_|).
107 // |needs_keyframe| is a pointer to the |xxx_need_keyframe_| flag
108 // associated with the |buffers|. Its state is read an updated as
109 // this method filters |buffers|.
110 // Buffers that are inside the append window are appended to the end
111 // of |filtered_buffers|.
112 void FilterWithAppendWindow(const StreamParser::BufferQueue
& buffers
,
113 bool* needs_keyframe
,
114 StreamParser::BufferQueue
* filtered_buffers
);
116 CreateDemuxerStreamCB create_demuxer_stream_cb_
;
117 IncreaseDurationCB increase_duration_cb_
;
119 // The offset to apply to media segment timestamps.
120 TimeDelta timestamp_offset_
;
122 TimeDelta append_window_start_
;
123 TimeDelta append_window_end_
;
125 // Set to true if the next buffers appended within the append window
126 // represent the start of a new media segment. This flag being set
127 // triggers a call to |new_segment_cb_| when the new buffers are
128 // appended. The flag is set on actual media segment boundaries and
129 // when the "append window" filtering causes discontinuities in the
131 bool new_media_segment_
;
133 // Keeps track of whether |timestamp_offset_| can be modified.
134 bool can_update_offset_
;
136 // The object used to parse appended data.
137 scoped_ptr
<StreamParser
> stream_parser_
;
139 ChunkDemuxerStream
* audio_
;
140 bool audio_needs_keyframe_
;
142 ChunkDemuxerStream
* video_
;
143 bool video_needs_keyframe_
;
147 DISALLOW_COPY_AND_ASSIGN(SourceState
);
150 class ChunkDemuxerStream
: public DemuxerStream
{
152 typedef std::deque
<scoped_refptr
<StreamParserBuffer
> > BufferQueue
;
154 explicit ChunkDemuxerStream(Type type
);
155 virtual ~ChunkDemuxerStream();
157 // ChunkDemuxerStream control methods.
158 void StartReturningData();
160 void CompletePendingReadIfPossible();
163 // SourceBufferStream manipulation methods.
164 void Seek(TimeDelta time
);
165 bool IsSeekWaitingForData() const;
167 // Add buffers to this stream. Buffers are stored in SourceBufferStreams,
168 // which handle ordering and overlap resolution.
169 // Returns true if buffers were successfully added.
170 bool Append(const StreamParser::BufferQueue
& buffers
);
172 // Removes buffers between |start| and |end| according to the steps
173 // in the "Coded Frame Removal Algorithm" in the Media Source
175 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#sourcebuffer-coded-frame-removal
177 // |duration| is the current duration of the presentation. It is
178 // required by the computation outlined in the spec.
179 void Remove(TimeDelta start
, TimeDelta end
, TimeDelta duration
);
181 // Signal to the stream that duration has changed to |duration|.
182 void OnSetDuration(TimeDelta duration
);
184 // Returns the range of buffered data in this stream, capped at |duration|.
185 Ranges
<TimeDelta
> GetBufferedRanges(TimeDelta duration
) const;
187 // Signal to the stream that buffers handed in through subsequent calls to
188 // Append() belong to a media segment that starts at |start_timestamp|.
189 void OnNewMediaSegment(TimeDelta start_timestamp
);
191 // Called when midstream config updates occur.
192 // Returns true if the new config is accepted.
193 // Returns false if the new config should trigger an error.
194 bool UpdateAudioConfig(const AudioDecoderConfig
& config
, const LogCB
& log_cb
);
195 bool UpdateVideoConfig(const VideoDecoderConfig
& config
, const LogCB
& log_cb
);
197 void MarkEndOfStream();
198 void UnmarkEndOfStream();
200 // DemuxerStream methods.
201 virtual void Read(const ReadCB
& read_cb
) OVERRIDE
;
202 virtual Type
type() OVERRIDE
;
203 virtual void EnableBitstreamConverter() OVERRIDE
;
204 virtual AudioDecoderConfig
audio_decoder_config() OVERRIDE
;
205 virtual VideoDecoderConfig
video_decoder_config() OVERRIDE
;
207 void set_memory_limit_for_testing(int memory_limit
) {
208 stream_
->set_memory_limit_for_testing(memory_limit
);
214 RETURNING_DATA_FOR_READS
,
215 RETURNING_ABORT_FOR_READS
,
219 // Assigns |state_| to |state|
220 void ChangeState_Locked(State state
);
222 void CompletePendingReadIfPossible_Locked();
224 // Gets the value to pass to the next Read() callback. Returns true if
225 // |status| and |buffer| should be passed to the callback. False indicates
226 // that |status| and |buffer| were not set and more data is needed.
227 bool GetNextBuffer_Locked(DemuxerStream::Status
* status
,
228 scoped_refptr
<StreamParserBuffer
>* buffer
);
230 // Specifies the type of the stream (must be AUDIO or VIDEO for now).
233 scoped_ptr
<SourceBufferStream
> stream_
;
235 mutable base::Lock lock_
;
239 DISALLOW_IMPLICIT_CONSTRUCTORS(ChunkDemuxerStream
);
242 SourceState::SourceState(scoped_ptr
<StreamParser
> stream_parser
,
244 const CreateDemuxerStreamCB
& create_demuxer_stream_cb
,
245 const IncreaseDurationCB
& increase_duration_cb
)
246 : create_demuxer_stream_cb_(create_demuxer_stream_cb
),
247 increase_duration_cb_(increase_duration_cb
),
248 append_window_end_(kInfiniteDuration()),
249 new_media_segment_(false),
250 can_update_offset_(true),
251 stream_parser_(stream_parser
.release()),
253 audio_needs_keyframe_(true),
255 video_needs_keyframe_(true),
257 DCHECK(!create_demuxer_stream_cb_
.is_null());
258 DCHECK(!increase_duration_cb_
.is_null());
261 void SourceState::Init(const StreamParser::InitCB
& init_cb
,
264 const StreamParser::NewTextBuffersCB
& text_cb
,
265 const StreamParser::NeedKeyCB
& need_key_cb
,
266 const AddTextTrackCB
& add_text_track_cb
) {
267 StreamParser::NewBuffersCB audio_cb
;
269 stream_parser_
->Init(init_cb
,
270 base::Bind(&SourceState::OnNewConfigs
,
271 base::Unretained(this),
274 base::Bind(&SourceState::OnNewBuffers
,
275 base::Unretained(this)),
276 base::Bind(&SourceState::OnTextBuffers
,
277 base::Unretained(this), text_cb
),
280 base::Bind(&SourceState::OnNewMediaSegment
,
281 base::Unretained(this)),
282 base::Bind(&SourceState::OnEndOfMediaSegment
,
283 base::Unretained(this)),
287 bool SourceState::SetTimestampOffset(TimeDelta timestamp_offset
) {
288 if (!can_update_offset_
)
291 timestamp_offset_
= timestamp_offset
;
295 bool SourceState::Append(const uint8
* data
, size_t length
) {
296 return stream_parser_
->Parse(data
, length
);
299 void SourceState::Abort() {
300 stream_parser_
->Flush();
301 audio_needs_keyframe_
= true;
302 video_needs_keyframe_
= true;
303 can_update_offset_
= true;
306 void SourceState::AdjustBufferTimestamps(
307 const StreamParser::BufferQueue
& buffers
) {
308 if (timestamp_offset_
== TimeDelta())
311 for (StreamParser::BufferQueue::const_iterator itr
= buffers
.begin();
312 itr
!= buffers
.end(); ++itr
) {
313 (*itr
)->SetDecodeTimestamp(
314 (*itr
)->GetDecodeTimestamp() + timestamp_offset_
);
315 (*itr
)->set_timestamp((*itr
)->timestamp() + timestamp_offset_
);
319 bool SourceState::OnNewConfigs(bool allow_audio
, bool allow_video
,
320 const AudioDecoderConfig
& audio_config
,
321 const VideoDecoderConfig
& video_config
) {
322 DVLOG(1) << "OnNewConfigs(" << allow_audio
<< ", " << allow_video
323 << ", " << audio_config
.IsValidConfig()
324 << ", " << video_config
.IsValidConfig() << ")";
326 if (!audio_config
.IsValidConfig() && !video_config
.IsValidConfig()) {
327 DVLOG(1) << "OnNewConfigs() : Audio & video config are not valid!";
331 // Signal an error if we get configuration info for stream types that weren't
332 // specified in AddId() or more configs after a stream is initialized.
333 if (allow_audio
!= audio_config
.IsValidConfig()) {
335 << "Initialization segment"
336 << (audio_config
.IsValidConfig() ? " has" : " does not have")
337 << " an audio track, but the mimetype"
338 << (allow_audio
? " specifies" : " does not specify")
339 << " an audio codec.";
343 if (allow_video
!= video_config
.IsValidConfig()) {
345 << "Initialization segment"
346 << (video_config
.IsValidConfig() ? " has" : " does not have")
347 << " a video track, but the mimetype"
348 << (allow_video
? " specifies" : " does not specify")
349 << " a video codec.";
354 if (audio_config
.IsValidConfig()) {
356 audio_
= create_demuxer_stream_cb_
.Run(DemuxerStream::AUDIO
);
359 DVLOG(1) << "Failed to create an audio stream.";
364 success
&= audio_
->UpdateAudioConfig(audio_config
, log_cb_
);
367 if (video_config
.IsValidConfig()) {
369 video_
= create_demuxer_stream_cb_
.Run(DemuxerStream::VIDEO
);
372 DVLOG(1) << "Failed to create a video stream.";
377 success
&= video_
->UpdateVideoConfig(video_config
, log_cb_
);
380 DVLOG(1) << "OnNewConfigs() : " << (success
? "success" : "failed");
384 void SourceState::OnNewMediaSegment() {
385 DVLOG(2) << "OnNewMediaSegment()";
386 can_update_offset_
= false;
387 new_media_segment_
= true;
390 void SourceState::OnEndOfMediaSegment() {
391 DVLOG(2) << "OnEndOfMediaSegment()";
392 can_update_offset_
= true;
393 new_media_segment_
= false;
396 bool SourceState::OnNewBuffers(const StreamParser::BufferQueue
& audio_buffers
,
397 const StreamParser::BufferQueue
& video_buffers
) {
398 DCHECK(!audio_buffers
.empty() || !video_buffers
.empty());
399 AdjustBufferTimestamps(audio_buffers
);
400 AdjustBufferTimestamps(video_buffers
);
402 StreamParser::BufferQueue filtered_audio
;
403 StreamParser::BufferQueue filtered_video
;
405 FilterWithAppendWindow(audio_buffers
, &audio_needs_keyframe_
,
408 FilterWithAppendWindow(video_buffers
, &video_needs_keyframe_
,
411 if (filtered_audio
.empty() && filtered_video
.empty())
414 if (new_media_segment_
) {
415 // Find the earliest timestamp in the filtered buffers and use that for the
416 // segment start timestamp.
417 TimeDelta segment_timestamp
= kNoTimestamp();
419 if (!filtered_audio
.empty())
420 segment_timestamp
= filtered_audio
.front()->GetDecodeTimestamp();
422 if (!filtered_video
.empty() &&
423 (segment_timestamp
== kNoTimestamp() ||
424 filtered_video
.front()->GetDecodeTimestamp() < segment_timestamp
)) {
425 segment_timestamp
= filtered_video
.front()->GetDecodeTimestamp();
428 new_media_segment_
= false;
431 audio_
->OnNewMediaSegment(segment_timestamp
);
434 video_
->OnNewMediaSegment(segment_timestamp
);
437 if (!filtered_audio
.empty()) {
438 if (!audio_
|| !audio_
->Append(filtered_audio
))
440 increase_duration_cb_
.Run(filtered_audio
.back()->timestamp(), audio_
);
443 if (!filtered_video
.empty()) {
444 if (!video_
|| !video_
->Append(filtered_video
))
446 increase_duration_cb_
.Run(filtered_video
.back()->timestamp(), video_
);
452 bool SourceState::OnTextBuffers(
453 const StreamParser::NewTextBuffersCB
& new_buffers_cb
,
454 TextTrack
* text_track
,
455 const StreamParser::BufferQueue
& buffers
) {
456 if (new_buffers_cb
.is_null())
459 AdjustBufferTimestamps(buffers
);
461 return new_buffers_cb
.Run(text_track
, buffers
);
464 void SourceState::FilterWithAppendWindow(
465 const StreamParser::BufferQueue
& buffers
, bool* needs_keyframe
,
466 StreamParser::BufferQueue
* filtered_buffers
) {
467 DCHECK(needs_keyframe
);
468 DCHECK(filtered_buffers
);
470 // This loop implements steps 1.9, 1.10, & 1.11 of the "Coded frame
471 // processing loop" in the Media Source Extensions spec.
472 // These steps filter out buffers that are not within the "append
473 // window" and handles resyncing on the next random access point
474 // (i.e., next keyframe) if a buffer gets dropped.
475 for (StreamParser::BufferQueue::const_iterator itr
= buffers
.begin();
476 itr
!= buffers
.end(); ++itr
) {
477 // Filter out buffers that are outside the append window. Anytime
478 // a buffer gets dropped we need to set |*needs_keyframe| to true
479 // because we can only resume decoding at keyframes.
480 TimeDelta presentation_timestamp
= (*itr
)->timestamp();
482 // TODO(acolwell): Change |frame_end_timestamp| value to
483 // |presentation_timestamp + (*itr)->duration()|, like the spec
484 // requires, once frame durations are actually present in all buffers.
485 TimeDelta frame_end_timestamp
= presentation_timestamp
;
486 if (presentation_timestamp
< append_window_start_
||
487 frame_end_timestamp
> append_window_end_
) {
488 DVLOG(1) << "Dropping buffer outside append window."
489 << " presentation_timestamp "
490 << presentation_timestamp
.InSecondsF();
491 *needs_keyframe
= true;
493 // This triggers a discontinuity so we need to treat the next frames
494 // appended within the append window as if they were the beginning of a
496 new_media_segment_
= true;
500 // If |*needs_keyframe| is true then filter out buffers until we
501 // encounter the next keyframe.
502 if (*needs_keyframe
) {
503 if (!(*itr
)->IsKeyframe()) {
504 DVLOG(1) << "Dropping non-keyframe. presentation_timestamp "
505 << presentation_timestamp
.InSecondsF();
509 *needs_keyframe
= false;
512 filtered_buffers
->push_back(*itr
);
516 ChunkDemuxerStream::ChunkDemuxerStream(Type type
)
518 state_(UNINITIALIZED
) {
521 void ChunkDemuxerStream::StartReturningData() {
522 DVLOG(1) << "ChunkDemuxerStream::StartReturningData()";
523 base::AutoLock
auto_lock(lock_
);
524 DCHECK(read_cb_
.is_null());
525 ChangeState_Locked(RETURNING_DATA_FOR_READS
);
528 void ChunkDemuxerStream::AbortReads() {
529 DVLOG(1) << "ChunkDemuxerStream::AbortReads()";
530 base::AutoLock
auto_lock(lock_
);
531 ChangeState_Locked(RETURNING_ABORT_FOR_READS
);
532 if (!read_cb_
.is_null())
533 base::ResetAndReturn(&read_cb_
).Run(kAborted
, NULL
);
536 void ChunkDemuxerStream::CompletePendingReadIfPossible() {
537 base::AutoLock
auto_lock(lock_
);
538 if (read_cb_
.is_null())
541 CompletePendingReadIfPossible_Locked();
544 void ChunkDemuxerStream::Shutdown() {
545 DVLOG(1) << "ChunkDemuxerStream::Shutdown()";
546 base::AutoLock
auto_lock(lock_
);
547 ChangeState_Locked(SHUTDOWN
);
549 // Pass an end of stream buffer to the pending callback to signal that no more
550 // data will be sent.
551 if (!read_cb_
.is_null()) {
552 base::ResetAndReturn(&read_cb_
).Run(DemuxerStream::kOk
,
553 StreamParserBuffer::CreateEOSBuffer());
557 bool ChunkDemuxerStream::IsSeekWaitingForData() const {
558 base::AutoLock
auto_lock(lock_
);
559 return stream_
->IsSeekPending();
562 void ChunkDemuxerStream::Seek(TimeDelta time
) {
563 DVLOG(1) << "ChunkDemuxerStream::Seek(" << time
.InSecondsF() << ")";
564 base::AutoLock
auto_lock(lock_
);
565 DCHECK(read_cb_
.is_null());
566 DCHECK(state_
== UNINITIALIZED
|| state_
== RETURNING_ABORT_FOR_READS
)
572 bool ChunkDemuxerStream::Append(const StreamParser::BufferQueue
& buffers
) {
576 base::AutoLock
auto_lock(lock_
);
577 DCHECK_NE(state_
, SHUTDOWN
);
578 if (!stream_
->Append(buffers
)) {
579 DVLOG(1) << "ChunkDemuxerStream::Append() : stream append failed";
583 if (!read_cb_
.is_null())
584 CompletePendingReadIfPossible_Locked();
589 void ChunkDemuxerStream::Remove(TimeDelta start
, TimeDelta end
,
590 TimeDelta duration
) {
591 base::AutoLock
auto_lock(lock_
);
592 stream_
->Remove(start
, end
, duration
);
595 void ChunkDemuxerStream::OnSetDuration(TimeDelta duration
) {
596 base::AutoLock
auto_lock(lock_
);
597 stream_
->OnSetDuration(duration
);
600 Ranges
<TimeDelta
> ChunkDemuxerStream::GetBufferedRanges(
601 TimeDelta duration
) const {
602 base::AutoLock
auto_lock(lock_
);
603 Ranges
<TimeDelta
> range
= stream_
->GetBufferedTime();
605 if (range
.size() == 0u)
608 // Clamp the end of the stream's buffered ranges to fit within the duration.
609 // This can be done by intersecting the stream's range with the valid time
611 Ranges
<TimeDelta
> valid_time_range
;
612 valid_time_range
.Add(range
.start(0), duration
);
613 return range
.IntersectionWith(valid_time_range
);
616 void ChunkDemuxerStream::OnNewMediaSegment(TimeDelta start_timestamp
) {
617 DVLOG(2) << "ChunkDemuxerStream::OnNewMediaSegment("
618 << start_timestamp
.InSecondsF() << ")";
619 base::AutoLock
auto_lock(lock_
);
620 stream_
->OnNewMediaSegment(start_timestamp
);
623 bool ChunkDemuxerStream::UpdateAudioConfig(const AudioDecoderConfig
& config
,
624 const LogCB
& log_cb
) {
625 DCHECK(config
.IsValidConfig());
626 DCHECK_EQ(type_
, AUDIO
);
627 base::AutoLock
auto_lock(lock_
);
629 DCHECK_EQ(state_
, UNINITIALIZED
);
630 stream_
.reset(new SourceBufferStream(config
, log_cb
));
634 return stream_
->UpdateAudioConfig(config
);
637 bool ChunkDemuxerStream::UpdateVideoConfig(const VideoDecoderConfig
& config
,
638 const LogCB
& log_cb
) {
639 DCHECK(config
.IsValidConfig());
640 DCHECK_EQ(type_
, VIDEO
);
641 base::AutoLock
auto_lock(lock_
);
644 DCHECK_EQ(state_
, UNINITIALIZED
);
645 stream_
.reset(new SourceBufferStream(config
, log_cb
));
649 return stream_
->UpdateVideoConfig(config
);
652 void ChunkDemuxerStream::MarkEndOfStream() {
653 base::AutoLock
auto_lock(lock_
);
654 stream_
->MarkEndOfStream();
657 void ChunkDemuxerStream::UnmarkEndOfStream() {
658 base::AutoLock
auto_lock(lock_
);
659 stream_
->UnmarkEndOfStream();
662 // DemuxerStream methods.
663 void ChunkDemuxerStream::Read(const ReadCB
& read_cb
) {
664 base::AutoLock
auto_lock(lock_
);
665 DCHECK_NE(state_
, UNINITIALIZED
);
666 DCHECK(read_cb_
.is_null());
668 read_cb_
= BindToCurrentLoop(read_cb
);
669 CompletePendingReadIfPossible_Locked();
672 DemuxerStream::Type
ChunkDemuxerStream::type() { return type_
; }
674 void ChunkDemuxerStream::EnableBitstreamConverter() {}
676 AudioDecoderConfig
ChunkDemuxerStream::audio_decoder_config() {
677 CHECK_EQ(type_
, AUDIO
);
678 base::AutoLock
auto_lock(lock_
);
679 return stream_
->GetCurrentAudioDecoderConfig();
682 VideoDecoderConfig
ChunkDemuxerStream::video_decoder_config() {
683 CHECK_EQ(type_
, VIDEO
);
684 base::AutoLock
auto_lock(lock_
);
685 return stream_
->GetCurrentVideoDecoderConfig();
688 void ChunkDemuxerStream::ChangeState_Locked(State state
) {
689 lock_
.AssertAcquired();
690 DVLOG(1) << "ChunkDemuxerStream::ChangeState_Locked() : "
692 << " - " << state_
<< " -> " << state
;
696 ChunkDemuxerStream::~ChunkDemuxerStream() {}
698 void ChunkDemuxerStream::CompletePendingReadIfPossible_Locked() {
699 lock_
.AssertAcquired();
700 DCHECK(!read_cb_
.is_null());
702 DemuxerStream::Status status
;
703 scoped_refptr
<StreamParserBuffer
> buffer
;
709 case RETURNING_DATA_FOR_READS
:
710 switch (stream_
->GetNextBuffer(&buffer
)) {
711 case SourceBufferStream::kSuccess
:
712 status
= DemuxerStream::kOk
;
714 case SourceBufferStream::kNeedBuffer
:
715 // Return early without calling |read_cb_| since we don't have
716 // any data to return yet.
718 case SourceBufferStream::kEndOfStream
:
719 status
= DemuxerStream::kOk
;
720 buffer
= StreamParserBuffer::CreateEOSBuffer();
722 case SourceBufferStream::kConfigChange
:
723 DVLOG(2) << "Config change reported to ChunkDemuxerStream.";
724 status
= kConfigChanged
;
729 case RETURNING_ABORT_FOR_READS
:
730 // Null buffers should be returned in this state since we are waiting
731 // for a seek. Any buffers in the SourceBuffer should NOT be returned
732 // because they are associated with the seek.
733 status
= DemuxerStream::kAborted
;
737 status
= DemuxerStream::kOk
;
738 buffer
= StreamParserBuffer::CreateEOSBuffer();
742 base::ResetAndReturn(&read_cb_
).Run(status
, buffer
);
745 ChunkDemuxer::ChunkDemuxer(const base::Closure
& open_cb
,
746 const NeedKeyCB
& need_key_cb
,
747 const AddTextTrackCB
& add_text_track_cb
,
749 : state_(WAITING_FOR_INIT
),
750 cancel_next_seek_(false),
753 need_key_cb_(need_key_cb
),
754 add_text_track_cb_(add_text_track_cb
),
756 duration_(kNoTimestamp()),
757 user_specified_duration_(-1) {
758 DCHECK(!open_cb_
.is_null());
759 DCHECK(!need_key_cb_
.is_null());
762 void ChunkDemuxer::Initialize(DemuxerHost
* host
, const PipelineStatusCB
& cb
) {
763 DVLOG(1) << "Init()";
765 base::AutoLock
auto_lock(lock_
);
767 init_cb_
= BindToCurrentLoop(cb
);
768 if (state_
== SHUTDOWN
) {
769 base::ResetAndReturn(&init_cb_
).Run(DEMUXER_ERROR_COULD_NOT_OPEN
);
772 DCHECK_EQ(state_
, WAITING_FOR_INIT
);
775 ChangeState_Locked(INITIALIZING
);
777 base::ResetAndReturn(&open_cb_
).Run();
780 void ChunkDemuxer::Stop(const base::Closure
& callback
) {
781 DVLOG(1) << "Stop()";
786 void ChunkDemuxer::Seek(TimeDelta time
, const PipelineStatusCB
& cb
) {
787 DVLOG(1) << "Seek(" << time
.InSecondsF() << ")";
788 DCHECK(time
>= TimeDelta());
790 base::AutoLock
auto_lock(lock_
);
791 DCHECK(seek_cb_
.is_null());
793 seek_cb_
= BindToCurrentLoop(cb
);
794 if (state_
!= INITIALIZED
&& state_
!= ENDED
) {
795 base::ResetAndReturn(&seek_cb_
).Run(PIPELINE_ERROR_INVALID_STATE
);
799 if (cancel_next_seek_
) {
800 cancel_next_seek_
= false;
801 base::ResetAndReturn(&seek_cb_
).Run(PIPELINE_OK
);
805 SeekAllSources(time
);
806 StartReturningData();
808 if (IsSeekWaitingForData_Locked()) {
809 DVLOG(1) << "Seek() : waiting for more data to arrive.";
813 base::ResetAndReturn(&seek_cb_
).Run(PIPELINE_OK
);
816 void ChunkDemuxer::OnAudioRendererDisabled() {
817 base::AutoLock
auto_lock(lock_
);
819 disabled_audio_
= audio_
.Pass();
822 // Demuxer implementation.
823 DemuxerStream
* ChunkDemuxer::GetStream(DemuxerStream::Type type
) {
824 base::AutoLock
auto_lock(lock_
);
825 if (type
== DemuxerStream::VIDEO
)
828 if (type
== DemuxerStream::AUDIO
)
834 TimeDelta
ChunkDemuxer::GetStartTime() const {
838 void ChunkDemuxer::StartWaitingForSeek(TimeDelta seek_time
) {
839 DVLOG(1) << "StartWaitingForSeek()";
840 base::AutoLock
auto_lock(lock_
);
841 DCHECK(state_
== INITIALIZED
|| state_
== ENDED
|| state_
== SHUTDOWN
||
842 state_
== PARSE_ERROR
) << state_
;
843 DCHECK(seek_cb_
.is_null());
845 if (state_
== SHUTDOWN
|| state_
== PARSE_ERROR
)
849 SeekAllSources(seek_time
);
851 // Cancel state set in CancelPendingSeek() since we want to
852 // accept the next Seek().
853 cancel_next_seek_
= false;
856 void ChunkDemuxer::CancelPendingSeek(TimeDelta seek_time
) {
857 base::AutoLock
auto_lock(lock_
);
858 DCHECK_NE(state_
, INITIALIZING
);
859 DCHECK(seek_cb_
.is_null() || IsSeekWaitingForData_Locked());
861 if (cancel_next_seek_
)
865 SeekAllSources(seek_time
);
867 if (seek_cb_
.is_null()) {
868 cancel_next_seek_
= true;
872 base::ResetAndReturn(&seek_cb_
).Run(PIPELINE_OK
);
875 ChunkDemuxer::Status
ChunkDemuxer::AddId(const std::string
& id
,
876 const std::string
& type
,
877 std::vector
<std::string
>& codecs
) {
878 base::AutoLock
auto_lock(lock_
);
880 if ((state_
!= WAITING_FOR_INIT
&& state_
!= INITIALIZING
) || IsValidId(id
))
881 return kReachedIdLimit
;
883 bool has_audio
= false;
884 bool has_video
= false;
885 scoped_ptr
<media::StreamParser
> stream_parser(
886 StreamParserFactory::Create(type
, codecs
, log_cb_
,
887 &has_audio
, &has_video
));
890 return ChunkDemuxer::kNotSupported
;
892 if ((has_audio
&& !source_id_audio_
.empty()) ||
893 (has_video
&& !source_id_video_
.empty()))
894 return kReachedIdLimit
;
897 source_id_audio_
= id
;
900 source_id_video_
= id
;
902 scoped_ptr
<SourceState
> source_state(
903 new SourceState(stream_parser
.Pass(), log_cb_
,
904 base::Bind(&ChunkDemuxer::CreateDemuxerStream
,
905 base::Unretained(this)),
906 base::Bind(&ChunkDemuxer::IncreaseDurationIfNecessary
,
907 base::Unretained(this))));
910 base::Bind(&ChunkDemuxer::OnSourceInitDone
, base::Unretained(this)),
913 base::Bind(&ChunkDemuxer::OnTextBuffers
, base::Unretained(this)),
917 source_state_map_
[id
] = source_state
.release();
921 void ChunkDemuxer::RemoveId(const std::string
& id
) {
922 base::AutoLock
auto_lock(lock_
);
923 CHECK(IsValidId(id
));
925 delete source_state_map_
[id
];
926 source_state_map_
.erase(id
);
928 if (source_id_audio_
== id
) {
931 source_id_audio_
.clear();
934 if (source_id_video_
== id
) {
937 source_id_video_
.clear();
941 Ranges
<TimeDelta
> ChunkDemuxer::GetBufferedRanges(const std::string
& id
) const {
942 base::AutoLock
auto_lock(lock_
);
944 DCHECK(IsValidId(id
));
945 DCHECK(id
== source_id_audio_
|| id
== source_id_video_
);
947 if (id
== source_id_audio_
&& id
!= source_id_video_
) {
948 // Only include ranges that have been buffered in |audio_|
949 return audio_
? audio_
->GetBufferedRanges(duration_
) : Ranges
<TimeDelta
>();
952 if (id
!= source_id_audio_
&& id
== source_id_video_
) {
953 // Only include ranges that have been buffered in |video_|
954 return video_
? video_
->GetBufferedRanges(duration_
) : Ranges
<TimeDelta
>();
957 return ComputeIntersection();
960 Ranges
<TimeDelta
> ChunkDemuxer::ComputeIntersection() const {
961 lock_
.AssertAcquired();
963 if (!audio_
|| !video_
)
964 return Ranges
<TimeDelta
>();
966 // Include ranges that have been buffered in both |audio_| and |video_|.
967 Ranges
<TimeDelta
> audio_ranges
= audio_
->GetBufferedRanges(duration_
);
968 Ranges
<TimeDelta
> video_ranges
= video_
->GetBufferedRanges(duration_
);
969 Ranges
<TimeDelta
> result
= audio_ranges
.IntersectionWith(video_ranges
);
971 if (state_
== ENDED
&& result
.size() > 0) {
972 // If appending has ended, extend the last intersection range to include the
973 // max end time of the last audio/video range. This allows the buffered
974 // information to match the actual time range that will get played out if
975 // the streams have slightly different lengths.
976 TimeDelta audio_start
= audio_ranges
.start(audio_ranges
.size() - 1);
977 TimeDelta audio_end
= audio_ranges
.end(audio_ranges
.size() - 1);
978 TimeDelta video_start
= video_ranges
.start(video_ranges
.size() - 1);
979 TimeDelta video_end
= video_ranges
.end(video_ranges
.size() - 1);
981 // Verify the last audio range overlaps with the last video range.
982 // This is enforced by the logic that controls the transition to ENDED.
983 DCHECK((audio_start
<= video_start
&& video_start
<= audio_end
) ||
984 (video_start
<= audio_start
&& audio_start
<= video_end
));
985 result
.Add(result
.end(result
.size()-1), std::max(audio_end
, video_end
));
991 void ChunkDemuxer::AppendData(const std::string
& id
,
994 DVLOG(1) << "AppendData(" << id
<< ", " << length
<< ")";
998 Ranges
<TimeDelta
> ranges
;
1001 base::AutoLock
auto_lock(lock_
);
1002 DCHECK_NE(state_
, ENDED
);
1004 // Capture if any of the SourceBuffers are waiting for data before we start
1006 bool old_waiting_for_data
= IsSeekWaitingForData_Locked();
1015 DCHECK(IsValidId(id
));
1016 if (!source_state_map_
[id
]->Append(data
, length
)) {
1017 ReportError_Locked(DEMUXER_ERROR_COULD_NOT_OPEN
);
1023 DCHECK(IsValidId(id
));
1024 if (!source_state_map_
[id
]->Append(data
, length
)) {
1025 ReportError_Locked(PIPELINE_ERROR_DECODE
);
1031 DVLOG(1) << "AppendData(): Ignoring data after a parse error.";
1034 case WAITING_FOR_INIT
:
1037 DVLOG(1) << "AppendData(): called in unexpected state " << state_
;
1041 // Check to see if data was appended at the pending seek point. This
1042 // indicates we have parsed enough data to complete the seek.
1043 if (old_waiting_for_data
&& !IsSeekWaitingForData_Locked() &&
1044 !seek_cb_
.is_null()) {
1045 base::ResetAndReturn(&seek_cb_
).Run(PIPELINE_OK
);
1048 ranges
= GetBufferedRanges();
1051 for (size_t i
= 0; i
< ranges
.size(); ++i
)
1052 host_
->AddBufferedTimeRange(ranges
.start(i
), ranges
.end(i
));
1055 void ChunkDemuxer::Abort(const std::string
& id
) {
1056 DVLOG(1) << "Abort(" << id
<< ")";
1057 base::AutoLock
auto_lock(lock_
);
1058 DCHECK(!id
.empty());
1059 CHECK(IsValidId(id
));
1060 source_state_map_
[id
]->Abort();
1063 void ChunkDemuxer::Remove(const std::string
& id
, base::TimeDelta start
,
1064 base::TimeDelta end
) {
1065 DVLOG(1) << "Remove(" << id
<< ", " << start
.InSecondsF()
1066 << ", " << end
.InSecondsF() << ")";
1067 base::AutoLock
auto_lock(lock_
);
1069 if (id
== source_id_audio_
&& audio_
)
1070 audio_
->Remove(start
, end
, duration_
);
1072 if (id
== source_id_video_
&& video_
)
1073 video_
->Remove(start
, end
, duration_
);
1076 double ChunkDemuxer::GetDuration() {
1077 base::AutoLock
auto_lock(lock_
);
1078 return GetDuration_Locked();
1081 double ChunkDemuxer::GetDuration_Locked() {
1082 lock_
.AssertAcquired();
1083 if (duration_
== kNoTimestamp())
1084 return std::numeric_limits
<double>::quiet_NaN();
1086 // Return positive infinity if the resource is unbounded.
1087 // http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#dom-media-duration
1088 if (duration_
== kInfiniteDuration())
1089 return std::numeric_limits
<double>::infinity();
1091 if (user_specified_duration_
>= 0)
1092 return user_specified_duration_
;
1094 return duration_
.InSecondsF();
1097 void ChunkDemuxer::SetDuration(double duration
) {
1098 base::AutoLock
auto_lock(lock_
);
1099 DVLOG(1) << "SetDuration(" << duration
<< ")";
1100 DCHECK_GE(duration
, 0);
1102 if (duration
== GetDuration_Locked())
1105 // Compute & bounds check the TimeDelta representation of duration.
1106 // This can be different if the value of |duration| doesn't fit the range or
1107 // precision of TimeDelta.
1108 TimeDelta min_duration
= TimeDelta::FromInternalValue(1);
1109 TimeDelta max_duration
= TimeDelta::FromInternalValue(kint64max
- 1);
1110 double min_duration_in_seconds
= min_duration
.InSecondsF();
1111 double max_duration_in_seconds
= max_duration
.InSecondsF();
1113 TimeDelta duration_td
;
1114 if (duration
== std::numeric_limits
<double>::infinity()) {
1115 duration_td
= media::kInfiniteDuration();
1116 } else if (duration
< min_duration_in_seconds
) {
1117 duration_td
= min_duration
;
1118 } else if (duration
> max_duration_in_seconds
) {
1119 duration_td
= max_duration
;
1121 duration_td
= TimeDelta::FromMicroseconds(
1122 duration
* base::Time::kMicrosecondsPerSecond
);
1125 DCHECK(duration_td
> TimeDelta());
1127 user_specified_duration_
= duration
;
1128 duration_
= duration_td
;
1129 host_
->SetDuration(duration_
);
1132 audio_
->OnSetDuration(duration_
);
1135 video_
->OnSetDuration(duration_
);
1138 bool ChunkDemuxer::SetTimestampOffset(const std::string
& id
, TimeDelta offset
) {
1139 base::AutoLock
auto_lock(lock_
);
1140 DVLOG(1) << "SetTimestampOffset(" << id
<< ", " << offset
.InSecondsF() << ")";
1141 CHECK(IsValidId(id
));
1143 return source_state_map_
[id
]->SetTimestampOffset(offset
);
1146 void ChunkDemuxer::MarkEndOfStream(PipelineStatus status
) {
1147 DVLOG(1) << "MarkEndOfStream(" << status
<< ")";
1148 base::AutoLock
auto_lock(lock_
);
1149 DCHECK_NE(state_
, WAITING_FOR_INIT
);
1150 DCHECK_NE(state_
, ENDED
);
1152 if (state_
== SHUTDOWN
|| state_
== PARSE_ERROR
)
1155 if (state_
== INITIALIZING
) {
1156 ReportError_Locked(DEMUXER_ERROR_COULD_NOT_OPEN
);
1160 bool old_waiting_for_data
= IsSeekWaitingForData_Locked();
1162 audio_
->MarkEndOfStream();
1165 video_
->MarkEndOfStream();
1167 CompletePendingReadsIfPossible();
1169 // Give a chance to resume the pending seek process.
1170 if (status
!= PIPELINE_OK
) {
1171 ReportError_Locked(status
);
1175 ChangeState_Locked(ENDED
);
1176 DecreaseDurationIfNecessary();
1178 if (old_waiting_for_data
&& !IsSeekWaitingForData_Locked() &&
1179 !seek_cb_
.is_null()) {
1180 base::ResetAndReturn(&seek_cb_
).Run(PIPELINE_OK
);
1184 void ChunkDemuxer::UnmarkEndOfStream() {
1185 DVLOG(1) << "UnmarkEndOfStream()";
1186 base::AutoLock
auto_lock(lock_
);
1187 DCHECK_EQ(state_
, ENDED
);
1189 ChangeState_Locked(INITIALIZED
);
1192 audio_
->UnmarkEndOfStream();
1195 video_
->UnmarkEndOfStream();
1198 void ChunkDemuxer::SetAppendWindowStart(const std::string
& id
,
1200 base::AutoLock
auto_lock(lock_
);
1201 DVLOG(1) << "SetAppendWindowStart(" << id
<< ", "
1202 << start
.InSecondsF() << ")";
1203 CHECK(IsValidId(id
));
1204 source_state_map_
[id
]->set_append_window_start(start
);
1207 void ChunkDemuxer::SetAppendWindowEnd(const std::string
& id
, TimeDelta end
) {
1208 base::AutoLock
auto_lock(lock_
);
1209 DVLOG(1) << "SetAppendWindowEnd(" << id
<< ", " << end
.InSecondsF() << ")";
1210 CHECK(IsValidId(id
));
1211 source_state_map_
[id
]->set_append_window_end(end
);
1214 void ChunkDemuxer::Shutdown() {
1215 DVLOG(1) << "Shutdown()";
1216 base::AutoLock
auto_lock(lock_
);
1218 if (state_
== SHUTDOWN
)
1227 ChangeState_Locked(SHUTDOWN
);
1229 if(!seek_cb_
.is_null())
1230 base::ResetAndReturn(&seek_cb_
).Run(PIPELINE_ERROR_ABORT
);
1233 void ChunkDemuxer::SetMemoryLimitsForTesting(int memory_limit
) {
1235 audio_
->set_memory_limit_for_testing(memory_limit
);
1238 video_
->set_memory_limit_for_testing(memory_limit
);
1241 void ChunkDemuxer::ChangeState_Locked(State new_state
) {
1242 lock_
.AssertAcquired();
1243 DVLOG(1) << "ChunkDemuxer::ChangeState_Locked() : "
1244 << state_
<< " -> " << new_state
;
1248 ChunkDemuxer::~ChunkDemuxer() {
1249 DCHECK_NE(state_
, INITIALIZED
);
1250 for (SourceStateMap::iterator it
= source_state_map_
.begin();
1251 it
!= source_state_map_
.end(); ++it
) {
1254 source_state_map_
.clear();
1257 void ChunkDemuxer::ReportError_Locked(PipelineStatus error
) {
1258 DVLOG(1) << "ReportError_Locked(" << error
<< ")";
1259 lock_
.AssertAcquired();
1260 DCHECK_NE(error
, PIPELINE_OK
);
1262 ChangeState_Locked(PARSE_ERROR
);
1264 PipelineStatusCB cb
;
1266 if (!init_cb_
.is_null()) {
1267 std::swap(cb
, init_cb_
);
1269 if (!seek_cb_
.is_null())
1270 std::swap(cb
, seek_cb_
);
1279 if (!cb
.is_null()) {
1284 base::AutoUnlock
auto_unlock(lock_
);
1285 host_
->OnDemuxerError(error
);
1288 bool ChunkDemuxer::IsSeekWaitingForData_Locked() const {
1289 lock_
.AssertAcquired();
1290 bool waiting_for_data
= false;
1293 waiting_for_data
= audio_
->IsSeekWaitingForData();
1295 if (!waiting_for_data
&& video_
)
1296 waiting_for_data
= video_
->IsSeekWaitingForData();
1298 return waiting_for_data
;
1301 void ChunkDemuxer::OnSourceInitDone(bool success
, TimeDelta duration
) {
1302 DVLOG(1) << "OnSourceInitDone(" << success
<< ", "
1303 << duration
.InSecondsF() << ")";
1304 lock_
.AssertAcquired();
1305 DCHECK_EQ(state_
, INITIALIZING
);
1306 if (!success
|| (!audio_
&& !video_
)) {
1307 ReportError_Locked(DEMUXER_ERROR_COULD_NOT_OPEN
);
1311 if (duration
!= TimeDelta() && duration_
== kNoTimestamp())
1312 UpdateDuration(duration
);
1314 // Wait until all streams have initialized.
1315 if ((!source_id_audio_
.empty() && !audio_
) ||
1316 (!source_id_video_
.empty() && !video_
))
1319 SeekAllSources(GetStartTime());
1320 StartReturningData();
1322 if (duration_
== kNoTimestamp())
1323 duration_
= kInfiniteDuration();
1325 // The demuxer is now initialized after the |start_timestamp_| was set.
1326 ChangeState_Locked(INITIALIZED
);
1327 base::ResetAndReturn(&init_cb_
).Run(PIPELINE_OK
);
1331 ChunkDemuxer::CreateDemuxerStream(DemuxerStream::Type type
) {
1333 case DemuxerStream::AUDIO
:
1336 audio_
.reset(new ChunkDemuxerStream(DemuxerStream::AUDIO
));
1337 return audio_
.get();
1339 case DemuxerStream::VIDEO
:
1342 video_
.reset(new ChunkDemuxerStream(DemuxerStream::VIDEO
));
1343 return video_
.get();
1345 case DemuxerStream::UNKNOWN
:
1346 case DemuxerStream::NUM_TYPES
:
1354 bool ChunkDemuxer::OnTextBuffers(
1355 TextTrack
* text_track
,
1356 const StreamParser::BufferQueue
& buffers
) {
1357 lock_
.AssertAcquired();
1358 DCHECK_NE(state_
, SHUTDOWN
);
1360 // TODO(matthewjheaney): IncreaseDurationIfNecessary
1362 for (StreamParser::BufferQueue::const_iterator itr
= buffers
.begin();
1363 itr
!= buffers
.end(); ++itr
) {
1364 const StreamParserBuffer
* const buffer
= itr
->get();
1365 const TimeDelta start
= buffer
->timestamp();
1366 const TimeDelta end
= start
+ buffer
->duration();
1368 std::string id
, settings
, content
;
1370 WebMWebVTTParser::Parse(buffer
->data(),
1371 buffer
->data_size(),
1372 &id
, &settings
, &content
);
1374 text_track
->addWebVTTCue(start
, end
, id
, content
, settings
);
1380 bool ChunkDemuxer::IsValidId(const std::string
& source_id
) const {
1381 lock_
.AssertAcquired();
1382 return source_state_map_
.count(source_id
) > 0u;
1385 void ChunkDemuxer::UpdateDuration(TimeDelta new_duration
) {
1386 DCHECK(duration_
!= new_duration
);
1387 user_specified_duration_
= -1;
1388 duration_
= new_duration
;
1389 host_
->SetDuration(new_duration
);
1392 void ChunkDemuxer::IncreaseDurationIfNecessary(
1393 TimeDelta last_appended_buffer_timestamp
,
1394 ChunkDemuxerStream
* stream
) {
1395 DCHECK(last_appended_buffer_timestamp
!= kNoTimestamp());
1396 if (last_appended_buffer_timestamp
<= duration_
)
1399 Ranges
<TimeDelta
> ranges
= stream
->GetBufferedRanges(kInfiniteDuration());
1400 DCHECK_GT(ranges
.size(), 0u);
1402 TimeDelta last_timestamp_buffered
= ranges
.end(ranges
.size() - 1);
1403 if (last_timestamp_buffered
> duration_
)
1404 UpdateDuration(last_timestamp_buffered
);
1407 void ChunkDemuxer::DecreaseDurationIfNecessary() {
1408 Ranges
<TimeDelta
> ranges
= GetBufferedRanges();
1409 if (ranges
.size() == 0u)
1412 TimeDelta last_timestamp_buffered
= ranges
.end(ranges
.size() - 1);
1413 if (last_timestamp_buffered
< duration_
)
1414 UpdateDuration(last_timestamp_buffered
);
1417 Ranges
<TimeDelta
> ChunkDemuxer::GetBufferedRanges() const {
1418 if (audio_
&& !video_
)
1419 return audio_
->GetBufferedRanges(duration_
);
1420 else if (!audio_
&& video_
)
1421 return video_
->GetBufferedRanges(duration_
);
1422 return ComputeIntersection();
1425 void ChunkDemuxer::StartReturningData() {
1427 audio_
->StartReturningData();
1430 video_
->StartReturningData();
1433 void ChunkDemuxer::AbortPendingReads() {
1435 audio_
->AbortReads();
1438 video_
->AbortReads();
1441 void ChunkDemuxer::SeekAllSources(TimeDelta seek_time
) {
1443 audio_
->Seek(seek_time
);
1446 video_
->Seek(seek_time
);
1449 void ChunkDemuxer::CompletePendingReadsIfPossible() {
1451 audio_
->CompletePendingReadIfPossible();
1454 video_
->CompletePendingReadIfPossible();
1457 } // namespace media