Index: media/base/android/media_source_player.cc |
diff --git a/media/base/android/media_source_player.cc b/media/base/android/media_source_player.cc |
index 52ea7db11af5e240a43ecd86a598cb99d6320db6..a5e2c1554579333bd978392b6dca7aa888978187 100644 |
--- a/media/base/android/media_source_player.cc |
+++ b/media/base/android/media_source_player.cc |
@@ -1,27 +1,35 @@ |
-// Copyright (c) 2013 The Chromium Authors. All rights reserved. |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
// Use of this source code is governed by a BSD-style license that can be |
// found in the LICENSE file. |
#include "media/base/android/media_source_player.h" |
-#include <limits> |
- |
-#include "base/android/jni_android.h" |
-#include "base/android/jni_string.h" |
-#include "base/barrier_closure.h" |
-#include "base/basictypes.h" |
#include "base/bind.h" |
-#include "base/callback_helpers.h" |
+#include "base/lazy_instance.h" |
#include "base/logging.h" |
-#include "base/strings/string_number_conversions.h" |
-#include "base/trace_event/trace_event.h" |
-#include "media/base/android/audio_decoder_job.h" |
-#include "media/base/android/media_drm_bridge.h" |
+#include "base/threading/thread.h" |
+ |
#include "media/base/android/media_player_manager.h" |
-#include "media/base/android/video_decoder_job.h" |
+#include "media/base/android/media_source_player_impl.h" |
namespace media { |
+class MediaThread : public base::Thread { |
+ public: |
+ MediaThread() : base::Thread("MediaThread") { |
xhwang
2015/04/29 23:50:51
We also have a media thread on desktop in the rend
timav
2015/04/30 21:18:50
I did this. I have a small question though: I thou
xhwang
2015/05/01 06:55:05
Some time I print thread names in my local build t
|
+ Start(); |
+ } |
+}; |
+ |
+// Create media thread |
+base::LazyInstance<MediaThread>::Leaky |
+ g_media_thread = LAZY_INSTANCE_INITIALIZER; |
+ |
+ |
+scoped_refptr<base::SingleThreadTaskRunner> GetMediaTaskRunner() { |
+ return g_media_thread.Pointer()->task_runner(); |
+} |
+ |
MediaSourcePlayer::MediaSourcePlayer( |
int player_id, |
MediaPlayerManager* manager, |
@@ -32,811 +40,117 @@ MediaSourcePlayer::MediaSourcePlayer( |
manager, |
request_media_resources_cb, |
frame_url), |
- demuxer_(demuxer.Pass()), |
- pending_event_(NO_EVENT_PENDING), |
- playing_(false), |
- interpolator_(&default_tick_clock_), |
- doing_browser_seek_(false), |
- pending_seek_(false), |
- drm_bridge_(NULL), |
- cdm_registration_id_(0), |
- is_waiting_for_key_(false), |
- key_added_while_decode_pending_(false), |
- is_waiting_for_audio_decoder_(false), |
- is_waiting_for_video_decoder_(false), |
- prerolling_(true), |
- weak_factory_(this) { |
- audio_decoder_job_.reset(new AudioDecoderJob( |
- base::Bind(&DemuxerAndroid::RequestDemuxerData, |
- base::Unretained(demuxer_.get()), |
- DemuxerStream::AUDIO), |
- base::Bind(&MediaSourcePlayer::OnDemuxerConfigsChanged, |
- weak_factory_.GetWeakPtr()))); |
- video_decoder_job_.reset(new VideoDecoderJob( |
- base::Bind(&DemuxerAndroid::RequestDemuxerData, |
- base::Unretained(demuxer_.get()), |
- DemuxerStream::VIDEO), |
- base::Bind(request_media_resources_cb_, player_id), |
- base::Bind(&MediaSourcePlayer::OnDemuxerConfigsChanged, |
- weak_factory_.GetWeakPtr()))); |
- demuxer_->Initialize(this); |
- interpolator_.SetUpperBound(base::TimeDelta()); |
- weak_this_ = weak_factory_.GetWeakPtr(); |
-} |
+ weak_factory_(this) |
+{ |
+ // UI thread |
+ DVLOG(1) << "MediaSourcePlayer::MediaSourcePlayer: player_id:" << player_id; |
-MediaSourcePlayer::~MediaSourcePlayer() { |
- Release(); |
- DCHECK_EQ(!drm_bridge_, !cdm_registration_id_); |
- if (drm_bridge_) { |
- drm_bridge_->UnregisterPlayer(cdm_registration_id_); |
- cdm_registration_id_ = 0; |
- } |
-} |
- |
-void MediaSourcePlayer::SetVideoSurface(gfx::ScopedJavaSurface surface) { |
- DVLOG(1) << __FUNCTION__; |
- if (!video_decoder_job_->SetVideoSurface(surface.Pass())) |
- return; |
- // Retry video decoder creation. |
- RetryDecoderCreation(false, true); |
+ impl_ = new MediaSourcePlayerImpl(demuxer.Pass()); |
} |
-void MediaSourcePlayer::ScheduleSeekEventAndStopDecoding( |
- base::TimeDelta seek_time) { |
- DVLOG(1) << __FUNCTION__ << "(" << seek_time.InSecondsF() << ")"; |
- DCHECK(!IsEventPending(SEEK_EVENT_PENDING)); |
+MediaSourcePlayer::~MediaSourcePlayer() |
+{ |
+ // UI thread |
+ DVLOG(1) << "MediaSourcePlayer::~MediaSourcePlayer:" |
+ << " player_id:" << player_id(); |
- pending_seek_ = false; |
+ // Postpone the deletion of |impl_| until it is properly stopped |
+ // on the Media thread. |
+ GetMediaTaskRunner()->PostTask( |
+ FROM_HERE, base::Bind(&MediaSourcePlayerImpl::DestroySelf, |
+ base::Unretained(impl_))); |
+} |
- interpolator_.SetBounds(seek_time, seek_time); |
+// MediaPlayerAndroid implementation. |
+// The methods are called on UI thread |
- if (audio_decoder_job_->is_decoding()) |
- audio_decoder_job_->StopDecode(); |
- if (video_decoder_job_->is_decoding()) |
- video_decoder_job_->StopDecode(); |
+void MediaSourcePlayer::SetVideoSurface(gfx::ScopedJavaSurface surface) { |
+ DVLOG(1) << __FUNCTION__; |
- SetPendingEvent(SEEK_EVENT_PENDING); |
- ProcessPendingEvents(); |
+ GetMediaTaskRunner()->PostTask( |
+ FROM_HERE, base::Bind(&MediaSourcePlayerImpl::SetVideoSurface, |
+ base::Unretained(impl_), base::Passed(&surface))); |
xhwang
2015/04/29 23:50:51
Now the messages from the render process are first
timav
2015/04/30 21:18:50
Except for SetVideoSurface, which comes directly f
xhwang
2015/05/01 06:55:05
Agreed that should be in a separate CL and we can
|
} |
-void MediaSourcePlayer::BrowserSeekToCurrentTime() { |
+void MediaSourcePlayer::Start() { |
DVLOG(1) << __FUNCTION__; |
- DCHECK(!IsEventPending(SEEK_EVENT_PENDING)); |
- doing_browser_seek_ = true; |
- ScheduleSeekEventAndStopDecoding(GetCurrentTime()); |
+ GetMediaTaskRunner()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&MediaSourcePlayerImpl::Start, base::Unretained(impl_))); |
} |
-bool MediaSourcePlayer::Seekable() { |
- // If the duration TimeDelta, converted to milliseconds from microseconds, |
- // is >= 2^31, then the media is assumed to be unbounded and unseekable. |
- // 2^31 is the bound due to java player using 32-bit integer for time |
- // values at millisecond resolution. |
- return duration_ < |
- base::TimeDelta::FromMilliseconds(std::numeric_limits<int32>::max()); |
-} |
- |
-void MediaSourcePlayer::Start() { |
+void MediaSourcePlayer::Pause(bool /* is_media_related_action */) { |
DVLOG(1) << __FUNCTION__; |
- playing_ = true; |
+ GetMediaTaskRunner()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&MediaSourcePlayerImpl::Pause, base::Unretained(impl_))); |
+} |
+ |
+void MediaSourcePlayer::SeekTo(base::TimeDelta timestamp) { |
+ DVLOG(1) << __FUNCTION__ << " " << timestamp; |
- StartInternal(); |
+ GetMediaTaskRunner()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&MediaSourcePlayerImpl::SeekTo, |
+ base::Unretained(impl_), timestamp)); |
} |
-void MediaSourcePlayer::Pause(bool is_media_related_action) { |
+void MediaSourcePlayer::Release() { |
DVLOG(1) << __FUNCTION__; |
- // Since decoder jobs have their own thread, decoding is not fully paused |
- // until all the decoder jobs call MediaDecoderCallback(). It is possible |
- // that Start() is called while the player is waiting for |
- // MediaDecoderCallback(). In that case, decoding will continue when |
- // MediaDecoderCallback() is called. |
- playing_ = false; |
- start_time_ticks_ = base::TimeTicks(); |
+ GetMediaTaskRunner()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&MediaSourcePlayerImpl::Release, base::Unretained(impl_))); |
} |
-bool MediaSourcePlayer::IsPlaying() { |
- return playing_; |
+void MediaSourcePlayer::SetVolume(double volume) { |
+ DVLOG(1) << __FUNCTION__ << " " << volume; |
+ GetMediaTaskRunner()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&MediaSourcePlayerImpl::SetVolume, |
+ base::Unretained(impl_), volume)); |
} |
int MediaSourcePlayer::GetVideoWidth() { |
- return video_decoder_job_->output_width(); |
+ return impl_->GetVideoWidth(); |
} |
int MediaSourcePlayer::GetVideoHeight() { |
- return video_decoder_job_->output_height(); |
-} |
- |
-void MediaSourcePlayer::SeekTo(base::TimeDelta timestamp) { |
- DVLOG(1) << __FUNCTION__ << "(" << timestamp.InSecondsF() << ")"; |
- |
- if (IsEventPending(SEEK_EVENT_PENDING)) { |
- DCHECK(doing_browser_seek_) << "SeekTo while SeekTo in progress"; |
- DCHECK(!pending_seek_) << "SeekTo while SeekTo pending browser seek"; |
- |
- // There is a browser seek currently in progress to obtain I-frame to feed |
- // a newly constructed video decoder. Remember this real seek request so |
- // it can be initiated once OnDemuxerSeekDone() occurs for the browser seek. |
- pending_seek_ = true; |
- pending_seek_time_ = timestamp; |
- return; |
- } |
- |
- doing_browser_seek_ = false; |
- ScheduleSeekEventAndStopDecoding(timestamp); |
+ return impl_->GetVideoHeight(); |
} |
base::TimeDelta MediaSourcePlayer::GetCurrentTime() { |
- return std::min(interpolator_.GetInterpolatedTime(), duration_); |
+ return impl_->GetCurrentTime(); |
} |
base::TimeDelta MediaSourcePlayer::GetDuration() { |
- return duration_; |
-} |
- |
-void MediaSourcePlayer::Release() { |
- DVLOG(1) << __FUNCTION__; |
- |
- audio_decoder_job_->ReleaseDecoderResources(); |
- video_decoder_job_->ReleaseDecoderResources(); |
- |
- // Prevent player restart, including job re-creation attempts. |
- playing_ = false; |
- |
- decoder_starvation_callback_.Cancel(); |
- |
- SetAudible(false); |
- DetachListener(); |
+ return impl_->GetDuration(); |
} |
-void MediaSourcePlayer::SetVolume(double volume) { |
- audio_decoder_job_->SetVolume(volume); |
+bool MediaSourcePlayer::IsPlaying() { |
+ return impl_->IsPlaying(); |
} |
bool MediaSourcePlayer::CanPause() { |
- return Seekable(); |
+ return impl_->CanPause(); |
} |
bool MediaSourcePlayer::CanSeekForward() { |
- return Seekable(); |
+ return impl_->CanSeekForward(); |
} |
bool MediaSourcePlayer::CanSeekBackward() { |
- return Seekable(); |
+ return impl_->CanSeekBackward(); |
} |
bool MediaSourcePlayer::IsPlayerReady() { |
- return audio_decoder_job_ || video_decoder_job_; |
-} |
- |
-void MediaSourcePlayer::StartInternal() { |
- DVLOG(1) << __FUNCTION__; |
- // If there are pending events, wait for them finish. |
- if (pending_event_ != NO_EVENT_PENDING) |
- return; |
- |
- // When we start, we could have new demuxed data coming in. This new data |
- // could be clear (not encrypted) or encrypted with different keys. So key |
- // related info should all be cleared. |
- is_waiting_for_key_ = false; |
- key_added_while_decode_pending_ = false; |
- AttachListener(NULL); |
- |
- SetPendingEvent(PREFETCH_REQUEST_EVENT_PENDING); |
- ProcessPendingEvents(); |
-} |
- |
-void MediaSourcePlayer::OnDemuxerConfigsAvailable( |
- const DemuxerConfigs& configs) { |
- DVLOG(1) << __FUNCTION__; |
- DCHECK(!HasAudio() && !HasVideo()); |
- |
- duration_ = configs.duration; |
- |
- audio_decoder_job_->SetDemuxerConfigs(configs); |
- video_decoder_job_->SetDemuxerConfigs(configs); |
- OnDemuxerConfigsChanged(); |
-} |
- |
-void MediaSourcePlayer::OnDemuxerDataAvailable(const DemuxerData& data) { |
- DVLOG(1) << __FUNCTION__ << "(" << data.type << ")"; |
- DCHECK_LT(0u, data.access_units.size()); |
- CHECK_GE(1u, data.demuxer_configs.size()); |
- |
- if (data.type == DemuxerStream::AUDIO) |
- audio_decoder_job_->OnDataReceived(data); |
- else if (data.type == DemuxerStream::VIDEO) |
- video_decoder_job_->OnDataReceived(data); |
-} |
- |
-void MediaSourcePlayer::OnDemuxerDurationChanged(base::TimeDelta duration) { |
- duration_ = duration; |
-} |
- |
-void MediaSourcePlayer::OnMediaCryptoReady() { |
- DCHECK(!drm_bridge_->GetMediaCrypto().is_null()); |
- drm_bridge_->SetMediaCryptoReadyCB(base::Closure()); |
- |
- // Retry decoder creation if the decoders are waiting for MediaCrypto. |
- RetryDecoderCreation(true, true); |
+ return impl_->IsPlayerReady(); |
} |
void MediaSourcePlayer::SetCdm(BrowserCdm* cdm) { |
- // Currently we don't support DRM change during the middle of playback, even |
- // if the player is paused. |
- // TODO(qinmin): support DRM change after playback has started. |
- // http://crbug.com/253792. |
- if (GetCurrentTime() > base::TimeDelta()) { |
- VLOG(0) << "Setting DRM bridge after playback has started. " |
- << "This is not well supported!"; |
- } |
- |
- if (drm_bridge_) { |
- NOTREACHED() << "Currently we do not support resetting CDM."; |
- return; |
- } |
- |
- // Only MediaDrmBridge will be set on MediaSourcePlayer. |
- drm_bridge_ = static_cast<MediaDrmBridge*>(cdm); |
- |
- cdm_registration_id_ = drm_bridge_->RegisterPlayer( |
- base::Bind(&MediaSourcePlayer::OnKeyAdded, weak_this_), |
- base::Bind(&MediaSourcePlayer::OnCdmUnset, weak_this_)); |
- |
- audio_decoder_job_->SetDrmBridge(drm_bridge_); |
- video_decoder_job_->SetDrmBridge(drm_bridge_); |
- |
- if (drm_bridge_->GetMediaCrypto().is_null()) { |
- drm_bridge_->SetMediaCryptoReadyCB( |
- base::Bind(&MediaSourcePlayer::OnMediaCryptoReady, weak_this_)); |
- return; |
- } |
- |
- // If the player is previously waiting for CDM, retry decoder creation. |
- RetryDecoderCreation(true, true); |
-} |
- |
-void MediaSourcePlayer::OnDemuxerSeekDone( |
- base::TimeDelta actual_browser_seek_time) { |
- DVLOG(1) << __FUNCTION__; |
- |
- ClearPendingEvent(SEEK_EVENT_PENDING); |
- if (IsEventPending(PREFETCH_REQUEST_EVENT_PENDING)) |
- ClearPendingEvent(PREFETCH_REQUEST_EVENT_PENDING); |
- |
- if (pending_seek_) { |
- DVLOG(1) << __FUNCTION__ << "processing pending seek"; |
- DCHECK(doing_browser_seek_); |
- pending_seek_ = false; |
- SeekTo(pending_seek_time_); |
- return; |
- } |
- |
- // It is possible that a browser seek to I-frame had to seek to a buffered |
- // I-frame later than the requested one due to data removal or GC. Update |
- // player clock to the actual seek target. |
- if (doing_browser_seek_) { |
- DCHECK(actual_browser_seek_time != kNoTimestamp()); |
- base::TimeDelta seek_time = actual_browser_seek_time; |
- // A browser seek must not jump into the past. Ideally, it seeks to the |
- // requested time, but it might jump into the future. |
- DCHECK(seek_time >= GetCurrentTime()); |
- DVLOG(1) << __FUNCTION__ << " : setting clock to actual browser seek time: " |
- << seek_time.InSecondsF(); |
- interpolator_.SetBounds(seek_time, seek_time); |
- audio_decoder_job_->SetBaseTimestamp(seek_time); |
- } else { |
- DCHECK(actual_browser_seek_time == kNoTimestamp()); |
- } |
- |
- base::TimeDelta current_time = GetCurrentTime(); |
- // TODO(qinmin): Simplify the logic by using |start_presentation_timestamp_| |
- // to preroll media decoder jobs. Currently |start_presentation_timestamp_| |
- // is calculated from decoder output, while preroll relies on the access |
- // unit's timestamp. There are some differences between the two. |
- preroll_timestamp_ = current_time; |
- if (HasAudio()) |
- audio_decoder_job_->BeginPrerolling(preroll_timestamp_); |
- if (HasVideo()) |
- video_decoder_job_->BeginPrerolling(preroll_timestamp_); |
- prerolling_ = true; |
- |
- if (!doing_browser_seek_) |
- manager()->OnSeekComplete(player_id(), current_time); |
- |
- ProcessPendingEvents(); |
-} |
- |
-void MediaSourcePlayer::UpdateTimestamps( |
- base::TimeDelta current_presentation_timestamp, |
- base::TimeDelta max_presentation_timestamp) { |
- interpolator_.SetBounds(current_presentation_timestamp, |
- max_presentation_timestamp); |
- manager()->OnTimeUpdate(player_id(), |
- GetCurrentTime(), |
- base::TimeTicks::Now()); |
-} |
- |
-void MediaSourcePlayer::ProcessPendingEvents() { |
- DVLOG(1) << __FUNCTION__ << " : 0x" << std::hex << pending_event_; |
- // Wait for all the decoding jobs to finish before processing pending tasks. |
- if (video_decoder_job_->is_decoding()) { |
- DVLOG(1) << __FUNCTION__ << " : A video job is still decoding."; |
- return; |
- } |
- |
- if (audio_decoder_job_->is_decoding()) { |
- DVLOG(1) << __FUNCTION__ << " : An audio job is still decoding."; |
- return; |
- } |
- |
- if (IsEventPending(PREFETCH_DONE_EVENT_PENDING)) { |
- DVLOG(1) << __FUNCTION__ << " : PREFETCH_DONE still pending."; |
- return; |
- } |
- |
- if (IsEventPending(SEEK_EVENT_PENDING)) { |
- DVLOG(1) << __FUNCTION__ << " : Handling SEEK_EVENT"; |
- ClearDecodingData(); |
- audio_decoder_job_->SetBaseTimestamp(GetCurrentTime()); |
- demuxer_->RequestDemuxerSeek(GetCurrentTime(), doing_browser_seek_); |
- return; |
- } |
- |
- if (IsEventPending(DECODER_CREATION_EVENT_PENDING)) { |
- // Don't continue if one of the decoder is not created. |
- if (is_waiting_for_audio_decoder_ || is_waiting_for_video_decoder_) |
- return; |
- ClearPendingEvent(DECODER_CREATION_EVENT_PENDING); |
- } |
- |
- if (IsEventPending(PREFETCH_REQUEST_EVENT_PENDING)) { |
- DVLOG(1) << __FUNCTION__ << " : Handling PREFETCH_REQUEST_EVENT."; |
- |
- int count = (AudioFinished() ? 0 : 1) + (VideoFinished() ? 0 : 1); |
- |
- // It is possible that all streams have finished decode, yet starvation |
- // occurred during the last stream's EOS decode. In this case, prefetch is a |
- // no-op. |
- ClearPendingEvent(PREFETCH_REQUEST_EVENT_PENDING); |
- if (count == 0) |
- return; |
- |
- SetPendingEvent(PREFETCH_DONE_EVENT_PENDING); |
- base::Closure barrier = BarrierClosure( |
- count, base::Bind(&MediaSourcePlayer::OnPrefetchDone, weak_this_)); |
- |
- if (!AudioFinished()) |
- audio_decoder_job_->Prefetch(barrier); |
- |
- if (!VideoFinished()) |
- video_decoder_job_->Prefetch(barrier); |
- |
- return; |
- } |
- |
- DCHECK_EQ(pending_event_, NO_EVENT_PENDING); |
- |
- // Now that all pending events have been handled, resume decoding if we are |
- // still playing. |
- if (playing_) |
- StartInternal(); |
-} |
- |
-void MediaSourcePlayer::MediaDecoderCallback( |
- bool is_audio, MediaCodecStatus status, |
- base::TimeDelta current_presentation_timestamp, |
- base::TimeDelta max_presentation_timestamp) { |
- DVLOG(1) << __FUNCTION__ << ": " << is_audio << ", " << status; |
- |
- // TODO(xhwang): Drop IntToString() when http://crbug.com/303899 is fixed. |
- if (is_audio) { |
- TRACE_EVENT_ASYNC_END1("media", |
- "MediaSourcePlayer::DecodeMoreAudio", |
- audio_decoder_job_.get(), |
- "MediaCodecStatus", |
- base::IntToString(status)); |
- } else { |
- TRACE_EVENT_ASYNC_END1("media", |
- "MediaSourcePlayer::DecodeMoreVideo", |
- video_decoder_job_.get(), |
- "MediaCodecStatus", |
- base::IntToString(status)); |
- } |
- |
- // Let tests hook the completion of this decode cycle. |
- if (!decode_callback_for_testing_.is_null()) |
- base::ResetAndReturn(&decode_callback_for_testing_).Run(); |
- |
- bool is_clock_manager = is_audio || !HasAudio(); |
- |
- if (is_clock_manager) |
- decoder_starvation_callback_.Cancel(); |
- |
- if (status == MEDIA_CODEC_ERROR) { |
- DVLOG(1) << __FUNCTION__ << " : decode error"; |
- Release(); |
- manager()->OnError(player_id(), MEDIA_ERROR_DECODE); |
- return; |
- } |
- |
- DCHECK(!IsEventPending(PREFETCH_DONE_EVENT_PENDING)); |
- |
- // Let |SEEK_EVENT_PENDING| (the highest priority event outside of |
- // |PREFETCH_DONE_EVENT_PENDING|) preempt output EOS detection here. Process |
- // any other pending events only after handling EOS detection. |
- if (IsEventPending(SEEK_EVENT_PENDING)) { |
- ProcessPendingEvents(); |
- return; |
- } |
- |
- if ((status == MEDIA_CODEC_OK || status == MEDIA_CODEC_INPUT_END_OF_STREAM) && |
- is_clock_manager && current_presentation_timestamp != kNoTimestamp()) { |
- UpdateTimestamps(current_presentation_timestamp, |
- max_presentation_timestamp); |
- } |
- |
- if (status == MEDIA_CODEC_OUTPUT_END_OF_STREAM) { |
- PlaybackCompleted(is_audio); |
- if (is_clock_manager) |
- interpolator_.StopInterpolating(); |
- } |
- |
- if (pending_event_ != NO_EVENT_PENDING) { |
- ProcessPendingEvents(); |
- return; |
- } |
- |
- if (status == MEDIA_CODEC_OUTPUT_END_OF_STREAM) { |
- if (is_audio) |
- SetAudible(false); |
- return; |
- } |
- |
- if (!playing_) { |
- if (is_clock_manager) |
- interpolator_.StopInterpolating(); |
- |
- if (is_audio) |
- SetAudible(false); |
- return; |
- } |
- |
- if (status == MEDIA_CODEC_NO_KEY) { |
- if (key_added_while_decode_pending_) { |
- DVLOG(2) << __FUNCTION__ << ": Key was added during decoding."; |
- ResumePlaybackAfterKeyAdded(); |
- } else { |
- if (is_audio) |
- SetAudible(false); |
- |
- is_waiting_for_key_ = true; |
- manager()->OnWaitingForDecryptionKey(player_id()); |
- } |
- return; |
- } |
- |
- // If |key_added_while_decode_pending_| is true and both audio and video |
- // decoding succeeded, we should clear |key_added_while_decode_pending_| here. |
- // But that would add more complexity into this function. If we don't clear it |
- // here, the worst case would be we call ResumePlaybackAfterKeyAdded() when |
- // we don't really have a new key. This should rarely happen and the |
- // performance impact should be pretty small. |
- // TODO(qinmin/xhwang): This class is complicated because we handle both audio |
- // and video in one file. If we separate them, we should be able to remove a |
- // lot of duplication. |
- |
- // If the status is MEDIA_CODEC_ABORT, stop decoding new data. The player is |
- // in the middle of a seek or stop event and needs to wait for the IPCs to |
- // come. |
- if (status == MEDIA_CODEC_ABORT) { |
- if (is_audio) |
- SetAudible(false); |
- return; |
- } |
- |
- if (prerolling_ && IsPrerollFinished(is_audio)) { |
- if (IsPrerollFinished(!is_audio)) { |
- prerolling_ = false; |
- StartInternal(); |
- } |
- return; |
- } |
- |
- // We successfully decoded a frame and going to the next one. |
- // Set the audible state. |
- if (is_audio) { |
- bool is_audible = !prerolling_ && audio_decoder_job_->volume() > 0; |
- SetAudible(is_audible); |
- } |
- |
- if (is_clock_manager) { |
- // If we have a valid timestamp, start the starvation callback. Otherwise, |
- // reset the |start_time_ticks_| so that the next frame will not suffer |
- // from the decoding delay caused by the current frame. |
- if (current_presentation_timestamp != kNoTimestamp()) |
- StartStarvationCallback(current_presentation_timestamp, |
- max_presentation_timestamp); |
- else |
- start_time_ticks_ = base::TimeTicks::Now(); |
- } |
- |
- if (is_audio) |
- DecodeMoreAudio(); |
- else |
- DecodeMoreVideo(); |
-} |
- |
-bool MediaSourcePlayer::IsPrerollFinished(bool is_audio) const { |
- if (is_audio) |
- return !HasAudio() || !audio_decoder_job_->prerolling(); |
- return !HasVideo() || !video_decoder_job_->prerolling(); |
-} |
- |
-void MediaSourcePlayer::DecodeMoreAudio() { |
- DVLOG(1) << __FUNCTION__; |
- DCHECK(!audio_decoder_job_->is_decoding()); |
- DCHECK(!AudioFinished()); |
- |
- MediaDecoderJob::MediaDecoderJobStatus status = audio_decoder_job_->Decode( |
- start_time_ticks_, |
- start_presentation_timestamp_, |
- base::Bind(&MediaSourcePlayer::MediaDecoderCallback, weak_this_, true)); |
- |
- switch (status) { |
- case MediaDecoderJob::STATUS_SUCCESS: |
- TRACE_EVENT_ASYNC_BEGIN0("media", "MediaSourcePlayer::DecodeMoreAudio", |
- audio_decoder_job_.get()); |
- break; |
- case MediaDecoderJob::STATUS_KEY_FRAME_REQUIRED: |
- NOTREACHED(); |
- break; |
- case MediaDecoderJob::STATUS_FAILURE: |
- is_waiting_for_audio_decoder_ = true; |
- if (!IsEventPending(DECODER_CREATION_EVENT_PENDING)) |
- SetPendingEvent(DECODER_CREATION_EVENT_PENDING); |
- break; |
- } |
-} |
- |
-void MediaSourcePlayer::DecodeMoreVideo() { |
- DVLOG(1) << __FUNCTION__; |
- DCHECK(!video_decoder_job_->is_decoding()); |
- DCHECK(!VideoFinished()); |
- |
- MediaDecoderJob::MediaDecoderJobStatus status = video_decoder_job_->Decode( |
- start_time_ticks_, |
- start_presentation_timestamp_, |
- base::Bind(&MediaSourcePlayer::MediaDecoderCallback, weak_this_, |
- false)); |
- |
- switch (status) { |
- case MediaDecoderJob::STATUS_SUCCESS: |
- TRACE_EVENT_ASYNC_BEGIN0("media", "MediaSourcePlayer::DecodeMoreVideo", |
- video_decoder_job_.get()); |
- break; |
- case MediaDecoderJob::STATUS_KEY_FRAME_REQUIRED: |
- BrowserSeekToCurrentTime(); |
- break; |
- case MediaDecoderJob::STATUS_FAILURE: |
- is_waiting_for_video_decoder_ = true; |
- if (!IsEventPending(DECODER_CREATION_EVENT_PENDING)) |
- SetPendingEvent(DECODER_CREATION_EVENT_PENDING); |
- break; |
- } |
-} |
- |
-void MediaSourcePlayer::PlaybackCompleted(bool is_audio) { |
- DVLOG(1) << __FUNCTION__ << "(" << is_audio << ")"; |
- |
- if (AudioFinished() && VideoFinished()) { |
- playing_ = false; |
- start_time_ticks_ = base::TimeTicks(); |
- manager()->OnPlaybackComplete(player_id()); |
- } |
-} |
- |
-void MediaSourcePlayer::ClearDecodingData() { |
- DVLOG(1) << __FUNCTION__; |
- audio_decoder_job_->Flush(); |
- video_decoder_job_->Flush(); |
- start_time_ticks_ = base::TimeTicks(); |
-} |
- |
-bool MediaSourcePlayer::HasVideo() const { |
- return video_decoder_job_->HasStream(); |
-} |
- |
-bool MediaSourcePlayer::HasAudio() const { |
- return audio_decoder_job_->HasStream(); |
-} |
- |
-bool MediaSourcePlayer::AudioFinished() { |
- return audio_decoder_job_->OutputEOSReached() || !HasAudio(); |
-} |
- |
-bool MediaSourcePlayer::VideoFinished() { |
- return video_decoder_job_->OutputEOSReached() || !HasVideo(); |
-} |
- |
-void MediaSourcePlayer::OnDecoderStarved() { |
- DVLOG(1) << __FUNCTION__; |
- |
- if (HasAudio()) { |
- // If the starvation timer fired but there are no encoded frames |
- // in the queue we believe the demuxer (i.e. renderer process) froze. |
- if (!audio_decoder_job_->HasData()) |
- SetAudible(false); |
- } |
- |
- SetPendingEvent(PREFETCH_REQUEST_EVENT_PENDING); |
- ProcessPendingEvents(); |
-} |
- |
-void MediaSourcePlayer::StartStarvationCallback( |
- base::TimeDelta current_presentation_timestamp, |
- base::TimeDelta max_presentation_timestamp) { |
- // 20ms was chosen because it is the typical size of a compressed audio frame. |
- // Anything smaller than this would likely cause unnecessary cycling in and |
- // out of the prefetch state. |
- const base::TimeDelta kMinStarvationTimeout = |
- base::TimeDelta::FromMilliseconds(20); |
- |
- base::TimeDelta current_timestamp = GetCurrentTime(); |
- base::TimeDelta timeout; |
- if (HasAudio()) { |
- timeout = max_presentation_timestamp - current_timestamp; |
- } else { |
- DCHECK(current_timestamp <= current_presentation_timestamp); |
- |
- // For video only streams, fps can be estimated from the difference |
- // between the previous and current presentation timestamps. The |
- // previous presentation timestamp is equal to current_timestamp. |
- // TODO(qinmin): determine whether 2 is a good coefficient for estimating |
- // video frame timeout. |
- timeout = 2 * (current_presentation_timestamp - current_timestamp); |
- } |
- |
- timeout = std::max(timeout, kMinStarvationTimeout); |
- |
- decoder_starvation_callback_.Reset( |
- base::Bind(&MediaSourcePlayer::OnDecoderStarved, weak_this_)); |
- base::MessageLoop::current()->PostDelayedTask( |
- FROM_HERE, decoder_starvation_callback_.callback(), timeout); |
-} |
- |
-void MediaSourcePlayer::OnPrefetchDone() { |
- DVLOG(1) << __FUNCTION__; |
- DCHECK(!audio_decoder_job_->is_decoding()); |
- DCHECK(!video_decoder_job_->is_decoding()); |
- |
- // A previously posted OnPrefetchDone() could race against a Release(). If |
- // Release() won the race, we should no longer have decoder jobs. |
- // TODO(qinmin/wolenetz): Maintain channel state to not double-request data |
- // or drop data received across Release()+Start(). See http://crbug.com/306314 |
- // and http://crbug.com/304234. |
- if (!IsEventPending(PREFETCH_DONE_EVENT_PENDING)) { |
- DVLOG(1) << __FUNCTION__ << " : aborting"; |
- return; |
- } |
- |
- ClearPendingEvent(PREFETCH_DONE_EVENT_PENDING); |
- |
- if (pending_event_ != NO_EVENT_PENDING) { |
- ProcessPendingEvents(); |
- return; |
- } |
- |
- if (!playing_) |
- return; |
- |
- start_time_ticks_ = base::TimeTicks::Now(); |
- start_presentation_timestamp_ = GetCurrentTime(); |
- if (!interpolator_.interpolating()) |
- interpolator_.StartInterpolating(); |
- |
- if (!AudioFinished()) |
- DecodeMoreAudio(); |
- |
- if (!VideoFinished()) |
- DecodeMoreVideo(); |
-} |
- |
-void MediaSourcePlayer::OnDemuxerConfigsChanged() { |
- manager()->OnMediaMetadataChanged( |
- player_id(), duration_, GetVideoWidth(), GetVideoHeight(), true); |
-} |
- |
-const char* MediaSourcePlayer::GetEventName(PendingEventFlags event) { |
- // Please keep this in sync with PendingEventFlags. |
- static const char* kPendingEventNames[] = { |
- "PREFETCH_DONE", |
- "SEEK", |
- "DECODER_CREATION", |
- "PREFETCH_REQUEST", |
- }; |
- |
- int mask = 1; |
- for (size_t i = 0; i < arraysize(kPendingEventNames); ++i, mask <<= 1) { |
- if (event & mask) |
- return kPendingEventNames[i]; |
- } |
- |
- return "UNKNOWN"; |
-} |
- |
-bool MediaSourcePlayer::IsEventPending(PendingEventFlags event) const { |
- return pending_event_ & event; |
-} |
- |
-void MediaSourcePlayer::SetPendingEvent(PendingEventFlags event) { |
- DVLOG(1) << __FUNCTION__ << "(" << GetEventName(event) << ")"; |
- DCHECK_NE(event, NO_EVENT_PENDING); |
- DCHECK(!IsEventPending(event)); |
- |
- pending_event_ |= event; |
-} |
- |
-void MediaSourcePlayer::ClearPendingEvent(PendingEventFlags event) { |
- DVLOG(1) << __FUNCTION__ << "(" << GetEventName(event) << ")"; |
- DCHECK_NE(event, NO_EVENT_PENDING); |
- DCHECK(IsEventPending(event)) << GetEventName(event); |
- |
- pending_event_ &= ~event; |
-} |
- |
-void MediaSourcePlayer::RetryDecoderCreation(bool audio, bool video) { |
- if (audio) |
- is_waiting_for_audio_decoder_ = false; |
- if (video) |
- is_waiting_for_video_decoder_ = false; |
- if (IsEventPending(DECODER_CREATION_EVENT_PENDING)) |
- ProcessPendingEvents(); |
-} |
- |
-void MediaSourcePlayer::OnKeyAdded() { |
- DVLOG(1) << __FUNCTION__; |
- |
- if (is_waiting_for_key_) { |
- ResumePlaybackAfterKeyAdded(); |
- return; |
- } |
- |
- if ((audio_decoder_job_->is_content_encrypted() && |
- audio_decoder_job_->is_decoding()) || |
- (video_decoder_job_->is_content_encrypted() && |
- video_decoder_job_->is_decoding())) { |
- DVLOG(1) << __FUNCTION__ << ": " << "Key added during pending decode."; |
- key_added_while_decode_pending_ = true; |
- } |
-} |
- |
-void MediaSourcePlayer::ResumePlaybackAfterKeyAdded() { |
- DVLOG(1) << __FUNCTION__; |
- DCHECK(is_waiting_for_key_ || key_added_while_decode_pending_); |
- |
- is_waiting_for_key_ = false; |
- key_added_while_decode_pending_ = false; |
- |
- // StartInternal() will trigger a prefetch, where in most cases we'll just |
- // use previously received data. |
- if (playing_) |
- StartInternal(); |
-} |
- |
-void MediaSourcePlayer::OnCdmUnset() { |
DVLOG(1) << __FUNCTION__; |
- DCHECK(drm_bridge_); |
- // TODO(xhwang): Currently this is only called during teardown. Support full |
- // detachment of CDM during playback. This will be needed when we start to |
- // support setMediaKeys(0) (see http://crbug.com/330324), or when we release |
- // MediaDrm when the video is paused, or when the device goes to sleep (see |
- // http://crbug.com/272421). |
- audio_decoder_job_->SetDrmBridge(NULL); |
- video_decoder_job_->SetDrmBridge(NULL); |
- cdm_registration_id_ = 0; |
- drm_bridge_ = NULL; |
} |
} // namespace media |