Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(883)

Unified Diff: media/filters/adaptive_demuxer.cc

Issue 7044008: Initial implementation of stream switching in AdaptiveDemuxer. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Addressed CR comments Created 9 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « media/filters/adaptive_demuxer.h ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: media/filters/adaptive_demuxer.cc
diff --git a/media/filters/adaptive_demuxer.cc b/media/filters/adaptive_demuxer.cc
index 5599b15c3b0eb9b7d61d28f1c40d9b6a8f71cc85..56dd3c9eefe227a89dadf3c81cd92747ef6044e3 100644
--- a/media/filters/adaptive_demuxer.cc
+++ b/media/filters/adaptive_demuxer.cc
@@ -4,20 +4,172 @@
#include "base/bind.h"
#include "base/logging.h"
+#include "base/message_loop.h"
#include "base/string_number_conversions.h"
#include "base/string_split.h"
#include "base/string_util.h"
+#include "media/ffmpeg/ffmpeg_common.h"
#include "media/filters/adaptive_demuxer.h"
namespace media {
+static const int64 kSwitchTimerPeriod = 5000; // In milliseconds.
+
+// Object that decides when to switch streams.
+class StreamSwitchManager
+ : public base::RefCountedThreadSafe<StreamSwitchManager> {
+ public:
+ StreamSwitchManager();
+ virtual ~StreamSwitchManager();
+
+ void Init(AdaptiveDemuxer* demuxer);
+
+ // Playback events. These methods are called when playback starts, pauses, or
+ // stops.
+ void Play();
+ void Pause();
+ void Stop();
+
+ private:
+ // Method called periodically to determine if we should switch
+ // streams.
+ void OnSwitchTimer();
+
+ // Called when the demuxer completes a stream switch.
+ void OnSwitchDone(PipelineStatus status);
+
+ // Helper method that schedules OnSwitchTimer() to be called.
+ void StartSwitchTimer();
+
+ // The demuxer that owns this object.
+ AdaptiveDemuxer* demuxer_;
+
+ // Message loop this object runs on.
+ MessageLoop* message_loop_;
+
+ // Is clip playing or not?
+ bool playing_;
+
+ // Is a stream switch in progress?
+ bool switch_pending_;
+
+ // Has an OnSwitchTimer() call been scheduled?
+ bool switch_timer_running_;
+
+ // The stream IDs for the video streams.
+ AdaptiveDemuxer::StreamIdVector video_ids_;
+
+ // An index into |video_ids_| for the current video stream.
+ int current_id_index_;
+
+ DISALLOW_COPY_AND_ASSIGN(StreamSwitchManager);
+};
+
+StreamSwitchManager::StreamSwitchManager()
+ : demuxer_(NULL),
+ playing_(false),
+ switch_pending_(false),
+ switch_timer_running_(false),
+ current_id_index_(-1) {
+}
+
+StreamSwitchManager::~StreamSwitchManager() {}
+
+void StreamSwitchManager::Init(AdaptiveDemuxer* demuxer) {
+ DCHECK(demuxer);
+ message_loop_ = MessageLoop::current();
+ demuxer_ = demuxer;
+ video_ids_ = demuxer->GetVideoIds();
+ current_id_index_ = -1;
+
+ if (video_ids_.size() > 0) {
+ // Find the index in video_ids_ for the current video ID.
+ int current_id = demuxer->GetCurrentVideoId();
+ current_id_index_ = 0;
+ for (int i = 0; i < video_ids_.size(); i++) {
+ if (current_id == video_ids_[i]) {
+ current_id_index_ = i;
+ break;
+ }
+ }
+ }
+}
+
+void StreamSwitchManager::Play() {
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
+ DCHECK(!playing_);
+ playing_ = true;
+
+ if (video_ids_.size() > 1 && !switch_timer_running_) {
+ StartSwitchTimer();
+ }
+}
+
+void StreamSwitchManager::Pause() {
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
+ DCHECK(playing_);
+ playing_ = false;
+}
+
+void StreamSwitchManager::Stop() {
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
+ DCHECK(!playing_);
+ demuxer_ = NULL;
+}
+
+void StreamSwitchManager::OnSwitchTimer() {
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
+
+ switch_timer_running_ = false;
+
+ if (!playing_)
+ return;
+
+ if (!switch_pending_) {
+ // Select the stream to switch to. For now we are just rotating
+ // through the available streams.
+ int new_id_index = (current_id_index_ + 1) % video_ids_.size();
+
+ // Did we select a new stream?
+ if (new_id_index != current_id_index_) {
+ current_id_index_ = new_id_index;
+ switch_pending_ = true;
+ demuxer_->ChangeVideoStream(video_ids_[new_id_index],
+ base::Bind(&StreamSwitchManager::OnSwitchDone,
+ this));
+ }
+ }
+
+ StartSwitchTimer();
+}
+
+void StreamSwitchManager::OnSwitchDone(PipelineStatus status) {
+ if (MessageLoop::current() != message_loop_) {
scherkus (not reviewing) 2011/05/23 23:17:06 nothing actionable right now but some food for tho
acolwell GONE FROM CHROMIUM 2011/05/23 23:46:40 I was going to comment on this when I made the cha
+ message_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this,&StreamSwitchManager::OnSwitchDone, status));
+ }
+ switch_pending_ = false;
+}
+
+void StreamSwitchManager::StartSwitchTimer() {
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
+ switch_timer_running_ = true;
+ message_loop_->PostDelayedTask(
+ FROM_HERE, NewRunnableMethod(this, &StreamSwitchManager::OnSwitchTimer),
+ kSwitchTimerPeriod);
+}
+
//
// AdaptiveDemuxerStream
//
AdaptiveDemuxerStream::AdaptiveDemuxerStream(
StreamVector const& streams, int initial_stream)
: streams_(streams), current_stream_index_(initial_stream),
- bitstream_converter_enabled_(false) {
+ bitstream_converter_enabled_(false),
+ pending_reads_(0),
+ switch_index_(-1),
+ last_buffer_timestamp_(kNoTimestamp) {
DCheckSanity();
}
@@ -57,7 +209,26 @@ DemuxerStream* AdaptiveDemuxerStream::current_stream() {
}
void AdaptiveDemuxerStream::Read(const ReadCallback& read_callback) {
- current_stream()->Read(read_callback);
+ DemuxerStream* stream = NULL;
+
+ {
+ base::AutoLock auto_lock(lock_);
+
+ read_cb_queue_.push_back(read_callback);
+
+ // Check to make sure we aren't doing a stream switch. We only want to
+ // make calls on |streams_[current_stream_index_]| when we aren't
+ // in the middle of a stream switch. Since the callback is stored in
+ // |read_cb_queue_| we will issue the Read() on the new stream once
+ // the switch has completed.
+ if (!IsSwitchPending_Locked()) {
+ stream = streams_[current_stream_index_];
+ pending_reads_++;
+ }
+ }
+
+ if (stream)
+ stream->Read(base::Bind(&AdaptiveDemuxerStream::OnReadDone, this));
}
AVStream* AdaptiveDemuxerStream::GetAVStream() {
@@ -80,16 +251,220 @@ void AdaptiveDemuxerStream::EnableBitstreamConverter() {
current_stream()->EnableBitstreamConverter();
}
-void AdaptiveDemuxerStream::ChangeCurrentStream(int index) {
- bool needs_bitstream_converter_enabled;
+void AdaptiveDemuxerStream::OnAdaptiveDemuxerSeek() {
+ base::AutoLock auto_lock(lock_);
+
+ last_buffer_timestamp_ = kNoTimestamp;
+
+ // TODO Figure out what to do if this happens during a stream switch.
scherkus (not reviewing) 2011/05/23 23:17:06 TODO -> TODO(acolwell):
acolwell GONE FROM CHROMIUM 2011/05/23 23:46:40 Done.
+}
+
+// This method initiates a stream switch. The diagram below shows the steps
+// involved.
+//
+// +-----------------------+
+// ChangeCurrentStream() -> | Store stream switch |
+// | index, seek_function, |
+// | and switch_cb. |
+// +-----------------------+
+// |
+// \|/
+// +-------------------+ Yes
+// | Are there pending | -----> (Wait for OnReadDone())
+// | Read()s on the | |
+// | current stream? | <-----+ \|/
+// +-------------------+ +--- OnReadDone()
+// | No
+// \|/
+// StartSwitch()
+// |
+// \|/
+// +------------------------+ No
+// | Have buffer timestamp? |-----+
+// +------------------------+ |
+// | Yes |
+// \|/ |
+// +-----------------------------+ |
+// | Seek stream to timestamp | |
+// | using switch_seek_function_ | |
+// +-----------------------------+ |
+// | |
+// \|/ |
+// OnSwitchSeekDone() <-----+
+// |
+// \|/
+// +------------------+ No
+// | Seek successful? |----------+
+// +------------------+ |
+// | Yes |
+// \|/ |
+// +------------------------------+ |
+// | Update current_stream_index_ | |
+// +------------------------------+ |
+// | |
+// \|/ |
+// +---------------------------+ |
+// | Call Read() on new stream |<---+
+// | for deferred reads. |
+// +---------------------------+
+// |
+// \|/
+// switch_cb(OK | ERROR)
+//
+// NOTE: Any AdaptiveDemuxerStream::Read() calls that occur during the stream
+// switch will be deferred until the switch has completed. The callbacks
+// will be queued on |read_cb_queue_|, but no Read() will be issued on the
+// current stream.
+void AdaptiveDemuxerStream::ChangeCurrentStream(
+ int index,
+ const SeekFunction& seek_function,
+ const PipelineStatusCB& switch_cb) {
+ DCHECK_GE(index, 0);
+
+ PipelineStatusCB error_cb;
+ PipelineStatus error_status = PIPELINE_ERROR_INVALID_STATE;
+
+ bool start_switch = false;
+
+ {
+ base::AutoLock auto_lock(lock_);
+
+ DCHECK_LE(index, streams_.size());
+ DCHECK(streams_[index].get());
+ DCHECK(!IsSwitchPending_Locked());
+
+ // TODO - Still need to handle the case where the stream has ended.
+ if (index == current_stream_index_) {
+ error_cb = switch_cb;
+ error_status = PIPELINE_OK;
+ } else {
+ switch_cb_ = switch_cb;
+ switch_index_ = index;
+ switch_seek_function_ = seek_function;
+
+ start_switch = CanStartSwitch_Locked();
+ }
+ }
+
+ if (!error_cb.is_null()) {
+ error_cb.Run(error_status);
+ return;
+ }
+
+ if (start_switch)
+ StartSwitch();
+}
+
+void AdaptiveDemuxerStream::OnReadDone(Buffer* buffer) {
+ ReadCallback read_cb;
+ bool start_switch = false;
+
+ {
+ base::AutoLock auto_lock(lock_);
+
+ pending_reads_--;
+
+ DCHECK_GE(pending_reads_, 0);
+ DCHECK_GE(read_cb_queue_.size(), 0);
+
+ read_cb = read_cb_queue_.front();
+ read_cb_queue_.pop_front();
+
+ if (buffer && !buffer->IsEndOfStream())
+ last_buffer_timestamp_ = buffer->GetTimestamp();
+
+ start_switch = IsSwitchPending_Locked() && CanStartSwitch_Locked();
+ }
+
+ if (!read_cb.is_null())
+ read_cb.Run(buffer);
+
+ if (start_switch)
+ StartSwitch();
+}
+
+bool AdaptiveDemuxerStream::IsSwitchPending_Locked() const {
+ lock_.AssertAcquired();
+ return !switch_cb_.is_null();
+}
+
+bool AdaptiveDemuxerStream::CanStartSwitch_Locked() const {
+ lock_.AssertAcquired();
+ return (pending_reads_ == 0);
+}
+
+void AdaptiveDemuxerStream::StartSwitch() {
+ SeekFunction seek_function;
+ base::TimeDelta seek_point;
+
+ {
+ base::AutoLock auto_lock(lock_);
+ DCHECK(IsSwitchPending_Locked());
+ DCHECK_EQ(pending_reads_, 0);
+ DCHECK_GE(switch_index_, 0);
+
+ seek_point = last_buffer_timestamp_;
+ seek_function = switch_seek_function_;
+
+ // TODO add code to call switch_cb_ if we are at the end of the stream now.
+ }
+
+ if (seek_point == kNoTimestamp) {
+ // We haven't seen a buffer so there is no need to seek. Just move on to
+ // the next stage in the switch process.
+ OnSwitchSeekDone(PIPELINE_OK, kNoTimestamp);
+ return;
+ }
+
+ DCHECK(!seek_function.is_null());
+ seek_function.Run(seek_point,
+ base::Bind(&AdaptiveDemuxerStream::OnSwitchSeekDone, this));
+}
+
+void AdaptiveDemuxerStream::OnSwitchSeekDone(PipelineStatus status,
+ base::TimeDelta seek_time) {
+ DemuxerStream* stream = NULL;
+ PipelineStatusCB switch_cb;
+ int reads_to_request = 0;
+ bool needs_bitstream_converter_enabled = false;
+
{
base::AutoLock auto_lock(lock_);
- current_stream_index_ = index;
- DCHECK(streams_[current_stream_index_]);
- needs_bitstream_converter_enabled = bitstream_converter_enabled_;
+
+ if (status == PIPELINE_OK) {
+ DCHECK(streams_[switch_index_]);
+
+ current_stream_index_ = switch_index_;
+ needs_bitstream_converter_enabled = bitstream_converter_enabled_;
+ }
+
+ // Clear stream switch state.
+ switch_cb = switch_cb_;
+ switch_cb_.Reset();
+ switch_index_ = -1;
+ switch_seek_function_.Reset();
+
+ // Get the number of outstanding Read()s on this object.
+ reads_to_request = read_cb_queue_.size();
+
+ DCHECK_EQ(pending_reads_, 0);
+ pending_reads_ = reads_to_request;
+
+ stream = streams_[current_stream_index_];
}
+
if (needs_bitstream_converter_enabled)
EnableBitstreamConverter();
+
+ if (stream) {
+ // Make the Read() calls that were deferred during the stream switch.
+ for(;reads_to_request > 0; --reads_to_request)
+ stream->Read(base::Bind(&AdaptiveDemuxerStream::OnReadDone, this));
+ }
+
+ // Signal that the stream switch has completed.
+ if (!switch_cb.is_null())
+ switch_cb.Run(status);
}
//
@@ -101,7 +476,10 @@ AdaptiveDemuxer::AdaptiveDemuxer(DemuxerVector const& demuxers,
int initial_video_demuxer_index)
: demuxers_(demuxers),
current_audio_demuxer_index_(initial_audio_demuxer_index),
- current_video_demuxer_index_(initial_video_demuxer_index) {
+ current_video_demuxer_index_(initial_video_demuxer_index),
+ playback_rate_(0),
+ switch_pending_(false),
+ stream_switch_manager_(new StreamSwitchManager()){
DCHECK(!demuxers_.empty());
DCHECK_GE(current_audio_demuxer_index_, -1);
DCHECK_GE(current_video_demuxer_index_, -1);
@@ -123,6 +501,8 @@ AdaptiveDemuxer::AdaptiveDemuxer(DemuxerVector const& demuxers,
video_streams, current_video_demuxer_index_);
}
+ stream_switch_manager_->Init(this);
+
// TODO(fischman): any streams in the underlying demuxers that aren't being
// consumed currently need to be sent to /dev/null or else FFmpegDemuxer will
// hold data for them in memory, waiting for them to get drained by a
@@ -131,17 +511,106 @@ AdaptiveDemuxer::AdaptiveDemuxer(DemuxerVector const& demuxers,
AdaptiveDemuxer::~AdaptiveDemuxer() {}
-void AdaptiveDemuxer::ChangeCurrentDemuxer(int audio_index, int video_index) {
+// Switches the current video stream. The diagram below describes the switch
+// process.
+// +-------------------------------+
+// ChangeVideoStream() ---> | video_index |
+// | == | Yes.
+// | current_video_demuxer_index_? |--> done_cb(OK)
+// +-------------------------------+
+// | No.
+// \|/
+// Call video_stream_->ChangeCurrentStream()
+// |
+// \|/
+// (Wait for ChangeVideoStreamDone())
+// |
+// \|/
+// ChangeVideoStreamDone()
+// |
+// \|/
+// +------------------------+ No
+// | Was switch successful? |---> done_cb(ERROR)
+// +------------------------+
+// |
+// \|/
+// Update current_video_demuxer_index_
+// & SetPlaybackRate() on new stream.
+// |
+// \|/
+// done_cb(OK).
+//
+void AdaptiveDemuxer::ChangeVideoStream(int video_index,
+ const PipelineStatusCB& done_cb) {
// TODO(fischman): this is currently broken because when a new Demuxer is to
// be used we need to set_host(host()) it, and we need to set_host(NULL) the
// current Demuxer if it's no longer being used.
+ AdaptiveDemuxerStream* stream = NULL;
+ bool switching_to_current_stream = false;
+ AdaptiveDemuxerStream::SeekFunction seek_function;
+
+ {
+ base::AutoLock auto_lock(lock_);
+
+ DCHECK(video_stream_);
+ DCHECK(!switch_pending_);
+ if (current_video_demuxer_index_ == video_index) {
+ switching_to_current_stream = true;
+ } else {
+ stream = video_stream_;
+ switch_pending_ = true;
+ seek_function = base::Bind(&AdaptiveDemuxer::StartStreamSwitchSeek,
+ this,
+ DemuxerStream::VIDEO,
+ video_index);
+ }
+ }
+
+ if (switching_to_current_stream) {
+ done_cb.Run(PIPELINE_OK);
+ return;
+ }
+
+ DCHECK(stream);
+ stream->ChangeCurrentStream(
+ video_index, seek_function,
+ base::Bind(&AdaptiveDemuxer::ChangeVideoStreamDone, this, video_index,
+ done_cb));
+}
+
+int AdaptiveDemuxer::GetCurrentVideoId() const {
base::AutoLock auto_lock(lock_);
- current_audio_demuxer_index_ = audio_index;
- current_video_demuxer_index_ = video_index;
- if (audio_stream_)
- audio_stream_->ChangeCurrentStream(audio_index);
- if (video_stream_)
- video_stream_->ChangeCurrentStream(video_index);
+ return current_video_demuxer_index_;
+}
+
+AdaptiveDemuxer::StreamIdVector AdaptiveDemuxer::GetVideoIds() const {
+ StreamIdVector ids;
+ base::AutoLock auto_lock(lock_);
+
+ for (int i = 0; i < demuxers_.size(); i++) {
+ if (demuxers_[i]->GetStream(DemuxerStream::VIDEO))
+ ids.push_back(i);
+ }
+
+ return ids;
+}
+
+void AdaptiveDemuxer::ChangeVideoStreamDone(int new_stream_index,
+ const PipelineStatusCB& done_cb,
+ PipelineStatus status) {
+ {
+ base::AutoLock auto_lock(lock_);
+
+ switch_pending_ = false;
+
+ if (status == PIPELINE_OK) {
+ demuxers_[current_video_demuxer_index_]->SetPlaybackRate(0);
+ current_video_demuxer_index_ = new_stream_index;
+ demuxers_[current_video_demuxer_index_]->SetPlaybackRate(playback_rate_);
+ }
+ }
+
+ done_cb.Run(status);
}
Demuxer* AdaptiveDemuxer::current_demuxer(DemuxerStream::Type type) {
@@ -241,6 +710,8 @@ class CountingStatusCB : public base::RefCountedThreadSafe<CountingStatusCB> {
};
void AdaptiveDemuxer::Stop(FilterCallback* callback) {
+ stream_switch_manager_->Stop();
+
// Stop() must be called on all of the demuxers even though only one demuxer
// is actively delivering audio and another one is delivering video. This
// just satisfies the contract that all demuxers must have Stop() called on
@@ -253,6 +724,13 @@ void AdaptiveDemuxer::Stop(FilterCallback* callback) {
}
void AdaptiveDemuxer::Seek(base::TimeDelta time, const FilterStatusCB& cb) {
+
+ if (audio_stream_)
+ audio_stream_->OnAdaptiveDemuxerSeek();
+
+ if (video_stream_)
+ video_stream_->OnAdaptiveDemuxerSeek();
+
Demuxer* audio = current_demuxer(DemuxerStream::AUDIO);
Demuxer* video = current_demuxer(DemuxerStream::VIDEO);
int count = (audio ? 1 : 0) + (video && audio != video ? 1 : 0);
@@ -276,6 +754,17 @@ void AdaptiveDemuxer::set_host(FilterHost* filter_host) {
}
void AdaptiveDemuxer::SetPlaybackRate(float playback_rate) {
+ {
+ base::AutoLock auto_lock(lock_);
+ if (playback_rate_ == 0 && playback_rate > 0) {
+ stream_switch_manager_->Play();
+ } else if (playback_rate_ > 0 && playback_rate == 0) {
+ stream_switch_manager_->Pause();
+ }
+
+ playback_rate_ = playback_rate;
+ }
+
Demuxer* audio = current_demuxer(DemuxerStream::AUDIO);
Demuxer* video = current_demuxer(DemuxerStream::VIDEO);
if (audio) audio->SetPlaybackRate(playback_rate);
@@ -300,6 +789,96 @@ scoped_refptr<DemuxerStream> AdaptiveDemuxer::GetStream(
}
}
+void AdaptiveDemuxer::StartStreamSwitchSeek(
+ DemuxerStream::Type type,
+ int stream_index,
+ base::TimeDelta seek_time,
+ const AdaptiveDemuxerStream::SeekFunctionCB& seek_cb) {
+ DCHECK_GE(stream_index, 0);
+ DCHECK(!seek_cb.is_null());
+
+ Demuxer* demuxer = NULL;
+ base::TimeDelta seek_point;
+ FilterStatusCB filter_cb;
+
+ {
+ base::AutoLock auto_lock(lock_);
+
+ demuxer = demuxers_[stream_index];
+
+ if (GetSeekTimeAfter(demuxer->GetStream(type)->GetAVStream(), seek_time,
+ &seek_point)) {
+ // We found a seek point.
+ filter_cb = base::Bind(&AdaptiveDemuxer::OnStreamSeekDone, this,
+ seek_point, seek_cb);
+ } else {
+ // We didn't find a seek point. Assume we don't have index data for it
+ // yet. Seek to the specified time to force index data to be loaded.
+ seek_point = seek_time;
+ filter_cb = base::Bind(&AdaptiveDemuxer::OnIndexSeekDone, this,
+ type, stream_index, seek_time, seek_cb);
+ }
+ }
+
+ DCHECK(demuxer);
+ demuxer->Seek(seek_point, filter_cb);
+}
+
+void AdaptiveDemuxer::OnIndexSeekDone(
+ DemuxerStream::Type type,
+ int stream_index,
+ base::TimeDelta seek_time,
+ const AdaptiveDemuxerStream::SeekFunctionCB& seek_cb,
+ PipelineStatus status) {
+ base::TimeDelta seek_point;
+ FilterStatusCB filter_cb;
+
+ Demuxer* demuxer = NULL;
+
+ if (status != PIPELINE_OK) {
+ seek_cb.Run(status, base::TimeDelta());
+ return;
+ }
+
+ {
+ base::AutoLock auto_lock(lock_);
+
+ demuxer = demuxers_[stream_index];
+ DCHECK(demuxer);
+
+ // Look for a seek point now that we have index data.
+ if (GetSeekTimeAfter(demuxer->GetStream(type)->GetAVStream(), seek_time,
+ &seek_point)) {
+ filter_cb = base::Bind(&AdaptiveDemuxer::OnStreamSeekDone, this,
+ seek_point, seek_cb);
+ } else {
+ // Failed again to find a seek point. Clear the demuxer so that
+ // a seek error will be reported.
+ demuxer = NULL;
+ }
+ }
+
+ if (!demuxer) {
+ seek_cb.Run(PIPELINE_ERROR_INITIALIZATION_FAILED, base::TimeDelta());
+ return;
+ }
+
+ demuxer->Seek(seek_point, filter_cb);
+}
+
+void AdaptiveDemuxer::OnStreamSeekDone(
+ base::TimeDelta seek_point,
+ const AdaptiveDemuxerStream::SeekFunctionCB& seek_cb,
+ PipelineStatus status) {
+ if (status != PIPELINE_OK) {
+ seek_cb.Run(status, base::TimeDelta());
+ return;
+ }
+
+ seek_cb.Run(PIPELINE_OK, seek_point);
+}
+
+
//
// AdaptiveDemuxerFactory
//
« no previous file with comments | « media/filters/adaptive_demuxer.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698