| Index: media/filters/chunk_demuxer.cc
|
| diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc
|
| index 6505b2c131141c6aa32d2c229bb9061121a2d0e3..17f1d96c9046de426f653cddef6ff0565b403901 100644
|
| --- a/media/filters/chunk_demuxer.cc
|
| +++ b/media/filters/chunk_demuxer.cc
|
| @@ -5,7 +5,6 @@
|
| #include "media/filters/chunk_demuxer.h"
|
|
|
| #include <algorithm>
|
| -#include <deque>
|
| #include <limits>
|
| #include <list>
|
|
|
| @@ -18,6 +17,7 @@
|
| #include "media/base/bind_to_current_loop.h"
|
| #include "media/base/stream_parser_buffer.h"
|
| #include "media/base/video_decoder_config.h"
|
| +#include "media/filters/legacy_frame_processor.h"
|
| #include "media/filters/stream_parser_factory.h"
|
|
|
| using base::TimeDelta;
|
| @@ -86,17 +86,13 @@ class SourceState {
|
| typedef base::Callback<ChunkDemuxerStream*(
|
| DemuxerStream::Type)> CreateDemuxerStreamCB;
|
|
|
| - // Callback signature used to notify ChunkDemuxer of timestamps
|
| - // that may cause the duration to be updated.
|
| - typedef base::Callback<void(
|
| - TimeDelta, ChunkDemuxerStream*)> IncreaseDurationCB;
|
| -
|
| typedef base::Callback<void(
|
| ChunkDemuxerStream*, const TextTrackConfig&)> NewTextTrackCB;
|
|
|
| - SourceState(scoped_ptr<StreamParser> stream_parser, const LogCB& log_cb,
|
| - const CreateDemuxerStreamCB& create_demuxer_stream_cb,
|
| - const IncreaseDurationCB& increase_duration_cb);
|
| + SourceState(
|
| + scoped_ptr<StreamParser> stream_parser,
|
| + scoped_ptr<FrameProcessorBase> frame_processor, const LogCB& log_cb,
|
| + const CreateDemuxerStreamCB& create_demuxer_stream_cb);
|
|
|
| ~SourceState();
|
|
|
| @@ -127,7 +123,7 @@ class SourceState {
|
| // Returns true if currently parsing a media segment, or false otherwise.
|
| bool parsing_media_segment() const { return parsing_media_segment_; }
|
|
|
| - // Sets |sequence_mode_| to |sequence_mode|.
|
| + // Sets |frame_processor_|'s sequence mode to |sequence_mode|.
|
| void SetSequenceMode(bool sequence_mode);
|
|
|
| // Returns the range of buffered data in this source, capped at |duration|.
|
| @@ -171,52 +167,17 @@ class SourceState {
|
| // Called by the |stream_parser_| at the end of a media segment.
|
| void OnEndOfMediaSegment();
|
|
|
| - // Called by the |stream_parser_| when new buffers have been parsed. It
|
| - // applies |*timestamp_offset_during_append_| to all buffers in
|
| - // |audio_buffers|, |video_buffers| and |text_map|, filters buffers against
|
| - // |append_window_[start,end]_during_append_| and then calls Append()
|
| - // with the surviving modified buffers on |audio_|, |video_| and/or the text
|
| - // demuxer streams associated with the track numbers in |text_map|.
|
| + // Called by the |stream_parser_| when new buffers have been parsed.
|
| + // It processes the new buffers using |frame_processor_|, which includes
|
| + // appending the processed frames to associated demuxer streams for each
|
| + // frame's track.
|
| // Returns true on a successful call. Returns false if an error occurred while
|
| // processing the buffers.
|
| bool OnNewBuffers(const StreamParser::BufferQueue& audio_buffers,
|
| const StreamParser::BufferQueue& video_buffers,
|
| const StreamParser::TextBufferQueueMap& text_map);
|
|
|
| - // Helper function for OnNewBuffers() when new text buffers have been parsed.
|
| - // It applies |*timestamp_offset_during_append_| to all buffers in |buffers|,
|
| - // filters the buffers against |append_window_[start,end]_during_append_| and
|
| - // then appends the (modified) buffers to the demuxer stream associated with
|
| - // the track having |text_track_id|.
|
| - // Returns true on a successful call. Returns false if an error occurred while
|
| - // processing the buffers.
|
| - bool OnTextBuffers(StreamParser::TrackId text_track_id,
|
| - const StreamParser::BufferQueue& buffers);
|
| -
|
| - // Helper function that appends |buffers| to |stream| and calls
|
| - // |increase_duration_cb_| to potentially update the duration.
|
| - // Returns true if the append was successful. Returns false if
|
| - // |stream| is NULL or something in |buffers| caused the append to fail.
|
| - bool AppendAndUpdateDuration(ChunkDemuxerStream* stream,
|
| - const StreamParser::BufferQueue& buffers);
|
| -
|
| - // Helper function that adds |*timestamp_offset_during_append_| to each buffer
|
| - // in |buffers|.
|
| - void AdjustBufferTimestamps(const StreamParser::BufferQueue& buffers);
|
| -
|
| - // Filters out buffers that are outside of the append window
|
| - // [|append_window_start_during_append_|, |append_window_end_during_append_|).
|
| - // |needs_keyframe| is a pointer to the |xxx_need_keyframe_| flag
|
| - // associated with the |buffers|. Its state is read an updated as
|
| - // this method filters |buffers|.
|
| - // Buffers that are inside the append window are appended to the end
|
| - // of |filtered_buffers|.
|
| - void FilterWithAppendWindow(const StreamParser::BufferQueue& buffers,
|
| - bool* needs_keyframe,
|
| - StreamParser::BufferQueue* filtered_buffers);
|
| -
|
| CreateDemuxerStreamCB create_demuxer_stream_cb_;
|
| - IncreaseDurationCB increase_duration_cb_;
|
| NewTextTrackCB new_text_track_cb_;
|
|
|
| // During Append(), if OnNewBuffers() coded frame processing updates the
|
| @@ -231,13 +192,6 @@ class SourceState {
|
| TimeDelta append_window_start_during_append_;
|
| TimeDelta append_window_end_during_append_;
|
|
|
| - // Tracks the mode by which appended media is processed. If true, then
|
| - // appended media is processed using "sequence" mode. Otherwise, appended
|
| - // media is processed using "segments" mode.
|
| - // TODO(wolenetz): Enable "sequence" mode logic. See http://crbug.com/249422
|
| - // and http://crbug.com/333437.
|
| - bool sequence_mode_;
|
| -
|
| // Set to true if the next buffers appended within the append window
|
| // represent the start of a new media segment. This flag being set
|
| // triggers a call to |new_segment_cb_| when the new buffers are
|
| @@ -255,133 +209,33 @@ class SourceState {
|
| scoped_ptr<StreamParser> stream_parser_;
|
|
|
| ChunkDemuxerStream* audio_; // Not owned by |this|.
|
| - bool audio_needs_keyframe_;
|
| -
|
| ChunkDemuxerStream* video_; // Not owned by |this|.
|
| - bool video_needs_keyframe_;
|
|
|
| typedef std::map<StreamParser::TrackId, ChunkDemuxerStream*> TextStreamMap;
|
| TextStreamMap text_stream_map_; // |this| owns the map's stream pointers.
|
|
|
| + scoped_ptr<FrameProcessorBase> frame_processor_;
|
| LogCB log_cb_;
|
|
|
| DISALLOW_COPY_AND_ASSIGN(SourceState);
|
| };
|
|
|
| -class ChunkDemuxerStream : public DemuxerStream {
|
| - public:
|
| - typedef std::deque<scoped_refptr<StreamParserBuffer> > BufferQueue;
|
| -
|
| - explicit ChunkDemuxerStream(Type type);
|
| - virtual ~ChunkDemuxerStream();
|
| -
|
| - // ChunkDemuxerStream control methods.
|
| - void StartReturningData();
|
| - void AbortReads();
|
| - void CompletePendingReadIfPossible();
|
| - void Shutdown();
|
| -
|
| - // SourceBufferStream manipulation methods.
|
| - void Seek(TimeDelta time);
|
| - bool IsSeekWaitingForData() const;
|
| -
|
| - // Add buffers to this stream. Buffers are stored in SourceBufferStreams,
|
| - // which handle ordering and overlap resolution.
|
| - // Returns true if buffers were successfully added.
|
| - bool Append(const StreamParser::BufferQueue& buffers);
|
| -
|
| - // Removes buffers between |start| and |end| according to the steps
|
| - // in the "Coded Frame Removal Algorithm" in the Media Source
|
| - // Extensions Spec.
|
| - // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#sourcebuffer-coded-frame-removal
|
| - //
|
| - // |duration| is the current duration of the presentation. It is
|
| - // required by the computation outlined in the spec.
|
| - void Remove(TimeDelta start, TimeDelta end, TimeDelta duration);
|
| -
|
| - // Signal to the stream that duration has changed to |duration|.
|
| - void OnSetDuration(TimeDelta duration);
|
| -
|
| - // Returns the range of buffered data in this stream, capped at |duration|.
|
| - Ranges<TimeDelta> GetBufferedRanges(TimeDelta duration) const;
|
| -
|
| - // Returns the duration of the buffered data.
|
| - // Returns TimeDelta() if the stream has no buffered data.
|
| - TimeDelta GetBufferedDuration() const;
|
| -
|
| - // Signal to the stream that buffers handed in through subsequent calls to
|
| - // Append() belong to a media segment that starts at |start_timestamp|.
|
| - void OnNewMediaSegment(TimeDelta start_timestamp);
|
| -
|
| - // Called when midstream config updates occur.
|
| - // Returns true if the new config is accepted.
|
| - // Returns false if the new config should trigger an error.
|
| - bool UpdateAudioConfig(const AudioDecoderConfig& config, const LogCB& log_cb);
|
| - bool UpdateVideoConfig(const VideoDecoderConfig& config, const LogCB& log_cb);
|
| - void UpdateTextConfig(const TextTrackConfig& config, const LogCB& log_cb);
|
| -
|
| - void MarkEndOfStream();
|
| - void UnmarkEndOfStream();
|
| -
|
| - // DemuxerStream methods.
|
| - virtual void Read(const ReadCB& read_cb) OVERRIDE;
|
| - virtual Type type() OVERRIDE;
|
| - virtual void EnableBitstreamConverter() OVERRIDE;
|
| - virtual AudioDecoderConfig audio_decoder_config() OVERRIDE;
|
| - virtual VideoDecoderConfig video_decoder_config() OVERRIDE;
|
| -
|
| - // Returns the text track configuration. It is an error to call this method
|
| - // if type() != TEXT.
|
| - TextTrackConfig text_track_config();
|
| -
|
| - // Sets the memory limit, in bytes, on the SourceBufferStream.
|
| - void set_memory_limit_for_testing(int memory_limit) {
|
| - stream_->set_memory_limit_for_testing(memory_limit);
|
| - }
|
| -
|
| - private:
|
| - enum State {
|
| - UNINITIALIZED,
|
| - RETURNING_DATA_FOR_READS,
|
| - RETURNING_ABORT_FOR_READS,
|
| - SHUTDOWN,
|
| - };
|
| -
|
| - // Assigns |state_| to |state|
|
| - void ChangeState_Locked(State state);
|
| -
|
| - void CompletePendingReadIfPossible_Locked();
|
| -
|
| - // Specifies the type of the stream.
|
| - Type type_;
|
| -
|
| - scoped_ptr<SourceBufferStream> stream_;
|
| -
|
| - mutable base::Lock lock_;
|
| - State state_;
|
| - ReadCB read_cb_;
|
| -
|
| - DISALLOW_IMPLICIT_CONSTRUCTORS(ChunkDemuxerStream);
|
| -};
|
| -
|
| -SourceState::SourceState(scoped_ptr<StreamParser> stream_parser,
|
| - const LogCB& log_cb,
|
| - const CreateDemuxerStreamCB& create_demuxer_stream_cb,
|
| - const IncreaseDurationCB& increase_duration_cb)
|
| +SourceState::SourceState(
|
| + scoped_ptr<StreamParser> stream_parser,
|
| + scoped_ptr<FrameProcessorBase> frame_processor,
|
| + const LogCB& log_cb,
|
| + const CreateDemuxerStreamCB& create_demuxer_stream_cb)
|
| : create_demuxer_stream_cb_(create_demuxer_stream_cb),
|
| - increase_duration_cb_(increase_duration_cb),
|
| timestamp_offset_during_append_(NULL),
|
| - sequence_mode_(false),
|
| new_media_segment_(false),
|
| parsing_media_segment_(false),
|
| stream_parser_(stream_parser.release()),
|
| audio_(NULL),
|
| - audio_needs_keyframe_(true),
|
| video_(NULL),
|
| - video_needs_keyframe_(true),
|
| + frame_processor_(frame_processor.release()),
|
| log_cb_(log_cb) {
|
| DCHECK(!create_demuxer_stream_cb_.is_null());
|
| - DCHECK(!increase_duration_cb_.is_null());
|
| + DCHECK(frame_processor_);
|
| }
|
|
|
| SourceState::~SourceState() {
|
| @@ -416,7 +270,7 @@ void SourceState::Init(const StreamParser::InitCB& init_cb,
|
| void SourceState::SetSequenceMode(bool sequence_mode) {
|
| DCHECK(!parsing_media_segment_);
|
|
|
| - sequence_mode_ = sequence_mode;
|
| + frame_processor_->SetSequenceMode(sequence_mode);
|
| }
|
|
|
| bool SourceState::Append(const uint8* data, size_t length,
|
| @@ -438,8 +292,7 @@ bool SourceState::Append(const uint8* data, size_t length,
|
|
|
| void SourceState::Abort() {
|
| stream_parser_->Flush();
|
| - audio_needs_keyframe_ = true;
|
| - video_needs_keyframe_ = true;
|
| + frame_processor_->Reset();
|
| parsing_media_segment_ = false;
|
| }
|
|
|
| @@ -626,20 +479,6 @@ bool SourceState::IsSeekWaitingForData() const {
|
| return false;
|
| }
|
|
|
| -void SourceState::AdjustBufferTimestamps(
|
| - const StreamParser::BufferQueue& buffers) {
|
| - TimeDelta timestamp_offset = *timestamp_offset_during_append_;
|
| - if (timestamp_offset == TimeDelta())
|
| - return;
|
| -
|
| - for (StreamParser::BufferQueue::const_iterator itr = buffers.begin();
|
| - itr != buffers.end(); ++itr) {
|
| - (*itr)->SetDecodeTimestamp(
|
| - (*itr)->GetDecodeTimestamp() + timestamp_offset);
|
| - (*itr)->set_timestamp((*itr)->timestamp() + timestamp_offset);
|
| - }
|
| -}
|
| -
|
| bool SourceState::OnNewConfigs(
|
| bool allow_audio, bool allow_video,
|
| const AudioDecoderConfig& audio_config,
|
| @@ -685,6 +524,12 @@ bool SourceState::OnNewConfigs(
|
| DVLOG(1) << "Failed to create an audio stream.";
|
| return false;
|
| }
|
| +
|
| + if (!frame_processor_->AddTrack(FrameProcessorBase::kAudioTrackId,
|
| + audio_)) {
|
| + DVLOG(1) << "Failed to add audio track to frame processor.";
|
| + return false;
|
| + }
|
| }
|
|
|
| success &= audio_->UpdateAudioConfig(audio_config, log_cb_);
|
| @@ -698,6 +543,12 @@ bool SourceState::OnNewConfigs(
|
| DVLOG(1) << "Failed to create a video stream.";
|
| return false;
|
| }
|
| +
|
| + if (!frame_processor_->AddTrack(FrameProcessorBase::kVideoTrackId,
|
| + video_)) {
|
| + DVLOG(1) << "Failed to add video track to frame processor.";
|
| + return false;
|
| + }
|
| }
|
|
|
| success &= video_->UpdateVideoConfig(video_config, log_cb_);
|
| @@ -709,6 +560,12 @@ bool SourceState::OnNewConfigs(
|
| itr != text_configs.end(); ++itr) {
|
| ChunkDemuxerStream* const text_stream =
|
| create_demuxer_stream_cb_.Run(DemuxerStream::TEXT);
|
| + if (!frame_processor_->AddTrack(itr->first, text_stream)) {
|
| + success &= false;
|
| + MEDIA_LOG(log_cb_) << "Failed to add text track ID " << itr->first
|
| + << " to frame processor.";
|
| + break;
|
| + }
|
| text_stream->UpdateTextConfig(itr->second, log_cb_);
|
| text_stream_map_[itr->first] = text_stream;
|
| new_text_track_cb_.Run(text_stream, itr->second);
|
| @@ -780,167 +637,11 @@ bool SourceState::OnNewBuffers(
|
| const StreamParser::TextBufferQueueMap& text_map) {
|
| DVLOG(2) << "OnNewBuffers()";
|
| DCHECK(timestamp_offset_during_append_);
|
| - DCHECK(!audio_buffers.empty() || !video_buffers.empty() ||
|
| - !text_map.empty());
|
|
|
| - // TODO(wolenetz): DCHECK + return false if any of these buffers have UNKNOWN
|
| - // type() in upcoming coded frame processing compliant implementation. See
|
| - // http://crbug.com/249422.
|
| -
|
| - AdjustBufferTimestamps(audio_buffers);
|
| - AdjustBufferTimestamps(video_buffers);
|
| -
|
| - StreamParser::BufferQueue filtered_audio;
|
| - StreamParser::BufferQueue filtered_video;
|
| -
|
| - FilterWithAppendWindow(audio_buffers, &audio_needs_keyframe_,
|
| - &filtered_audio);
|
| -
|
| - FilterWithAppendWindow(video_buffers, &video_needs_keyframe_,
|
| - &filtered_video);
|
| -
|
| - if ((!filtered_audio.empty() || !filtered_video.empty()) &&
|
| - new_media_segment_) {
|
| - // Find the earliest timestamp in the filtered buffers and use that for the
|
| - // segment start timestamp.
|
| - TimeDelta segment_timestamp = kNoTimestamp();
|
| -
|
| - if (!filtered_audio.empty())
|
| - segment_timestamp = filtered_audio.front()->GetDecodeTimestamp();
|
| -
|
| - if (!filtered_video.empty() &&
|
| - (segment_timestamp == kNoTimestamp() ||
|
| - filtered_video.front()->GetDecodeTimestamp() < segment_timestamp)) {
|
| - segment_timestamp = filtered_video.front()->GetDecodeTimestamp();
|
| - }
|
| -
|
| - new_media_segment_ = false;
|
| -
|
| - if (audio_)
|
| - audio_->OnNewMediaSegment(segment_timestamp);
|
| -
|
| - if (video_)
|
| - video_->OnNewMediaSegment(segment_timestamp);
|
| -
|
| - for (TextStreamMap::iterator itr = text_stream_map_.begin();
|
| - itr != text_stream_map_.end(); ++itr) {
|
| - itr->second->OnNewMediaSegment(segment_timestamp);
|
| - }
|
| - }
|
| -
|
| - if (!filtered_audio.empty() &&
|
| - !AppendAndUpdateDuration(audio_, filtered_audio)) {
|
| - return false;
|
| - }
|
| -
|
| - if (!filtered_video.empty() &&
|
| - !AppendAndUpdateDuration(video_, filtered_video)) {
|
| - return false;
|
| - }
|
| -
|
| - if (text_map.empty())
|
| - return true;
|
| -
|
| - // Process any buffers for each of the text tracks in the map.
|
| - bool all_text_buffers_empty = true;
|
| - for (StreamParser::TextBufferQueueMap::const_iterator itr = text_map.begin();
|
| - itr != text_map.end();
|
| - ++itr) {
|
| - const StreamParser::BufferQueue text_buffers = itr->second;
|
| - if (!text_buffers.empty()) {
|
| - all_text_buffers_empty = false;
|
| - if (!OnTextBuffers(itr->first, text_buffers))
|
| - return false;
|
| - }
|
| - }
|
| -
|
| - DCHECK(!all_text_buffers_empty);
|
| - return true;
|
| -}
|
| -
|
| -bool SourceState::OnTextBuffers(
|
| - StreamParser::TrackId text_track_id,
|
| - const StreamParser::BufferQueue& buffers) {
|
| - DCHECK(!buffers.empty());
|
| -
|
| - TextStreamMap::iterator itr = text_stream_map_.find(text_track_id);
|
| - if (itr == text_stream_map_.end())
|
| - return false;
|
| -
|
| - AdjustBufferTimestamps(buffers);
|
| -
|
| - StreamParser::BufferQueue filtered_buffers;
|
| - bool needs_keyframe = false;
|
| - FilterWithAppendWindow(buffers, &needs_keyframe, &filtered_buffers);
|
| -
|
| - if (filtered_buffers.empty())
|
| - return true;
|
| -
|
| - return AppendAndUpdateDuration(itr->second, filtered_buffers);
|
| -}
|
| -
|
| -bool SourceState::AppendAndUpdateDuration(
|
| - ChunkDemuxerStream* stream,
|
| - const StreamParser::BufferQueue& buffers) {
|
| - DCHECK(!buffers.empty());
|
| -
|
| - if (!stream || !stream->Append(buffers))
|
| - return false;
|
| -
|
| - increase_duration_cb_.Run(buffers.back()->timestamp(), stream);
|
| - return true;
|
| -}
|
| -
|
| -void SourceState::FilterWithAppendWindow(
|
| - const StreamParser::BufferQueue& buffers, bool* needs_keyframe,
|
| - StreamParser::BufferQueue* filtered_buffers) {
|
| - DCHECK(needs_keyframe);
|
| - DCHECK(filtered_buffers);
|
| -
|
| - // This loop implements steps 1.9, 1.10, & 1.11 of the "Coded frame
|
| - // processing loop" in the Media Source Extensions spec.
|
| - // These steps filter out buffers that are not within the "append
|
| - // window" and handles resyncing on the next random access point
|
| - // (i.e., next keyframe) if a buffer gets dropped.
|
| - for (StreamParser::BufferQueue::const_iterator itr = buffers.begin();
|
| - itr != buffers.end(); ++itr) {
|
| - // Filter out buffers that are outside the append window. Anytime
|
| - // a buffer gets dropped we need to set |*needs_keyframe| to true
|
| - // because we can only resume decoding at keyframes.
|
| - TimeDelta presentation_timestamp = (*itr)->timestamp();
|
| -
|
| - // TODO(acolwell): Change |frame_end_timestamp| value to
|
| - // |presentation_timestamp + (*itr)->duration()|, like the spec
|
| - // requires, once frame durations are actually present in all buffers.
|
| - TimeDelta frame_end_timestamp = presentation_timestamp;
|
| - if (presentation_timestamp < append_window_start_during_append_ ||
|
| - frame_end_timestamp > append_window_end_during_append_) {
|
| - DVLOG(1) << "Dropping buffer outside append window."
|
| - << " presentation_timestamp "
|
| - << presentation_timestamp.InSecondsF();
|
| - *needs_keyframe = true;
|
| -
|
| - // This triggers a discontinuity so we need to treat the next frames
|
| - // appended within the append window as if they were the beginning of a
|
| - // new segment.
|
| - new_media_segment_ = true;
|
| - continue;
|
| - }
|
| -
|
| - // If |*needs_keyframe| is true then filter out buffers until we
|
| - // encounter the next keyframe.
|
| - if (*needs_keyframe) {
|
| - if (!(*itr)->IsKeyframe()) {
|
| - DVLOG(1) << "Dropping non-keyframe. presentation_timestamp "
|
| - << presentation_timestamp.InSecondsF();
|
| - continue;
|
| - }
|
| -
|
| - *needs_keyframe = false;
|
| - }
|
| -
|
| - filtered_buffers->push_back(*itr);
|
| - }
|
| + return frame_processor_->ProcessFrames(
|
| + audio_buffers, video_buffers, text_map,
|
| + append_window_start_during_append_, append_window_end_during_append_,
|
| + &new_media_segment_, timestamp_offset_during_append_);
|
| }
|
|
|
| ChunkDemuxerStream::ChunkDemuxerStream(Type type)
|
| @@ -1368,11 +1069,14 @@ ChunkDemuxer::Status ChunkDemuxer::AddId(const std::string& id,
|
| if (has_video)
|
| source_id_video_ = id;
|
|
|
| + scoped_ptr<FrameProcessorBase> frame_processor(new LegacyFrameProcessor(
|
| + base::Bind(&ChunkDemuxer::IncreaseDurationIfNecessary,
|
| + base::Unretained(this))));
|
| +
|
| scoped_ptr<SourceState> source_state(
|
| - new SourceState(stream_parser.Pass(), log_cb_,
|
| + new SourceState(stream_parser.Pass(),
|
| + frame_processor.Pass(), log_cb_,
|
| base::Bind(&ChunkDemuxer::CreateDemuxerStream,
|
| - base::Unretained(this)),
|
| - base::Bind(&ChunkDemuxer::IncreaseDurationIfNecessary,
|
| base::Unretained(this))));
|
|
|
| SourceState::NewTextTrackCB new_text_track_cb;
|
|
|