Chromium Code Reviews| 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..25118b32e9916b4eb2ea1f5b6ac3b1228c6d1760 100644 |
| --- a/media/base/android/media_source_player.cc |
| +++ b/media/base/android/media_source_player.cc |
| @@ -1,27 +1,34 @@ |
| -// 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" |
| namespace media { |
| +class MediaThread : public base::Thread { |
| + public: |
| + MediaThread() : base::Thread("MediaThread") { |
| + 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, |
| @@ -33,810 +40,146 @@ MediaSourcePlayer::MediaSourcePlayer( |
| 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()))); |
| + weak_factory_(this) |
| +{ |
| + DVLOG(1) << "MediaSourcePlayer::Player: player_id:" << player_id; |
| + |
| demuxer_->Initialize(this); |
| - interpolator_.SetUpperBound(base::TimeDelta()); |
| weak_this_ = weak_factory_.GetWeakPtr(); |
|
qinmin
2015/04/23 18:48:38
weak_ptr is bind to the UI thread here.
|
| } |
| -MediaSourcePlayer::~MediaSourcePlayer() { |
| - Release(); |
| - DCHECK_EQ(!drm_bridge_, !cdm_registration_id_); |
| - if (drm_bridge_) { |
| - drm_bridge_->UnregisterPlayer(cdm_registration_id_); |
| - cdm_registration_id_ = 0; |
| - } |
| +MediaSourcePlayer::~MediaSourcePlayer() |
| +{ |
| + // Called on UI thread, we need to release asynchronously |
| } |
| void MediaSourcePlayer::SetVideoSurface(gfx::ScopedJavaSurface surface) { |
| DVLOG(1) << __FUNCTION__; |
| - if (!video_decoder_job_->SetVideoSurface(surface.Pass())) |
| - return; |
| - // Retry video decoder creation. |
| - RetryDecoderCreation(false, true); |
| -} |
| - |
| -void MediaSourcePlayer::ScheduleSeekEventAndStopDecoding( |
| - base::TimeDelta seek_time) { |
| - DVLOG(1) << __FUNCTION__ << "(" << seek_time.InSecondsF() << ")"; |
| - DCHECK(!IsEventPending(SEEK_EVENT_PENDING)); |
| - pending_seek_ = false; |
| - |
| - interpolator_.SetBounds(seek_time, seek_time); |
| - |
| - if (audio_decoder_job_->is_decoding()) |
| - audio_decoder_job_->StopDecode(); |
| - if (video_decoder_job_->is_decoding()) |
| - video_decoder_job_->StopDecode(); |
| - |
| - SetPendingEvent(SEEK_EVENT_PENDING); |
| - ProcessPendingEvents(); |
| + GetMediaTaskRunner()->PostTask( |
| + FROM_HERE, base::Bind(&MediaSourcePlayer::SetVideoSurfaceInternal, |
| + weak_this_, base::Passed(&surface))); |
|
qinmin
2015/04/23 18:48:38
and the weak ptr is now being used on the media th
timav
2015/04/23 23:06:12
Yes, I learned this later, while running real impl
|
| } |
| -void MediaSourcePlayer::BrowserSeekToCurrentTime() { |
| +void MediaSourcePlayer::Start() { |
| DVLOG(1) << __FUNCTION__; |
| - DCHECK(!IsEventPending(SEEK_EVENT_PENDING)); |
| - doing_browser_seek_ = true; |
| - ScheduleSeekEventAndStopDecoding(GetCurrentTime()); |
| -} |
| - |
| -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()); |
| + GetMediaTaskRunner()->PostTask( |
| + FROM_HERE, base::Bind(&MediaSourcePlayer::StartInternal, weak_this_)); |
| } |
| -void MediaSourcePlayer::Start() { |
| +void MediaSourcePlayer::Pause(bool /* is_media_related_action */) { |
| DVLOG(1) << __FUNCTION__; |
| - playing_ = true; |
| + GetMediaTaskRunner()->PostTask( |
| + FROM_HERE, base::Bind(&MediaSourcePlayer::PauseInternal, weak_this_)); |
| +} |
| + |
| +void MediaSourcePlayer::SeekTo(base::TimeDelta timestamp) { |
| + DVLOG(1) << __FUNCTION__ << " " << timestamp; |
| - StartInternal(); |
| + GetMediaTaskRunner()->PostTask( |
| + FROM_HERE, |
| + base::Bind(&MediaSourcePlayer::SeekToInternal, weak_this_, 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(&MediaSourcePlayer::ReleaseInternal, weak_this_)); |
| } |
| -bool MediaSourcePlayer::IsPlaying() { |
| - return playing_; |
| +void MediaSourcePlayer::SetVolume(double volume) { |
| + DVLOG(1) << __FUNCTION__ << " " << volume; |
| } |
| int MediaSourcePlayer::GetVideoWidth() { |
| - return video_decoder_job_->output_width(); |
| + return 320; |
| } |
| 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 240; |
| } |
| base::TimeDelta MediaSourcePlayer::GetCurrentTime() { |
| - return std::min(interpolator_.GetInterpolatedTime(), duration_); |
| + return base::TimeDelta(); |
| } |
| 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 base::TimeDelta(); |
| } |
| -void MediaSourcePlayer::SetVolume(double volume) { |
| - audio_decoder_job_->SetVolume(volume); |
| +bool MediaSourcePlayer::IsPlaying() { |
| + return false; |
| } |
| bool MediaSourcePlayer::CanPause() { |
| - return Seekable(); |
| + return false; |
| } |
| bool MediaSourcePlayer::CanSeekForward() { |
| - return Seekable(); |
| + return false; |
| } |
| bool MediaSourcePlayer::CanSeekBackward() { |
| - return Seekable(); |
| + return false; |
| } |
| 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 true; |
| } |
| 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() { |
| +void MediaSourcePlayer::OnDemuxerConfigsAvailable( |
| + const DemuxerConfigs& params) { |
| 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()); |
| - } |
| + // TODO: Post on UI thread |
| + manager()->OnMediaMetadataChanged( |
| + player_id(), base::TimeDelta::FromMilliseconds(100000), |
| + GetVideoWidth(), GetVideoHeight(), true); |
| } |
| -void MediaSourcePlayer::ClearDecodingData() { |
| +void MediaSourcePlayer::OnDemuxerDataAvailable(const DemuxerData& params) { |
| 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(); |
| +void MediaSourcePlayer::OnDemuxerSeekDone( |
| + base::TimeDelta actual_browser_seek_time) { |
| + DVLOG(1) << __FUNCTION__ << " actual_time:" << actual_browser_seek_time; |
| } |
| -bool MediaSourcePlayer::HasAudio() const { |
| - return audio_decoder_job_->HasStream(); |
| +void MediaSourcePlayer::OnDemuxerDurationChanged(base::TimeDelta duration) { |
| + DVLOG(1) << __FUNCTION__ << " duration:" << duration; |
| } |
| -bool MediaSourcePlayer::AudioFinished() { |
| - return audio_decoder_job_->OutputEOSReached() || !HasAudio(); |
| -} |
| -bool MediaSourcePlayer::VideoFinished() { |
| - return video_decoder_job_->OutputEOSReached() || !HasVideo(); |
| -} |
| +// Events from UI, called on Media thread |
| -void MediaSourcePlayer::OnDecoderStarved() { |
| +void MediaSourcePlayer::SetVideoSurfaceInternal( |
| + gfx::ScopedJavaSurface surface) { |
| 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() { |
| +void MediaSourcePlayer::StartInternal() { |
| 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() { |
| +void MediaSourcePlayer::PauseInternal() { |
| 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::SeekToInternal(base::TimeDelta timestamp) { |
| + DVLOG(1) << __FUNCTION__ << " " << timestamp; |
| } |
| -void MediaSourcePlayer::OnCdmUnset() { |
| +void MediaSourcePlayer::ReleaseInternal() { |
| 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 |