Index: media/base/android/media_codec_player.cc |
diff --git a/media/base/android/media_codec_player.cc b/media/base/android/media_codec_player.cc |
index 27b5bcadb86711426a3a84c18e7f929932578e7a..e1b7562c4e2f29c940fd6cf776485a9e16426bdb 100644 |
--- a/media/base/android/media_codec_player.cc |
+++ b/media/base/android/media_codec_player.cc |
@@ -58,7 +58,7 @@ MediaCodecPlayer::MediaCodecPlayer( |
frame_url), |
ui_task_runner_(base::ThreadTaskRunnerHandle::Get()), |
demuxer_(demuxer.Pass()), |
- state_(STATE_PAUSED), |
+ state_(kStatePaused), |
interpolator_(&default_tick_clock_), |
pending_start_(false), |
pending_seek_(kNoTimestamp()), |
@@ -73,6 +73,7 @@ MediaCodecPlayer::MediaCodecPlayer( |
base::Bind(&MediaPlayerManager::OnPlaybackComplete, manager, player_id); |
seek_done_cb_ = |
base::Bind(&MediaPlayerManager::OnSeekComplete, manager, player_id); |
+ error_cb_ = base::Bind(&MediaPlayerManager::OnError, manager, player_id); |
attach_listener_cb_ = base::Bind(&MediaPlayerAndroid::AttachListener, |
WeakPtrForUIThread(), nullptr); |
detach_listener_cb_ = |
@@ -130,16 +131,62 @@ void MediaCodecPlayer::SetVideoSurface(gfx::ScopedJavaSurface surface) { |
DVLOG(1) << __FUNCTION__ << (surface.IsEmpty() ? " empty" : " non-empty"); |
- // I assume that if video decoder already has the surface, |
- // there will be two calls: |
- // (1) SetVideoSurface(0) |
- // (2) SetVideoSurface(new_surface) |
- video_decoder_->SetPendingSurface(surface.Pass()); |
+ // Save the empty-ness before we pass the surface to the decoder. |
+ bool surface_is_empty = surface.IsEmpty(); |
- if (video_decoder_->HasPendingSurface() && |
- state_ == STATE_WAITING_FOR_SURFACE) { |
- SetState(STATE_PLAYING); |
- StartPlaybackDecoders(); |
+ // Apparently RemoveVideoSurface() can be called several times in a row, |
+ // ignore the second and subsequent calls. |
+ if (surface_is_empty && !video_decoder_->HasVideoSurface()) { |
+ DVLOG(1) << __FUNCTION__ << ": surface already removed, ignoring"; |
+ return; |
+ } |
+ |
+ video_decoder_->SetVideoSurface(surface.Pass()); |
+ |
+ if (surface_is_empty) { |
+ // Remove video surface. |
+ switch (state_) { |
+ case kStatePlaying: |
+ if (VideoFinished()) |
+ break; |
+ |
+ DVLOG(1) << __FUNCTION__ << ": stopping and restarting"; |
+ // Stop decoders as quickly as possible. |
+ StopDecoders(); // synchronous stop |
+ |
+ // Prefetch or wait for initial configuration. |
+ if (HasAudio() || HasVideo()) { |
+ SetState(kStatePrefetching); |
+ StartPrefetchDecoders(); |
+ } else { |
+ SetState(kStateWaitingForConfig); |
+ } |
+ break; |
+ |
+ default: |
+ break; // ignore |
+ } |
+ } else { |
+ // Replace video surface. |
+ switch (state_) { |
+ case kStateWaitingForSurface: |
+ SetState(kStatePlaying); |
+ StartPlaybackOrBrowserSeek(); |
+ break; |
+ |
+ case kStatePlaying: |
+ if (VideoFinished()) |
+ break; |
+ |
+ DVLOG(1) << __FUNCTION__ << ": requesting to stop and restart"; |
+ SetState(kStateStopping); |
+ RequestToStopDecoders(); |
+ SetPendingStart(true); |
+ break; |
+ |
+ default: |
+ break; // ignore |
+ } |
} |
} |
@@ -149,24 +196,24 @@ void MediaCodecPlayer::Start() { |
DVLOG(1) << __FUNCTION__; |
switch (state_) { |
- case STATE_PAUSED: |
+ case kStatePaused: |
// Prefetch or wait for initial configuration. |
if (HasAudio() || HasVideo()) { |
- SetState(STATE_PREFETCHING); |
+ SetState(kStatePrefetching); |
StartPrefetchDecoders(); |
} else { |
- SetState(STATE_WAITING_FOR_CONFIG); |
+ SetState(kStateWaitingForConfig); |
} |
break; |
- case STATE_STOPPING: |
- case STATE_WAITING_FOR_SEEK: |
+ case kStateStopping: |
+ case kStateWaitingForSeek: |
SetPendingStart(true); |
break; |
- case STATE_WAITING_FOR_CONFIG: |
- case STATE_PREFETCHING: |
- case STATE_PLAYING: |
- case STATE_WAITING_FOR_SURFACE: |
- case STATE_ERROR: |
+ case kStateWaitingForConfig: |
+ case kStatePrefetching: |
+ case kStatePlaying: |
+ case kStateWaitingForSurface: |
+ case kStateError: |
break; // Ignore |
default: |
NOTREACHED(); |
@@ -182,20 +229,20 @@ void MediaCodecPlayer::Pause(bool is_media_related_action) { |
SetPendingStart(false); |
switch (state_) { |
- case STATE_WAITING_FOR_CONFIG: |
- case STATE_PREFETCHING: |
- case STATE_WAITING_FOR_SURFACE: |
- SetState(STATE_PAUSED); |
+ case kStateWaitingForConfig: |
+ case kStatePrefetching: |
+ case kStateWaitingForSurface: |
+ SetState(kStatePaused); |
StopDecoders(); |
break; |
- case STATE_PLAYING: |
- SetState(STATE_STOPPING); |
+ case kStatePlaying: |
+ SetState(kStateStopping); |
RequestToStopDecoders(); |
break; |
- case STATE_PAUSED: |
- case STATE_STOPPING: |
- case STATE_WAITING_FOR_SEEK: |
- case STATE_ERROR: |
+ case kStatePaused: |
+ case kStateStopping: |
+ case kStateWaitingForSeek: |
+ case kStateError: |
break; // Ignore |
default: |
NOTREACHED(); |
@@ -209,31 +256,31 @@ void MediaCodecPlayer::SeekTo(base::TimeDelta timestamp) { |
DVLOG(1) << __FUNCTION__ << " " << timestamp; |
switch (state_) { |
- case STATE_PAUSED: |
- SetState(STATE_WAITING_FOR_SEEK); |
+ case kStatePaused: |
+ SetState(kStateWaitingForSeek); |
RequestDemuxerSeek(timestamp); |
break; |
- case STATE_WAITING_FOR_CONFIG: |
- case STATE_PREFETCHING: |
- case STATE_WAITING_FOR_SURFACE: |
- SetState(STATE_WAITING_FOR_SEEK); |
+ case kStateWaitingForConfig: |
+ case kStatePrefetching: |
+ case kStateWaitingForSurface: |
+ SetState(kStateWaitingForSeek); |
StopDecoders(); |
SetPendingStart(true); |
RequestDemuxerSeek(timestamp); |
break; |
- case STATE_PLAYING: |
- SetState(STATE_STOPPING); |
+ case kStatePlaying: |
+ SetState(kStateStopping); |
RequestToStopDecoders(); |
SetPendingStart(true); |
SetPendingSeek(timestamp); |
break; |
- case STATE_STOPPING: |
+ case kStateStopping: |
SetPendingSeek(timestamp); |
break; |
- case STATE_WAITING_FOR_SEEK: |
+ case kStateWaitingForSeek: |
SetPendingSeek(timestamp); |
break; |
- case STATE_ERROR: |
+ case kStateError: |
break; // ignore |
default: |
NOTREACHED(); |
@@ -246,8 +293,22 @@ void MediaCodecPlayer::Release() { |
DVLOG(1) << __FUNCTION__; |
- SetState(STATE_PAUSED); |
+ // Stop decoding threads and delete MediaCodecs, but keep IPC between browser |
+ // and renderer processes going. Seek should work across and after Release(). |
+ |
ReleaseDecoderResources(); |
+ |
+ SetPendingStart(false); |
+ |
+ if (state_ != kStateWaitingForSeek) |
+ SetState(kStatePaused); |
+ |
+ base::TimeDelta pending_seek_time = GetPendingSeek(); |
+ if (pending_seek_time != kNoTimestamp()) { |
+ SetPendingSeek(kNoTimestamp()); |
+ SetState(kStateWaitingForSeek); |
+ RequestDemuxerSeek(pending_seek_time); |
+ } |
} |
void MediaCodecPlayer::SetVolume(double volume) { |
@@ -282,7 +343,7 @@ bool MediaCodecPlayer::IsPlaying() { |
// TODO(timav): Use another variable since |state_| should only be accessed on |
// Media thread. |
- return state_ == STATE_PLAYING || state_ == STATE_STOPPING; |
+ return state_ == kStatePlaying || state_ == kStateStopping; |
} |
bool MediaCodecPlayer::CanPause() { |
@@ -354,9 +415,6 @@ void MediaCodecPlayer::OnDemuxerSeekDone( |
DVLOG(1) << __FUNCTION__ << " actual_time:" << actual_browser_seek_time; |
- if (state_ != STATE_WAITING_FOR_SEEK) |
- return; // ignore |
- |
DCHECK(seek_info_.get()); |
DCHECK(seek_info_->seek_time != kNoTimestamp()); |
@@ -375,9 +433,22 @@ void MediaCodecPlayer::OnDemuxerSeekDone( |
interpolator_.SetBounds(seek_time, seek_time); |
audio_decoder_->SetBaseTimestamp(seek_time); |
+ // The Flush() might set the state to kStateError. |
+ if (state_ == kStateError) { |
+ // Notify the Renderer. |
+ if (!seek_info_->is_browser_seek) |
+ ui_task_runner_->PostTask(FROM_HERE, |
+ base::Bind(seek_done_cb_, seek_time)); |
+ |
+ seek_info_.reset(); |
+ return; |
+ } |
+ |
+ DCHECK_EQ(kStateWaitingForSeek, state_); |
+ |
base::TimeDelta pending_seek_time = GetPendingSeek(); |
if (pending_seek_time != kNoTimestamp()) { |
- // Keep STATE_WAITING_FOR_SEEK |
+ // Keep kStateWaitingForSeek |
SetPendingSeek(kNoTimestamp()); |
RequestDemuxerSeek(pending_seek_time); |
return; |
@@ -387,13 +458,13 @@ void MediaCodecPlayer::OnDemuxerSeekDone( |
SetPendingStart(false); |
// Prefetch or wait for initial configuration. |
if (HasAudio() || HasVideo()) { |
- SetState(STATE_PREFETCHING); |
+ SetState(kStatePrefetching); |
StartPrefetchDecoders(); |
} else { |
- SetState(STATE_WAITING_FOR_CONFIG); |
+ SetState(kStateWaitingForConfig); |
} |
} else { |
- SetState(STATE_PAUSED); |
+ SetState(kStatePaused); |
} |
// Notify the Renderer. |
@@ -458,7 +529,7 @@ void MediaCodecPlayer::RequestDemuxerData(DemuxerStream::Type stream_type) { |
void MediaCodecPlayer::OnPrefetchDone() { |
DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); |
- if (state_ != STATE_PREFETCHING) { |
+ if (state_ != kStatePrefetching) { |
DVLOG(1) << __FUNCTION__ << " wrong state " << AsString(state_) |
<< " ignoring"; |
return; // Ignore |
@@ -470,17 +541,18 @@ void MediaCodecPlayer::OnPrefetchDone() { |
// No configuration at all after prefetching. |
// This is an error, initial configuration is expected |
// before the first data chunk. |
- GetMediaTaskRunner()->PostTask(FROM_HERE, error_cb_); |
+ DCHECK(!internal_error_cb_.is_null()); |
+ GetMediaTaskRunner()->PostTask(FROM_HERE, internal_error_cb_); |
return; |
} |
- if (HasVideo() && !HasPendingSurface()) { |
- SetState(STATE_WAITING_FOR_SURFACE); |
+ if (HasVideo() && !video_decoder_->HasVideoSurface()) { |
+ SetState(kStateWaitingForSurface); |
return; |
} |
- SetState(STATE_PLAYING); |
- StartPlaybackDecoders(); |
+ SetState(kStatePlaying); |
+ StartPlaybackOrBrowserSeek(); |
} |
void MediaCodecPlayer::OnStopDone() { |
@@ -496,23 +568,23 @@ void MediaCodecPlayer::OnStopDone() { |
base::TimeDelta seek_time; |
switch (state_) { |
- case STATE_STOPPING: { |
+ case kStateStopping: { |
base::TimeDelta seek_time = GetPendingSeek(); |
if (seek_time != kNoTimestamp()) { |
- SetState(STATE_WAITING_FOR_SEEK); |
+ SetState(kStateWaitingForSeek); |
SetPendingSeek(kNoTimestamp()); |
RequestDemuxerSeek(seek_time); |
} else if (HasPendingStart()) { |
SetPendingStart(false); |
- SetState(STATE_PREFETCHING); |
+ SetState(kStatePrefetching); |
StartPrefetchDecoders(); |
} else { |
- SetState(STATE_PAUSED); |
+ SetState(kStatePaused); |
} |
} break; |
- case STATE_PLAYING: |
+ case kStatePlaying: |
// Unexpected stop means completion |
- SetState(STATE_PAUSED); |
+ SetState(kStatePaused); |
break; |
default: |
// DVLOG(0) << __FUNCTION__ << " illegal state: " << AsString(state_); |
@@ -520,7 +592,7 @@ void MediaCodecPlayer::OnStopDone() { |
// Ignore! There can be a race condition: audio posts OnStopDone, |
// then video posts, then first OnStopDone arrives at which point |
// both streams are already stopped, then second OnStopDone arrives. When |
- // the second one arrives, the state us not STATE_STOPPING any more. |
+ // the second one arrives, the state us not kStateStopping any more. |
break; |
} |
@@ -535,20 +607,23 @@ void MediaCodecPlayer::OnError() { |
DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); |
DVLOG(1) << __FUNCTION__; |
- // STATE_ERROR blocks all events |
- SetState(STATE_ERROR); |
+ // kStateError blocks all events |
+ SetState(kStateError); |
ReleaseDecoderResources(); |
+ |
+ ui_task_runner_->PostTask(FROM_HERE, |
+ base::Bind(error_cb_, MEDIA_ERROR_DECODE)); |
} |
void MediaCodecPlayer::OnStarvation(DemuxerStream::Type type) { |
DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); |
DVLOG(1) << __FUNCTION__ << " stream type:" << type; |
- if (state_ != STATE_PLAYING) |
+ if (state_ != kStatePlaying) |
return; // Ignore |
- SetState(STATE_STOPPING); |
+ SetState(kStateStopping); |
RequestToStopDecoders(); |
SetPendingStart(true); |
} |
@@ -605,17 +680,6 @@ void MediaCodecPlayer::SetState(PlayerState new_state) { |
state_ = new_state; |
} |
-void MediaCodecPlayer::SetPendingSurface(gfx::ScopedJavaSurface surface) { |
- DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); |
- DVLOG(1) << __FUNCTION__; |
- |
- video_decoder_->SetPendingSurface(surface.Pass()); |
-} |
- |
-bool MediaCodecPlayer::HasPendingSurface() const { |
- return video_decoder_->HasPendingSurface(); |
-} |
- |
void MediaCodecPlayer::SetPendingStart(bool need_to_start) { |
DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); |
DVLOG(1) << __FUNCTION__ << ": " << need_to_start; |
@@ -665,8 +729,8 @@ void MediaCodecPlayer::SetDemuxerConfigs(const DemuxerConfigs& configs) { |
if (configs.video_codec != kUnknownVideoCodec) |
video_decoder_->SetDemuxerConfigs(configs); |
- if (state_ == STATE_WAITING_FOR_CONFIG) { |
- SetState(STATE_PREFETCHING); |
+ if (state_ == kStateWaitingForConfig) { |
+ SetState(kStatePrefetching); |
StartPrefetchDecoders(); |
} |
} |
@@ -699,33 +763,63 @@ void MediaCodecPlayer::StartPrefetchDecoders() { |
video_decoder_->Prefetch(prefetch_cb); |
} |
-void MediaCodecPlayer::StartPlaybackDecoders() { |
+void MediaCodecPlayer::StartPlaybackOrBrowserSeek() { |
DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); |
DVLOG(1) << __FUNCTION__; |
- // Configure all streams before the start since |
- // we may discover that browser seek is required. |
+ // TODO(timav): consider replacing this method with posting a |
+ // browser seek task (i.e. generate an event) from StartPlaybackDecoders(). |
+ |
+ StartStatus status = StartPlaybackDecoders(); |
+ |
+ switch (status) { |
+ case kStartBrowserSeekRequired: |
+ // Browser seek |
+ SetState(kStateWaitingForSeek); |
+ SetPendingStart(true); |
+ StopDecoders(); |
+ RequestDemuxerSeek(GetInterpolatedTime(), true); |
+ break; |
+ case kStartFailed: |
+ GetMediaTaskRunner()->PostTask(FROM_HERE, internal_error_cb_); |
+ break; |
+ case kStartOk: |
+ break; |
+ } |
+} |
+ |
+MediaCodecPlayer::StartStatus MediaCodecPlayer::StartPlaybackDecoders() { |
+ DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); |
+ DVLOG(1) << __FUNCTION__; |
bool do_audio = !AudioFinished(); |
bool do_video = !VideoFinished(); |
- // If there is nothing to play, the state machine should determine |
- // this at the prefetch state and never call this method. |
+ // If there is nothing to play, the state machine should determine this at the |
+ // prefetch state and never call this method. |
DCHECK(do_audio || do_video); |
- if (do_audio) { |
- MediaCodecDecoder::ConfigStatus status = audio_decoder_->Configure(); |
- if (status != MediaCodecDecoder::CONFIG_OK) { |
- GetMediaTaskRunner()->PostTask(FROM_HERE, error_cb_); |
- return; |
- } |
- } |
+ // Configure all streams before the start since we may discover that browser |
+ // seek is required. Start with video: if browser seek is required it would |
+ // not make sense to configure audio. |
if (do_video) { |
MediaCodecDecoder::ConfigStatus status = video_decoder_->Configure(); |
- if (status != MediaCodecDecoder::CONFIG_OK) { |
- GetMediaTaskRunner()->PostTask(FROM_HERE, error_cb_); |
- return; |
+ switch (status) { |
+ case MediaCodecDecoder::kConfigOk: |
+ break; |
+ case MediaCodecDecoder::kConfigKeyFrameRequired: |
+ // TODO(timav): post a task or return the status? |
+ return kStartBrowserSeekRequired; |
+ case MediaCodecDecoder::kConfigFailure: |
+ return kStartFailed; |
+ } |
+ } |
+ |
+ if (do_audio) { |
+ MediaCodecDecoder::ConfigStatus status = audio_decoder_->Configure(); |
+ if (status != MediaCodecDecoder::kConfigOk) { |
+ return kStartFailed; |
} |
} |
@@ -737,8 +831,7 @@ void MediaCodecPlayer::StartPlaybackDecoders() { |
if (do_audio) { |
if (!audio_decoder_->Start(current_time)) { |
- GetMediaTaskRunner()->PostTask(FROM_HERE, error_cb_); |
- return; |
+ return kStartFailed; |
} |
// Attach listener on UI thread |
@@ -747,18 +840,19 @@ void MediaCodecPlayer::StartPlaybackDecoders() { |
if (do_video) { |
if (!video_decoder_->Start(current_time)) { |
- GetMediaTaskRunner()->PostTask(FROM_HERE, error_cb_); |
- return; |
+ return kStartFailed; |
} |
} |
+ |
+ return kStartOk; |
} |
void MediaCodecPlayer::StopDecoders() { |
DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); |
DVLOG(1) << __FUNCTION__; |
- audio_decoder_->SyncStop(); |
video_decoder_->SyncStop(); |
+ audio_decoder_->SyncStop(); |
} |
void MediaCodecPlayer::RequestToStopDecoders() { |
@@ -789,14 +883,14 @@ void MediaCodecPlayer::RequestDemuxerSeek(base::TimeDelta seek_time, |
bool is_browser_seek) { |
DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); |
DVLOG(1) << __FUNCTION__ << " " << seek_time |
- << (is_browser_seek ? " browser_seek" : ""); |
+ << (is_browser_seek ? " BROWSER_SEEK" : ""); |
// Flush decoders before requesting demuxer. |
audio_decoder_->Flush(); |
video_decoder_->Flush(); |
- // Save active seek data. Logically it is attached to STATE_WAITING_FOR_SEEK. |
- DCHECK(state_ == STATE_WAITING_FOR_SEEK); |
+ // Save active seek data. Logically it is attached to kStateWaitingForSeek. |
+ DCHECK_EQ(kStateWaitingForSeek, state_); |
seek_info_.reset(new SeekInfo(seek_time, is_browser_seek)); |
demuxer_->RequestDemuxerSeek(seek_time, is_browser_seek); |
@@ -821,14 +915,15 @@ void MediaCodecPlayer::CreateDecoders() { |
DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); |
DVLOG(1) << __FUNCTION__; |
- error_cb_ = base::Bind(&MediaCodecPlayer::OnError, media_weak_this_); |
+ internal_error_cb_ = base::Bind(&MediaCodecPlayer::OnError, media_weak_this_); |
audio_decoder_.reset(new MediaCodecAudioDecoder( |
GetMediaTaskRunner(), base::Bind(&MediaCodecPlayer::RequestDemuxerData, |
media_weak_this_, DemuxerStream::AUDIO), |
base::Bind(&MediaCodecPlayer::OnStarvation, media_weak_this_, |
DemuxerStream::AUDIO), |
- base::Bind(&MediaCodecPlayer::OnStopDone, media_weak_this_), error_cb_, |
+ base::Bind(&MediaCodecPlayer::OnStopDone, media_weak_this_), |
+ internal_error_cb_, |
base::Bind(&MediaCodecPlayer::OnTimeIntervalUpdate, media_weak_this_, |
DemuxerStream::AUDIO))); |
@@ -837,7 +932,8 @@ void MediaCodecPlayer::CreateDecoders() { |
media_weak_this_, DemuxerStream::VIDEO), |
base::Bind(&MediaCodecPlayer::OnStarvation, media_weak_this_, |
DemuxerStream::VIDEO), |
- base::Bind(&MediaCodecPlayer::OnStopDone, media_weak_this_), error_cb_, |
+ base::Bind(&MediaCodecPlayer::OnStopDone, media_weak_this_), |
+ internal_error_cb_, |
base::Bind(&MediaCodecPlayer::OnTimeIntervalUpdate, media_weak_this_, |
DemuxerStream::VIDEO), |
base::Bind(&MediaCodecPlayer::OnVideoResolutionChanged, media_weak_this_), |
@@ -866,14 +962,14 @@ base::TimeDelta MediaCodecPlayer::GetInterpolatedTime() { |
const char* MediaCodecPlayer::AsString(PlayerState state) { |
switch (state) { |
- RETURN_STRING(STATE_PAUSED); |
- RETURN_STRING(STATE_WAITING_FOR_CONFIG); |
- RETURN_STRING(STATE_PREFETCHING); |
- RETURN_STRING(STATE_PLAYING); |
- RETURN_STRING(STATE_STOPPING); |
- RETURN_STRING(STATE_WAITING_FOR_SURFACE); |
- RETURN_STRING(STATE_WAITING_FOR_SEEK); |
- RETURN_STRING(STATE_ERROR); |
+ RETURN_STRING(kStatePaused); |
+ RETURN_STRING(kStateWaitingForConfig); |
+ RETURN_STRING(kStatePrefetching); |
+ RETURN_STRING(kStatePlaying); |
+ RETURN_STRING(kStateStopping); |
+ RETURN_STRING(kStateWaitingForSurface); |
+ RETURN_STRING(kStateWaitingForSeek); |
+ RETURN_STRING(kStateError); |
} |
return nullptr; // crash early |
} |