summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--content/browser/media/android/browser_media_player_manager.cc15
-rw-r--r--content/browser/media/android/browser_media_player_manager.h1
-rw-r--r--media/base/android/BUILD.gn1
-rw-r--r--media/base/android/media_codec_player.cc613
-rw-r--r--media/base/android/media_codec_player.h210
-rw-r--r--media/base/android/media_codec_player_unittest.cc424
-rw-r--r--media/base/android/media_player_android.cc4
-rw-r--r--media/base/android/media_player_android.h23
-rw-r--r--media/media.gyp1
9 files changed, 1195 insertions, 97 deletions
diff --git a/content/browser/media/android/browser_media_player_manager.cc b/content/browser/media/android/browser_media_player_manager.cc
index 3ea4de8..645a38f 100644
--- a/content/browser/media/android/browser_media_player_manager.cc
+++ b/content/browser/media/android/browser_media_player_manager.cc
@@ -135,7 +135,6 @@ ContentViewCore* BrowserMediaPlayerManager::GetContentViewCore() const {
MediaPlayerAndroid* BrowserMediaPlayerManager::CreateMediaPlayer(
const MediaPlayerHostMsg_Initialize_Params& media_player_params,
bool hide_url_log,
- MediaPlayerManager* manager,
BrowserDemuxerAndroid* demuxer) {
switch (media_player_params.type) {
case MEDIA_PLAYER_TYPE_URL: {
@@ -146,22 +145,20 @@ MediaPlayerAndroid* BrowserMediaPlayerManager::CreateMediaPlayer(
media_player_params.first_party_for_cookies,
user_agent,
hide_url_log,
- manager,
+ this,
base::Bind(&BrowserMediaPlayerManager::OnMediaResourcesRequested,
weak_ptr_factory_.GetWeakPtr()),
media_player_params.frame_url,
media_player_params.allow_credentials);
- BrowserMediaPlayerManager* browser_media_player_manager =
- static_cast<BrowserMediaPlayerManager*>(manager);
ContentViewCoreImpl* content_view_core_impl =
static_cast<ContentViewCoreImpl*>(ContentViewCore::FromWebContents(
- browser_media_player_manager->web_contents_));
+ web_contents_));
if (!content_view_core_impl) {
// May reach here due to prerendering. Don't extract the metadata
// since it is expensive.
// TODO(qinmin): extract the metadata once the user decided to load
// the page.
- browser_media_player_manager->OnMediaMetadataChanged(
+ OnMediaMetadataChanged(
media_player_params.player_id, base::TimeDelta(), 0, 0, false);
} else if (!content_view_core_impl->ShouldBlockMediaRequest(
media_player_params.url)) {
@@ -175,7 +172,7 @@ MediaPlayerAndroid* BrowserMediaPlayerManager::CreateMediaPlayer(
HasSwitch(switches::kEnableMediaThreadForMediaPlayback)) {
return new MediaCodecPlayer(
media_player_params.player_id,
- manager,
+ weak_ptr_factory_.GetWeakPtr(),
base::Bind(&BrowserMediaPlayerManager::OnMediaResourcesRequested,
weak_ptr_factory_.GetWeakPtr()),
demuxer->CreateDemuxer(media_player_params.demuxer_client_id),
@@ -183,7 +180,7 @@ MediaPlayerAndroid* BrowserMediaPlayerManager::CreateMediaPlayer(
} else {
return new MediaSourcePlayer(
media_player_params.player_id,
- manager,
+ this,
base::Bind(&BrowserMediaPlayerManager::OnMediaResourcesRequested,
weak_ptr_factory_.GetWeakPtr()),
demuxer->CreateDemuxer(media_player_params.demuxer_client_id),
@@ -535,9 +532,7 @@ void BrowserMediaPlayerManager::OnInitialize(
web_contents()->GetRenderProcessHost());
MediaPlayerAndroid* player =
CreateMediaPlayer(media_player_params,
-
host->GetBrowserContext()->IsOffTheRecord(),
- this,
host->browser_demuxer_android().get());
if (!player)
diff --git a/content/browser/media/android/browser_media_player_manager.h b/content/browser/media/android/browser_media_player_manager.h
index 5a20002..9ec5e77 100644
--- a/content/browser/media/android/browser_media_player_manager.h
+++ b/content/browser/media/android/browser_media_player_manager.h
@@ -165,7 +165,6 @@ class CONTENT_EXPORT BrowserMediaPlayerManager
media::MediaPlayerAndroid* CreateMediaPlayer(
const MediaPlayerHostMsg_Initialize_Params& media_player_params,
bool hide_url_log,
- media::MediaPlayerManager* manager,
BrowserDemuxerAndroid* demuxer);
// MediaPlayerAndroid must call this before it is going to decode
diff --git a/media/base/android/BUILD.gn b/media/base/android/BUILD.gn
index 5db57a3..7c87a7f 100644
--- a/media/base/android/BUILD.gn
+++ b/media/base/android/BUILD.gn
@@ -78,6 +78,7 @@ source_set("unittests") {
"access_unit_queue_unittest.cc",
"media_codec_bridge_unittest.cc",
"media_codec_decoder_unittest.cc",
+ "media_codec_player_unittest.cc",
"media_drm_bridge_unittest.cc",
"media_source_player_unittest.cc",
"test_data_factory.cc",
diff --git a/media/base/android/media_codec_player.cc b/media/base/android/media_codec_player.cc
index ca6f01e..aa05fdf 100644
--- a/media/base/android/media_codec_player.cc
+++ b/media/base/android/media_codec_player.cc
@@ -4,21 +4,27 @@
#include "media/base/android/media_codec_player.h"
+#include "base/barrier_closure.h"
#include "base/bind.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/thread_task_runner_handle.h"
-
-#define RUN_ON_MEDIA_THREAD(METHOD, ...) \
- do { \
- if (!GetMediaTaskRunner()->BelongsToCurrentThread()) { \
- GetMediaTaskRunner()->PostTask( \
- FROM_HERE, \
- base::Bind(&MediaCodecPlayer:: METHOD, weak_this_, ##__VA_ARGS__)); \
- return; \
- } \
- } while(0)
-
+#include "base/threading/thread.h"
+#include "media/base/android/media_codec_audio_decoder.h"
+#include "media/base/android/media_codec_video_decoder.h"
+#include "media/base/android/media_player_manager.h"
+#include "media/base/buffers.h"
+
+#define RUN_ON_MEDIA_THREAD(METHOD, ...) \
+ do { \
+ if (!GetMediaTaskRunner()->BelongsToCurrentThread()) { \
+ DCHECK(ui_task_runner_->BelongsToCurrentThread()); \
+ GetMediaTaskRunner()->PostTask( \
+ FROM_HERE, base::Bind(&MediaCodecPlayer::METHOD, media_weak_this_, \
+ ##__VA_ARGS__)); \
+ return; \
+ } \
+ } while (0)
namespace media {
@@ -42,53 +48,74 @@ scoped_refptr<base::SingleThreadTaskRunner> GetMediaTaskRunner() {
MediaCodecPlayer::MediaCodecPlayer(
int player_id,
- MediaPlayerManager* manager,
+ base::WeakPtr<MediaPlayerManager> manager,
const RequestMediaResourcesCB& request_media_resources_cb,
scoped_ptr<DemuxerAndroid> demuxer,
const GURL& frame_url)
: MediaPlayerAndroid(player_id,
- manager,
+ manager.get(),
request_media_resources_cb,
frame_url),
ui_task_runner_(base::ThreadTaskRunnerHandle::Get()),
demuxer_(demuxer.Pass()),
- weak_factory_(this) {
- // UI thread
+ state_(STATE_PAUSED),
+ interpolator_(&default_tick_clock_),
+ pending_start_(false),
+ media_weak_factory_(this) {
DCHECK(ui_task_runner_->BelongsToCurrentThread());
DVLOG(1) << "MediaCodecPlayer::MediaCodecPlayer: player_id:" << player_id;
- weak_this_ = weak_factory_.GetWeakPtr();
+ request_resources_cb_ = base::Bind(request_media_resources_cb_, player_id);
+
+ completion_cb_ =
+ base::Bind(&MediaPlayerManager::OnPlaybackComplete, manager, player_id);
+ attach_listener_cb_ = base::Bind(&MediaPlayerAndroid::AttachListener,
+ WeakPtrForUIThread(), nullptr);
+ detach_listener_cb_ =
+ base::Bind(&MediaPlayerAndroid::DetachListener, WeakPtrForUIThread());
+ metadata_changed_cb_ = base::Bind(&MediaPlayerAndroid::OnMediaMetadataChanged,
+ WeakPtrForUIThread());
+ time_update_cb_ =
+ base::Bind(&MediaPlayerAndroid::OnTimeUpdate, WeakPtrForUIThread());
+
+ media_weak_this_ = media_weak_factory_.GetWeakPtr();
// Finish initializaton on Media thread
GetMediaTaskRunner()->PostTask(
- FROM_HERE, base::Bind(&MediaCodecPlayer::Initialize, weak_this_));
+ FROM_HERE, base::Bind(&MediaCodecPlayer::Initialize, media_weak_this_));
}
MediaCodecPlayer::~MediaCodecPlayer()
{
- // Media thread
DVLOG(1) << "MediaCodecPlayer::~MediaCodecPlayer";
DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
}
void MediaCodecPlayer::Initialize() {
- // Media thread
DVLOG(1) << __FUNCTION__;
DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
+ interpolator_.SetUpperBound(base::TimeDelta());
+
+ CreateDecoders();
+
+ // This call might in turn call MediaCodecPlayer::OnDemuxerConfigsAvailable()
+ // which propagates configs into decoders. Therefore CreateDecoders() should
+ // be called first.
demuxer_->Initialize(this);
}
-// MediaPlayerAndroid implementation.
+// The implementation of MediaPlayerAndroid interface.
void MediaCodecPlayer::DeleteOnCorrectThread() {
- // UI thread
DVLOG(1) << __FUNCTION__;
DCHECK(ui_task_runner_->BelongsToCurrentThread());
- // The listener-related portion of the base class has to be
- // destroyed on UI thread.
+ DetachListener();
+
+ // The base class part that deals with MediaPlayerListener
+ // has to be destroyed on UI thread.
DestroyListenerOnUIThread();
// Post deletion onto Media thread
@@ -98,124 +125,142 @@ void MediaCodecPlayer::DeleteOnCorrectThread() {
void MediaCodecPlayer::SetVideoSurface(gfx::ScopedJavaSurface surface) {
RUN_ON_MEDIA_THREAD(SetVideoSurface, base::Passed(&surface));
- // Media thread
- DVLOG(1) << __FUNCTION__;
+ DVLOG(1) << __FUNCTION__ << (surface.IsEmpty() ? " empty" : " non-empty");
- NOTIMPLEMENTED();
+ // 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());
+
+ if (video_decoder_->HasPendingSurface() &&
+ state_ == STATE_WAITING_FOR_SURFACE) {
+ SetState(STATE_PLAYING);
+ StartPlaybackDecoders();
+ }
}
void MediaCodecPlayer::Start() {
RUN_ON_MEDIA_THREAD(Start);
- // Media thread
DVLOG(1) << __FUNCTION__;
- NOTIMPLEMENTED();
+ switch (state_) {
+ case STATE_PAUSED:
+ if (HasAudio() || HasVideo()) {
+ SetState(STATE_PREFETCHING);
+ StartPrefetchDecoders();
+ } else {
+ SetState(STATE_WAITING_FOR_CONFIG);
+ }
+ break;
+ case STATE_STOPPING:
+ SetPendingStart(true);
+ break;
+ default:
+ // Ignore
+ break;
+ }
}
void MediaCodecPlayer::Pause(bool is_media_related_action) {
RUN_ON_MEDIA_THREAD(Pause, is_media_related_action);
- // Media thread
DVLOG(1) << __FUNCTION__;
- NOTIMPLEMENTED();
+ switch (state_) {
+ case STATE_PREFETCHING:
+ SetState(STATE_PAUSED);
+ StopDecoders();
+ break;
+ case STATE_WAITING_FOR_SURFACE:
+ SetState(STATE_PAUSED);
+ StopDecoders();
+ break;
+ case STATE_PLAYING:
+ SetState(STATE_STOPPING);
+ RequestToStopDecoders();
+ break;
+ default:
+ // Ignore
+ break;
+ }
}
void MediaCodecPlayer::SeekTo(base::TimeDelta timestamp) {
RUN_ON_MEDIA_THREAD(SeekTo, timestamp);
- // Media thread
DVLOG(1) << __FUNCTION__ << " " << timestamp;
-
NOTIMPLEMENTED();
}
void MediaCodecPlayer::Release() {
RUN_ON_MEDIA_THREAD(Release);
- // Media thread
DVLOG(1) << __FUNCTION__;
- NOTIMPLEMENTED();
+ SetState(STATE_PAUSED);
+ ReleaseDecoderResources();
}
void MediaCodecPlayer::SetVolume(double volume) {
RUN_ON_MEDIA_THREAD(SetVolume, volume);
- // Media thread
DVLOG(1) << __FUNCTION__ << " " << volume;
-
- NOTIMPLEMENTED();
+ audio_decoder_->SetVolume(volume);
}
int MediaCodecPlayer::GetVideoWidth() {
- // UI thread
DCHECK(ui_task_runner_->BelongsToCurrentThread());
-
- NOTIMPLEMENTED();
- return 320;
+ return metadata_cache_.video_size.width();
}
int MediaCodecPlayer::GetVideoHeight() {
- // UI thread
DCHECK(ui_task_runner_->BelongsToCurrentThread());
-
- NOTIMPLEMENTED();
- return 240;
+ return metadata_cache_.video_size.height();
}
base::TimeDelta MediaCodecPlayer::GetCurrentTime() {
- // UI thread, Media thread
- NOTIMPLEMENTED();
- return base::TimeDelta();
+ DCHECK(ui_task_runner_->BelongsToCurrentThread());
+ return current_time_cache_;
}
base::TimeDelta MediaCodecPlayer::GetDuration() {
- // UI thread
DCHECK(ui_task_runner_->BelongsToCurrentThread());
-
- NOTIMPLEMENTED();
- return base::TimeDelta();
+ return metadata_cache_.duration;
}
bool MediaCodecPlayer::IsPlaying() {
- // UI thread
DCHECK(ui_task_runner_->BelongsToCurrentThread());
- NOTIMPLEMENTED();
- return false;
+ return state_ == STATE_PLAYING;
}
bool MediaCodecPlayer::CanPause() {
- // UI thread
DCHECK(ui_task_runner_->BelongsToCurrentThread());
NOTIMPLEMENTED();
return false;
}
bool MediaCodecPlayer::CanSeekForward() {
- // UI thread
DCHECK(ui_task_runner_->BelongsToCurrentThread());
NOTIMPLEMENTED();
return false;
}
bool MediaCodecPlayer::CanSeekBackward() {
- // UI thread
DCHECK(ui_task_runner_->BelongsToCurrentThread());
NOTIMPLEMENTED();
return false;
}
bool MediaCodecPlayer::IsPlayerReady() {
- // UI thread
DCHECK(ui_task_runner_->BelongsToCurrentThread());
- NOTIMPLEMENTED();
+ // This method is called to check whether it's safe to release the player when
+ // the OS needs more resources. This class can be released at any time.
return true;
}
void MediaCodecPlayer::SetCdm(BrowserCdm* cdm) {
- // UI thread
DCHECK(ui_task_runner_->BelongsToCurrentThread());
NOTIMPLEMENTED();
}
@@ -224,30 +269,462 @@ void MediaCodecPlayer::SetCdm(BrowserCdm* cdm) {
void MediaCodecPlayer::OnDemuxerConfigsAvailable(
const DemuxerConfigs& configs) {
- // Media thread
DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
- NOTIMPLEMENTED();
+ DVLOG(1) << __FUNCTION__;
+
+ duration_ = configs.duration;
+
+ SetDemuxerConfigs(configs);
+
+ // Update cache and notify manager on UI thread
+ gfx::Size video_size = HasVideo() ? configs.video_size : gfx::Size();
+ ui_task_runner_->PostTask(
+ FROM_HERE, base::Bind(metadata_changed_cb_, duration_, video_size));
}
void MediaCodecPlayer::OnDemuxerDataAvailable(const DemuxerData& data) {
- // Media thread
DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
- NOTIMPLEMENTED();
+
+ DCHECK_LT(0u, data.access_units.size());
+ CHECK_GE(1u, data.demuxer_configs.size());
+
+ DVLOG(2) << "Player::" << __FUNCTION__;
+
+ if (data.type == DemuxerStream::AUDIO)
+ audio_decoder_->OnDemuxerDataAvailable(data);
+
+ if (data.type == DemuxerStream::VIDEO)
+ video_decoder_->OnDemuxerDataAvailable(data);
}
void MediaCodecPlayer::OnDemuxerSeekDone(
base::TimeDelta actual_browser_seek_time) {
- // Media thread
DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
+
+ DVLOG(1) << __FUNCTION__ << " actual_time:" << actual_browser_seek_time;
+
NOTIMPLEMENTED();
}
void MediaCodecPlayer::OnDemuxerDurationChanged(
base::TimeDelta duration) {
- // Media thread
DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
- NOTIMPLEMENTED();
+ DVLOG(1) << __FUNCTION__ << " duration:" << duration;
+
+ duration_ = duration;
+}
+
+// Events from Player, called on UI thread
+
+void MediaCodecPlayer::OnMediaMetadataChanged(base::TimeDelta duration,
+ const gfx::Size& video_size) {
+ DCHECK(ui_task_runner_->BelongsToCurrentThread());
+
+ if (duration != kNoTimestamp())
+ metadata_cache_.duration = duration;
+
+ if (!video_size.IsEmpty())
+ metadata_cache_.video_size = video_size;
+
+ manager()->OnMediaMetadataChanged(player_id(), metadata_cache_.duration,
+ metadata_cache_.video_size.width(),
+ metadata_cache_.video_size.height(), true);
+}
+
+void MediaCodecPlayer::OnTimeUpdate(base::TimeDelta current_timestamp,
+ base::TimeTicks current_time_ticks) {
+ DCHECK(ui_task_runner_->BelongsToCurrentThread());
+
+ current_time_cache_ = current_timestamp;
+ manager()->OnTimeUpdate(player_id(), current_timestamp, current_time_ticks);
+}
+
+// Events from Decoders, called on Media thread
+
+void MediaCodecPlayer::RequestDemuxerData(DemuxerStream::Type stream_type) {
+ DVLOG(2) << __FUNCTION__ << " streamType:" << stream_type;
+
+ // Use this method instead of directly binding with
+ // DemuxerAndroid::RequestDemuxerData() to avoid the race condition on
+ // deletion:
+ // 1. DeleteSoon is posted from UI to Media thread.
+ // 2. RequestDemuxerData callback is posted from Decoder to Media thread.
+ // 3. DeleteSoon arrives, we delete the player and detach from
+ // BrowserDemuxerAndroid.
+ // 4. RequestDemuxerData is processed by the media thread queue. Since the
+ // weak_ptr was invalidated in (3), this is a no-op. If we used
+ // DemuxerAndroid::RequestDemuxerData() it would arrive and will try to
+ // call the client, but the client (i.e. this player) would not exist.
+ demuxer_->RequestDemuxerData(stream_type);
+}
+
+void MediaCodecPlayer::OnPrefetchDone() {
+ DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
+ DVLOG(1) << __FUNCTION__;
+
+ if (state_ != STATE_PREFETCHING)
+ return; // Ignore
+
+ if (!HasAudio() && !HasVideo()) {
+ // 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_);
+ return;
+ }
+
+ if (HasVideo() && !HasPendingSurface()) {
+ SetState(STATE_WAITING_FOR_SURFACE);
+ return;
+ }
+
+ SetState(STATE_PLAYING);
+ StartPlaybackDecoders();
+}
+
+void MediaCodecPlayer::OnStopDone() {
+ DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
+ DVLOG(1) << __FUNCTION__;
+
+ if (!(audio_decoder_->IsStopped() && video_decoder_->IsStopped()))
+ return; // Wait until other stream is stopped
+
+ // At this point decoder threads should not be running
+ if (interpolator_.interpolating())
+ interpolator_.StopInterpolating();
+
+ switch (state_) {
+ case STATE_STOPPING:
+ if (HasPendingStart()) {
+ SetPendingStart(false);
+ SetState(STATE_PREFETCHING);
+ StartPrefetchDecoders();
+ } else {
+ SetState(STATE_PAUSED);
+ }
+ break;
+ case STATE_PLAYING:
+ // Unexpected stop means completion
+ SetState(STATE_PAUSED);
+ break;
+ default:
+ DVLOG(0) << __FUNCTION__ << " illegal state: " << AsString(state_);
+ NOTREACHED();
+ break;
+ }
+
+ // DetachListener to UI thread
+ ui_task_runner_->PostTask(FROM_HERE, detach_listener_cb_);
+
+ if (AudioFinished() && VideoFinished())
+ ui_task_runner_->PostTask(FROM_HERE, completion_cb_);
+}
+
+void MediaCodecPlayer::OnError() {
+ DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
+ DVLOG(1) << __FUNCTION__;
+
+ // STATE_ERROR blocks all events
+ SetState(STATE_ERROR);
+
+ ReleaseDecoderResources();
+}
+
+void MediaCodecPlayer::OnStarvation(DemuxerStream::Type type) {
+ DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
+ DVLOG(1) << __FUNCTION__ << " stream type:" << type;
+
+ if (state_ != STATE_PLAYING)
+ return; // Ignore
+
+ SetState(STATE_STOPPING);
+ RequestToStopDecoders();
+ SetPendingStart(true);
+}
+
+void MediaCodecPlayer::OnTimeIntervalUpdate(DemuxerStream::Type type,
+ base::TimeDelta now_playing,
+ base::TimeDelta last_buffered) {
+ DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
+
+ interpolator_.SetBounds(now_playing, last_buffered);
+
+ // Post to UI thread
+ ui_task_runner_->PostTask(FROM_HERE,
+ base::Bind(time_update_cb_, GetInterpolatedTime(),
+ base::TimeTicks::Now()));
+}
+
+void MediaCodecPlayer::OnVideoCodecCreated() {
+ DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
+
+ // This callback requests resources by releasing other players.
+ ui_task_runner_->PostTask(FROM_HERE, request_resources_cb_);
+}
+
+void MediaCodecPlayer::OnVideoResolutionChanged(const gfx::Size& size) {
+ DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
+
+ DVLOG(1) << __FUNCTION__ << " " << size.width() << "x" << size.height();
+
+ // Update cache and notify manager on UI thread
+ ui_task_runner_->PostTask(
+ FROM_HERE, base::Bind(metadata_changed_cb_, kNoTimestamp(), size));
}
+// State machine operations, called on Media thread
+
+void MediaCodecPlayer::SetState(PlayerState new_state) {
+ DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
+
+ DVLOG(1) << "SetState:" << AsString(state_) << " -> " << AsString(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() {
+ return video_decoder_->HasPendingSurface();
+}
+
+void MediaCodecPlayer::SetPendingStart(bool need_to_start) {
+ DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
+ DVLOG(1) << __FUNCTION__ << ": " << need_to_start;
+ pending_start_ = need_to_start;
+}
+
+bool MediaCodecPlayer::HasPendingStart() {
+ DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
+ return pending_start_;
+}
+
+bool MediaCodecPlayer::HasAudio() {
+ DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
+ return audio_decoder_->HasStream();
+}
+
+bool MediaCodecPlayer::HasVideo() {
+ DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
+ return video_decoder_->HasStream();
+}
+
+void MediaCodecPlayer::SetDemuxerConfigs(const DemuxerConfigs& configs) {
+ DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
+ DVLOG(1) << __FUNCTION__ << " " << configs;
+
+ DCHECK(audio_decoder_);
+ DCHECK(video_decoder_);
+
+ // At least one valid codec must be present.
+ DCHECK(configs.audio_codec != kUnknownAudioCodec ||
+ configs.video_codec != kUnknownVideoCodec);
+
+ if (configs.audio_codec != kUnknownAudioCodec)
+ audio_decoder_->SetDemuxerConfigs(configs);
+
+ if (configs.video_codec != kUnknownVideoCodec)
+ video_decoder_->SetDemuxerConfigs(configs);
+
+ if (state_ == STATE_WAITING_FOR_CONFIG) {
+ SetState(STATE_PREFETCHING);
+ StartPrefetchDecoders();
+ }
+}
+
+void MediaCodecPlayer::StartPrefetchDecoders() {
+ DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
+ DVLOG(1) << __FUNCTION__;
+
+ bool do_audio = false;
+ bool do_video = false;
+ int count = 0;
+ if (!AudioFinished()) {
+ do_audio = true;
+ ++count;
+ }
+ if (!VideoFinished()) {
+ do_video = true;
+ ++count;
+ }
+
+ DCHECK_LT(0, count); // at least one decoder should be active
+
+ base::Closure prefetch_cb = base::BarrierClosure(
+ count, base::Bind(&MediaCodecPlayer::OnPrefetchDone, media_weak_this_));
+
+ if (do_audio)
+ audio_decoder_->Prefetch(prefetch_cb);
+
+ if (do_video)
+ video_decoder_->Prefetch(prefetch_cb);
+}
+
+void MediaCodecPlayer::StartPlaybackDecoders() {
+ DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
+ DVLOG(1) << __FUNCTION__;
+
+ // Configure all streams before the start since
+ // we may discover that browser seek is required.
+
+ 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.
+ 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;
+ }
+ }
+
+ if (do_video) {
+ MediaCodecDecoder::ConfigStatus status = video_decoder_->Configure();
+ if (status != MediaCodecDecoder::CONFIG_OK) {
+ GetMediaTaskRunner()->PostTask(FROM_HERE, error_cb_);
+ return;
+ }
+ }
+
+ // At this point decoder threads should not be running.
+ if (!interpolator_.interpolating())
+ interpolator_.StartInterpolating();
+
+ base::TimeDelta current_time = GetInterpolatedTime();
+
+ if (do_audio) {
+ if (!audio_decoder_->Start(current_time)) {
+ GetMediaTaskRunner()->PostTask(FROM_HERE, error_cb_);
+ return;
+ }
+
+ // Attach listener on UI thread
+ ui_task_runner_->PostTask(FROM_HERE, attach_listener_cb_);
+ }
+
+ if (do_video) {
+ if (!video_decoder_->Start(current_time)) {
+ GetMediaTaskRunner()->PostTask(FROM_HERE, error_cb_);
+ return;
+ }
+ }
+}
+
+void MediaCodecPlayer::StopDecoders() {
+ DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
+ DVLOG(1) << __FUNCTION__;
+
+ audio_decoder_->SyncStop();
+ video_decoder_->SyncStop();
+}
+
+void MediaCodecPlayer::RequestToStopDecoders() {
+ DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
+ DVLOG(1) << __FUNCTION__;
+
+ bool do_audio = false;
+ bool do_video = false;
+
+ if (audio_decoder_->IsPrefetchingOrPlaying())
+ do_audio = true;
+ if (video_decoder_->IsPrefetchingOrPlaying())
+ do_video = true;
+
+ if (!do_audio && !do_video) {
+ GetMediaTaskRunner()->PostTask(
+ FROM_HERE, base::Bind(&MediaCodecPlayer::OnStopDone, media_weak_this_));
+ return;
+ }
+
+ if (do_audio)
+ audio_decoder_->RequestToStop();
+ if (do_video)
+ video_decoder_->RequestToStop();
+}
+
+void MediaCodecPlayer::ReleaseDecoderResources() {
+ DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
+ DVLOG(1) << __FUNCTION__;
+
+ if (audio_decoder_)
+ audio_decoder_->ReleaseDecoderResources();
+
+ if (video_decoder_)
+ video_decoder_->ReleaseDecoderResources();
+
+ // At this point decoder threads should not be running
+ if (interpolator_.interpolating())
+ interpolator_.StopInterpolating();
+}
+
+void MediaCodecPlayer::CreateDecoders() {
+ DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
+ DVLOG(1) << __FUNCTION__;
+
+ 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::OnTimeIntervalUpdate, media_weak_this_,
+ DemuxerStream::AUDIO)));
+
+ video_decoder_.reset(new MediaCodecVideoDecoder(
+ GetMediaTaskRunner(), base::Bind(&MediaCodecPlayer::RequestDemuxerData,
+ media_weak_this_, DemuxerStream::VIDEO),
+ base::Bind(&MediaCodecPlayer::OnStarvation, media_weak_this_,
+ DemuxerStream::VIDEO),
+ base::Bind(&MediaCodecPlayer::OnStopDone, media_weak_this_), error_cb_,
+ MediaCodecDecoder::SetTimeCallback(), // null callback
+ base::Bind(&MediaCodecPlayer::OnVideoResolutionChanged, media_weak_this_),
+ base::Bind(&MediaCodecPlayer::OnVideoCodecCreated, media_weak_this_)));
+}
+
+bool MediaCodecPlayer::AudioFinished() {
+ return audio_decoder_->IsCompleted() || !audio_decoder_->HasStream();
+}
+
+bool MediaCodecPlayer::VideoFinished() {
+ return video_decoder_->IsCompleted() || !video_decoder_->HasStream();
+}
+
+base::TimeDelta MediaCodecPlayer::GetInterpolatedTime() {
+ DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
+
+ base::TimeDelta interpolated_time = interpolator_.GetInterpolatedTime();
+ return std::min(interpolated_time, duration_);
+}
+
+#undef RETURN_STRING
+#define RETURN_STRING(x) \
+ case x: \
+ return #x;
+
+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_ERROR);
+ }
+ return nullptr; // crash early
+}
+
+#undef RETURN_STRING
+
} // namespace media
diff --git a/media/base/android/media_codec_player.h b/media/base/android/media_codec_player.h
index 518adbb..f9f9570 100644
--- a/media/base/android/media_codec_player.h
+++ b/media/base/android/media_codec_player.h
@@ -9,35 +9,117 @@
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/threading/thread.h"
+#include "base/time/default_tick_clock.h"
#include "media/base/android/demuxer_android.h"
#include "media/base/android/media_player_android.h"
+#include "media/base/demuxer_stream.h"
#include "media/base/media_export.h"
+#include "media/base/time_delta_interpolator.h"
+#include "ui/gfx/geometry/size.h"
#include "ui/gl/android/scoped_java_surface.h"
+// The MediaCodecPlayer class implements the media player by using Android's
+// MediaCodec. It differs from MediaSourcePlayer in that it removes most
+// processing away from the UI thread: it uses a dedicated Media thread to
+// receive the data and to handle the commands.
+
+// The player works as a state machine. Here are relationships between states:
+//
+// [ Paused ] ------------------------ (Any state)
+// | | |
+// | v v
+// | <------------------[ WaitingForConfig ] [ Error ]
+// |
+// |
+// |
+// v
+// [ Prefetching ] -------------------
+// | |
+// | v
+// | <-----------------[ WaitingForSurface ]
+// v
+// [ Playing ]
+// |
+// |
+// v
+// [ Stopping ]
+
+
+// Events and actions for pause/resume workflow.
+// ---------------------------------------------
+//
+// Start, no config:
+// ------------------------> [ Paused ] -----------------> [ Waiting ]
+// | StopDone: [ for configs ]
+// | ^ | /
+// | | | /
+// | Pause: | | Start w/config: /
+// | | | dec.Prefetch /
+// | | | /
+// | | | /
+// | | | /
+// | | | / DemuxerConfigs:
+// | | | / dec.Prefetch
+// | | | /
+// | | | /
+// | | v /
+// | /
+// | ------------------> [ Prefetching ] <--------/ [ Waiting ]
+// | | [ ] --------------> [ for surface ]
+// | | | PrefetchDone, /
+// | | | no surface: /
+// | | | /
+// | | | /
+// | | StopDone w/ | /
+// | | pending start: | PrefetchDone: /
+// | | dec.Prefetch | dec.Start /
+// | | | / SetSurface:
+// | | | / dec.Start
+// | | | /
+// | | v /
+// | | /
+// | | [ Playing ] <----------/
+// | |
+// | | |
+// | | |
+// | | | Pause: dec.RequestToStop
+// | | |
+// | | |
+// | | v
+// | |
+// ------------------------- [ Stopping ]
+
namespace media {
class BrowserCdm;
+class MediaCodecAudioDecoder;
+class MediaCodecVideoDecoder;
// Returns the task runner for the media thread
MEDIA_EXPORT scoped_refptr<base::SingleThreadTaskRunner> GetMediaTaskRunner();
-
-// This class implements the media player using Android's MediaCodec.
-// It differs from MediaSourcePlayer in that it removes most
-// processing away from UI thread: it uses a dedicated Media thread
-// to receive the data and to handle commands.
class MEDIA_EXPORT MediaCodecPlayer : public MediaPlayerAndroid,
public DemuxerAndroidClient {
public:
+ // Typedefs for the notification callbacks
+ typedef base::Callback<void(base::TimeDelta, const gfx::Size&)>
+ MetadataChangedCallback;
+
+ typedef base::Callback<void(base::TimeDelta, base::TimeTicks)>
+ TimeUpdateCallback;
+
// Constructs a player with the given ID and demuxer. |manager| must outlive
// the lifetime of this object.
MediaCodecPlayer(int player_id,
- MediaPlayerManager* manager,
+ base::WeakPtr<MediaPlayerManager> manager,
const RequestMediaResourcesCB& request_media_resources_cb,
scoped_ptr<DemuxerAndroid> demuxer,
const GURL& frame_url);
~MediaCodecPlayer() override;
+ // A helper method that performs the media thread part of initialization.
+ void Initialize();
+
// MediaPlayerAndroid implementation.
void DeleteOnCorrectThread() override;
void SetVideoSurface(gfx::ScopedJavaSurface surface) override;
@@ -63,19 +145,123 @@ class MEDIA_EXPORT MediaCodecPlayer : public MediaPlayerAndroid,
void OnDemuxerSeekDone(base::TimeDelta actual_browser_seek_time) override;
void OnDemuxerDurationChanged(base::TimeDelta duration) override;
- // Helper methods
- void Initialize();
- void DestroySelf();
-
private:
+ // The state machine states.
+ enum PlayerState {
+ STATE_PAUSED,
+ STATE_WAITING_FOR_CONFIG,
+ STATE_PREFETCHING,
+ STATE_PLAYING,
+ STATE_STOPPING,
+ STATE_WAITING_FOR_SURFACE,
+ STATE_ERROR,
+ };
+
+ // Cached values for the manager.
+ struct MediaMetadata {
+ base::TimeDelta duration;
+ gfx::Size video_size;
+ };
+
+ // MediaPlayerAndroid implementation.
+ // This method caches the data and calls manager's OnMediaMetadataChanged().
+ void OnMediaMetadataChanged(base::TimeDelta duration,
+ const gfx::Size& video_size) override;
+
+ // This method caches the current time and calls manager's OnTimeUpdate().
+ void OnTimeUpdate(base::TimeDelta current_timestamp,
+ base::TimeTicks current_time_ticks) override;
+
+ // Callbacks from decoders
+ void RequestDemuxerData(DemuxerStream::Type stream_type);
+ void OnPrefetchDone();
+ void OnStopDone();
+ void OnError();
+ void OnStarvation(DemuxerStream::Type stream_type);
+ void OnTimeIntervalUpdate(DemuxerStream::Type stream_type,
+ base::TimeDelta now_playing,
+ base::TimeDelta last_buffered);
+
+ // Callbacks from video decoder
+ void OnVideoCodecCreated();
+ void OnVideoResolutionChanged(const gfx::Size& size);
+
+ // Operations called from the state machine.
+ void SetState(PlayerState new_state);
+ void SetPendingSurface(gfx::ScopedJavaSurface surface);
+ bool HasPendingSurface();
+ void SetPendingStart(bool need_to_start);
+ bool HasPendingStart();
+ bool HasVideo();
+ bool HasAudio();
+ void SetDemuxerConfigs(const DemuxerConfigs& configs);
+ void StartPrefetchDecoders();
+ void StartPlaybackDecoders();
+ void StopDecoders();
+ void RequestToStopDecoders();
+ void ReleaseDecoderResources();
+
+ // Helper methods.
+ void CreateDecoders();
+ bool AudioFinished();
+ bool VideoFinished();
+ base::TimeDelta GetInterpolatedTime();
+
+ static const char* AsString(PlayerState state);
+
+ // Data.
+
// Object for posting tasks on UI thread.
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_;
+ // Major components: demuxer, audio and video decoders.
scoped_ptr<DemuxerAndroid> demuxer_;
+ scoped_ptr<MediaCodecAudioDecoder> audio_decoder_;
+ scoped_ptr<MediaCodecVideoDecoder> video_decoder_;
+
+ // The state of the state machine.
+ PlayerState state_;
+
+ // Notification callbacks, they call MediaPlayerManager.
+ base::Closure request_resources_cb_;
+ TimeUpdateCallback time_update_cb_;
+ base::Closure completion_cb_;
+
+ // A callback that updates metadata cache and calls the manager.
+ MetadataChangedCallback metadata_changed_cb_;
+
+ // We call the base class' AttachListener() and DetachListener() methods on UI
+ // thread with these callbacks.
+ base::Closure attach_listener_cb_;
+ base::Closure detach_listener_cb_;
+
+ // Error callback is posted by decoders or by this class itself if we cannot
+ // configure or start decoder.
+ base::Closure error_cb_;
+
+ // Total duration reported by demuxer.
+ base::TimeDelta duration_;
+
+ // base::TickClock used by |interpolator_|.
+ base::DefaultTickClock default_tick_clock_;
+
+ // Tracks the most recent media time update and provides interpolated values
+ // as playback progresses.
+ TimeDeltaInterpolator interpolator_;
+
+ // Pending data to be picked up by the upcoming state.
+ gfx::ScopedJavaSurface pending_surface_;
+ bool pending_start_;
+
+ // Configuration data for the manager, accessed on the UI thread.
+ MediaMetadata metadata_cache_;
+
+ // Cached current time, accessed on UI thread.
+ base::TimeDelta current_time_cache_;
- base::WeakPtr<MediaCodecPlayer> weak_this_;
+ base::WeakPtr<MediaCodecPlayer> media_weak_this_;
// NOTE: Weak pointers must be invalidated before all other member variables.
- base::WeakPtrFactory<MediaCodecPlayer> weak_factory_;
+ base::WeakPtrFactory<MediaCodecPlayer> media_weak_factory_;
DISALLOW_COPY_AND_ASSIGN(MediaCodecPlayer);
};
diff --git a/media/base/android/media_codec_player_unittest.cc b/media/base/android/media_codec_player_unittest.cc
new file mode 100644
index 0000000..2eb77a5
--- /dev/null
+++ b/media/base/android/media_codec_player_unittest.cc
@@ -0,0 +1,424 @@
+// 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 "base/bind.h"
+#include "base/logging.h"
+#include "base/timer/timer.h"
+#include "media/base/android/demuxer_android.h"
+#include "media/base/android/media_codec_bridge.h"
+#include "media/base/android/media_codec_player.h"
+#include "media/base/android/media_player_manager.h"
+#include "media/base/android/test_data_factory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+// Helper macro to skip the test if MediaCodecBridge isn't available.
+#define SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE() \
+ do { \
+ if (!MediaCodecBridge::IsAvailable()) { \
+ VLOG(0) << "Could not run test - not supported on device."; \
+ return; \
+ } \
+ } while (0)
+
+#define RUN_ON_MEDIA_THREAD(CLASS, METHOD, ...) \
+ do { \
+ if (!GetMediaTaskRunner()->BelongsToCurrentThread()) { \
+ GetMediaTaskRunner()->PostTask( \
+ FROM_HERE, \
+ base::Bind(&CLASS::METHOD, base::Unretained(this), ##__VA_ARGS__)); \
+ return; \
+ } \
+ } while (0)
+
+namespace {
+
+const base::TimeDelta kDefaultTimeout = base::TimeDelta::FromMilliseconds(200);
+const base::TimeDelta kAudioFramePeriod = base::TimeDelta::FromMilliseconds(20);
+
+// Mock of MediaPlayerManager for testing purpose.
+
+class MockMediaPlayerManager : public MediaPlayerManager {
+ public:
+ MockMediaPlayerManager()
+ : playback_completed_(false), weak_ptr_factory_(this) {}
+ ~MockMediaPlayerManager() override {}
+
+ MediaResourceGetter* GetMediaResourceGetter() override { return nullptr; }
+ MediaUrlInterceptor* GetMediaUrlInterceptor() override { return nullptr; }
+ void OnTimeUpdate(int player_id,
+ base::TimeDelta current_timestamp,
+ base::TimeTicks current_time_ticks) override {}
+ void OnMediaMetadataChanged(int player_id,
+ base::TimeDelta duration,
+ int width,
+ int height,
+ bool success) override {
+ media_metadata_.duration = duration;
+ media_metadata_.width = width;
+ media_metadata_.height = height;
+ media_metadata_.modified = true;
+ }
+
+ void OnPlaybackComplete(int player_id) override {
+ playback_completed_ = true;
+ }
+ void OnMediaInterrupted(int player_id) override {}
+ void OnBufferingUpdate(int player_id, int percentage) override {}
+ void OnSeekComplete(int player_id,
+ const base::TimeDelta& current_time) override {}
+ void OnError(int player_id, int error) override {}
+ void OnVideoSizeChanged(int player_id, int width, int height) override {}
+ void OnAudibleStateChanged(int player_id, bool is_audible_now) override {}
+ void OnWaitingForDecryptionKey(int player_id) override {}
+ MediaPlayerAndroid* GetFullscreenPlayer() override { return nullptr; }
+ MediaPlayerAndroid* GetPlayer(int player_id) override { return nullptr; }
+ bool RequestPlay(int player_id) override { return true; }
+
+ void OnMediaResourcesRequested(int player_id) {}
+
+ base::WeakPtr<MockMediaPlayerManager> GetWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+ }
+
+ // Conditions to wait for.
+ bool IsMetadataChanged() const { return media_metadata_.modified; }
+ bool IsPlaybackCompleted() const { return playback_completed_; }
+
+ struct MediaMetadata {
+ base::TimeDelta duration;
+ int width;
+ int height;
+ bool modified;
+ MediaMetadata() : width(0), height(0), modified(false) {}
+ };
+ MediaMetadata media_metadata_;
+
+ private:
+ bool playback_completed_;
+
+ base::WeakPtrFactory<MockMediaPlayerManager> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockMediaPlayerManager);
+};
+
+// Helper method that creates demuxer configuration.
+
+DemuxerConfigs CreateAudioVideoConfigs(const base::TimeDelta& duration,
+ const gfx::Size& video_size) {
+ DemuxerConfigs configs =
+ TestDataFactory::CreateAudioConfigs(kCodecVorbis, duration);
+ configs.video_codec = kCodecVP8;
+ configs.video_size = video_size;
+ configs.is_video_encrypted = false;
+ return configs;
+}
+
+DemuxerConfigs CreateAudioVideoConfigs(const TestDataFactory* audio,
+ const TestDataFactory* video) {
+ DemuxerConfigs result = audio->GetConfigs();
+ DemuxerConfigs vconf = video->GetConfigs();
+
+ result.video_codec = vconf.video_codec;
+ result.video_size = vconf.video_size;
+ result.is_video_encrypted = vconf.is_video_encrypted;
+ return result;
+}
+
+// AudioFactory creates data chunks that simulate audio stream from demuxer.
+
+class AudioFactory : public TestDataFactory {
+ public:
+ AudioFactory(const base::TimeDelta& duration)
+ : TestDataFactory("vorbis-packet-%d", duration, kAudioFramePeriod) {}
+
+ DemuxerConfigs GetConfigs() const override {
+ return TestDataFactory::CreateAudioConfigs(kCodecVorbis, duration_);
+ }
+
+ protected:
+ void ModifyAccessUnit(int index_in_chunk, AccessUnit* unit) override {
+ // Vorbis needs 4 extra bytes padding on Android to decode properly.
+ // Check NuMediaExtractor.cpp in Android source code.
+ uint8 padding[4] = {0xff, 0xff, 0xff, 0xff};
+ unit->data.insert(unit->data.end(), padding, padding + 4);
+ }
+};
+
+// Mock of DemuxerAndroid for testing purpose.
+
+class MockDemuxerAndroid : public DemuxerAndroid {
+ public:
+ MockDemuxerAndroid() : client_(nullptr) {}
+ ~MockDemuxerAndroid() override {}
+
+ // DemuxerAndroid implementation
+ void Initialize(DemuxerAndroidClient* client) override;
+ void RequestDemuxerData(DemuxerStream::Type type) override;
+ void RequestDemuxerSeek(const base::TimeDelta& time_to_seek,
+ bool is_browser_seek) override {}
+
+ // Sets the audio data factory.
+ void SetAudioFactory(scoped_ptr<TestDataFactory> factory) {
+ audio_factory_ = factory.Pass();
+ }
+
+ // Sets the video data factory.
+ void SetVideoFactory(scoped_ptr<TestDataFactory> factory) {
+ video_factory_ = factory.Pass();
+ }
+
+ // Post DemuxerConfigs to the client (i.e. the player) on correct thread.
+ void PostConfigs(const DemuxerConfigs& configs);
+
+ // Post DemuxerConfigs derived from data factories that has been set.
+ void PostInternalConfigs();
+
+ // Conditions to wait for.
+ bool IsInitialized() const { return client_; }
+ bool HasPendingConfigs() const { return pending_configs_; }
+
+ private:
+ DemuxerAndroidClient* client_;
+ scoped_ptr<DemuxerConfigs> pending_configs_;
+ scoped_ptr<TestDataFactory> audio_factory_;
+ scoped_ptr<TestDataFactory> video_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockDemuxerAndroid);
+};
+
+void MockDemuxerAndroid::Initialize(DemuxerAndroidClient* client) {
+ DVLOG(1) << "MockDemuxerAndroid::" << __FUNCTION__;
+ DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
+
+ client_ = client;
+ if (pending_configs_)
+ client_->OnDemuxerConfigsAvailable(*pending_configs_);
+}
+
+void MockDemuxerAndroid::RequestDemuxerData(DemuxerStream::Type type) {
+ DemuxerData chunk;
+ base::TimeDelta delay;
+
+ bool created = false;
+ if (type == DemuxerStream::AUDIO && audio_factory_)
+ created = audio_factory_->CreateChunk(&chunk, &delay);
+ else if (type == DemuxerStream::VIDEO && audio_factory_)
+ created = video_factory_->CreateChunk(&chunk, &delay);
+
+ if (!created)
+ return;
+
+ chunk.type = type;
+
+ // Post to Media thread.
+ DCHECK(client_);
+ GetMediaTaskRunner()->PostDelayedTask(
+ FROM_HERE, base::Bind(&DemuxerAndroidClient::OnDemuxerDataAvailable,
+ base::Unretained(client_), chunk),
+ delay);
+}
+
+void MockDemuxerAndroid::PostConfigs(const DemuxerConfigs& configs) {
+ DVLOG(1) << "MockDemuxerAndroid::" << __FUNCTION__;
+ RUN_ON_MEDIA_THREAD(MockDemuxerAndroid, PostConfigs, configs);
+
+ DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
+
+ if (client_)
+ client_->OnDemuxerConfigsAvailable(configs);
+ else
+ pending_configs_ = scoped_ptr<DemuxerConfigs>(new DemuxerConfigs(configs));
+}
+
+void MockDemuxerAndroid::PostInternalConfigs() {
+ ASSERT_TRUE(audio_factory_ || video_factory_);
+
+ if (audio_factory_ && video_factory_) {
+ PostConfigs(
+ CreateAudioVideoConfigs(audio_factory_.get(), video_factory_.get()));
+ } else if (audio_factory_) {
+ PostConfigs(audio_factory_->GetConfigs());
+ } else if (video_factory_) {
+ PostConfigs(video_factory_->GetConfigs());
+ }
+}
+
+} // namespace (anonymous)
+
+// The test fixture for MediaCodecPlayer
+
+class MediaCodecPlayerTest : public testing::Test {
+ public:
+ MediaCodecPlayerTest();
+ ~MediaCodecPlayerTest() override;
+
+ protected:
+ typedef base::Callback<bool()> Predicate;
+
+ void CreatePlayer();
+
+ // Waits for condition to become true or for timeout to expire.
+ // Returns true if the condition becomes true.
+ bool WaitForCondition(const Predicate& condition,
+ const base::TimeDelta& timeout = kDefaultTimeout);
+
+ base::MessageLoop message_loop_;
+ MockMediaPlayerManager manager_;
+ MockDemuxerAndroid* demuxer_; // owned by player_
+ MediaCodecPlayer* player_; // raw pointer due to DeleteOnCorrectThread()
+
+ private:
+ bool is_timeout_expired() const { return is_timeout_expired_; }
+ void SetTimeoutExpired(bool value) { is_timeout_expired_ = value; }
+
+ bool is_timeout_expired_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaCodecPlayerTest);
+};
+
+MediaCodecPlayerTest::MediaCodecPlayerTest()
+ : demuxer_(new MockDemuxerAndroid()), player_(nullptr) {
+}
+
+void MediaCodecPlayerTest::CreatePlayer() {
+ DCHECK(demuxer_);
+ player_ = new MediaCodecPlayer(
+ 0, // player_id
+ manager_.GetWeakPtr(),
+ base::Bind(&MockMediaPlayerManager::OnMediaResourcesRequested,
+ base::Unretained(&manager_)),
+ scoped_ptr<MockDemuxerAndroid>(demuxer_), GURL());
+
+ DCHECK(player_);
+}
+
+MediaCodecPlayerTest::~MediaCodecPlayerTest() {
+ if (player_)
+ player_->DeleteOnCorrectThread();
+}
+
+bool MediaCodecPlayerTest::WaitForCondition(const Predicate& condition,
+ const base::TimeDelta& timeout) {
+ // Let the message_loop_ process events.
+ // We start the timer and RunUntilIdle() until it signals.
+
+ SetTimeoutExpired(false);
+
+ base::Timer timer(false, false);
+ timer.Start(FROM_HERE, timeout,
+ base::Bind(&MediaCodecPlayerTest::SetTimeoutExpired,
+ base::Unretained(this), true));
+
+ do {
+ if (condition.Run()) {
+ timer.Stop();
+ return true;
+ }
+ message_loop_.RunUntilIdle();
+ } while (!is_timeout_expired());
+
+ DCHECK(!timer.IsRunning());
+ return false;
+}
+
+TEST_F(MediaCodecPlayerTest, SetAudioConfigsBeforePlayerCreation) {
+ // Post configuration when there is no player yet.
+ EXPECT_EQ(nullptr, player_);
+
+ base::TimeDelta duration = base::TimeDelta::FromSeconds(10);
+
+ demuxer_->PostConfigs(
+ TestDataFactory::CreateAudioConfigs(kCodecVorbis, duration));
+
+ // Wait until the configuration gets to the media thread.
+ EXPECT_TRUE(WaitForCondition(base::Bind(
+ &MockDemuxerAndroid::HasPendingConfigs, base::Unretained(demuxer_))));
+
+ // Then create the player.
+ CreatePlayer();
+
+ // Configuration should propagate through the player and to the manager.
+ EXPECT_TRUE(
+ WaitForCondition(base::Bind(&MockMediaPlayerManager::IsMetadataChanged,
+ base::Unretained(&manager_))));
+
+ EXPECT_EQ(duration, manager_.media_metadata_.duration);
+ EXPECT_EQ(0, manager_.media_metadata_.width);
+ EXPECT_EQ(0, manager_.media_metadata_.height);
+}
+
+TEST_F(MediaCodecPlayerTest, SetAudioConfigsAfterPlayerCreation) {
+ CreatePlayer();
+
+ // Wait till the player is initialized on media thread.
+ EXPECT_TRUE(WaitForCondition(base::Bind(&MockDemuxerAndroid::IsInitialized,
+ base::Unretained(demuxer_))));
+
+ // Post configuration after the player has been initialized.
+ base::TimeDelta duration = base::TimeDelta::FromSeconds(10);
+ demuxer_->PostConfigs(
+ TestDataFactory::CreateAudioConfigs(kCodecVorbis, duration));
+
+ // Configuration should propagate through the player and to the manager.
+ EXPECT_TRUE(
+ WaitForCondition(base::Bind(&MockMediaPlayerManager::IsMetadataChanged,
+ base::Unretained(&manager_))));
+
+ EXPECT_EQ(duration, manager_.media_metadata_.duration);
+ EXPECT_EQ(0, manager_.media_metadata_.width);
+ EXPECT_EQ(0, manager_.media_metadata_.height);
+}
+
+TEST_F(MediaCodecPlayerTest, SetAudioVideoConfigsAfterPlayerCreation) {
+ CreatePlayer();
+
+ // Wait till the player is initialized on media thread.
+ EXPECT_TRUE(WaitForCondition(base::Bind(&MockDemuxerAndroid::IsInitialized,
+ base::Unretained(demuxer_))));
+
+ // Post configuration after the player has been initialized.
+ base::TimeDelta duration = base::TimeDelta::FromSeconds(10);
+ demuxer_->PostConfigs(CreateAudioVideoConfigs(duration, gfx::Size(320, 240)));
+
+ // Configuration should propagate through the player and to the manager.
+ EXPECT_TRUE(
+ WaitForCondition(base::Bind(&MockMediaPlayerManager::IsMetadataChanged,
+ base::Unretained(&manager_))));
+
+ EXPECT_EQ(duration, manager_.media_metadata_.duration);
+ EXPECT_EQ(320, manager_.media_metadata_.width);
+ EXPECT_EQ(240, manager_.media_metadata_.height);
+}
+
+TEST_F(MediaCodecPlayerTest, PlayAudioTillCompletion) {
+ SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE();
+
+ base::TimeDelta duration = base::TimeDelta::FromMilliseconds(1000);
+ base::TimeDelta timeout = base::TimeDelta::FromMilliseconds(1100);
+
+ demuxer_->SetAudioFactory(
+ scoped_ptr<AudioFactory>(new AudioFactory(duration)));
+
+ CreatePlayer();
+
+ // Wait till the player is initialized on media thread.
+ EXPECT_TRUE(WaitForCondition(base::Bind(&MockDemuxerAndroid::IsInitialized,
+ base::Unretained(demuxer_))));
+
+ // Post configuration after the player has been initialized.
+ demuxer_->PostInternalConfigs();
+
+ EXPECT_FALSE(manager_.IsPlaybackCompleted());
+
+ player_->Start();
+
+ EXPECT_TRUE(
+ WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackCompleted,
+ base::Unretained(&manager_)),
+ timeout));
+}
+
+} // namespace media
diff --git a/media/base/android/media_player_android.cc b/media/base/android/media_player_android.cc
index 60e1dfc..99668e81 100644
--- a/media/base/android/media_player_android.cc
+++ b/media/base/android/media_player_android.cc
@@ -98,4 +98,8 @@ void MediaPlayerAndroid::SetAudible(bool is_audible) {
}
}
+base::WeakPtr<MediaPlayerAndroid> MediaPlayerAndroid::WeakPtrForUIThread() {
+ return weak_factory_.GetWeakPtr();
+}
+
} // namespace media
diff --git a/media/base/android/media_player_android.h b/media/base/android/media_player_android.h
index 8928222..e523624 100644
--- a/media/base/android/media_player_android.h
+++ b/media/base/android/media_player_android.h
@@ -13,6 +13,7 @@
#include "base/time/time.h"
#include "media/base/android/media_player_listener.h"
#include "media/base/media_export.h"
+#include "ui/gfx/geometry/size.h"
#include "ui/gl/android/scoped_java_surface.h"
#include "url/gurl.h"
@@ -79,10 +80,24 @@ class MEDIA_EXPORT MediaPlayerAndroid {
// Associates the |cdm| with this player.
virtual void SetCdm(BrowserCdm* cdm);
+ // Overridden in MediaCodecPlayer to pass data between threads.
+ virtual void OnMediaMetadataChanged(base::TimeDelta duration,
+ const gfx::Size& video_size) {}
+
+ // Overridden in MediaCodecPlayer to pass data between threads.
+ virtual void OnTimeUpdate(base::TimeDelta current_timestamp,
+ base::TimeTicks current_time_ticks) {}
+
int player_id() { return player_id_; }
GURL frame_url() { return frame_url_; }
+ // Attach/Detaches |listener_| for listening to all the media events. If
+ // |j_media_player| is NULL, |listener_| only listens to the system media
+ // events. Otherwise, it also listens to the events from |j_media_player|.
+ void AttachListener(jobject j_media_player);
+ void DetachListener();
+
protected:
MediaPlayerAndroid(int player_id,
MediaPlayerManager* manager,
@@ -101,12 +116,6 @@ class MEDIA_EXPORT MediaPlayerAndroid {
virtual void OnSeekComplete();
virtual void OnMediaPrepared();
- // Attach/Detaches |listener_| for listening to all the media events. If
- // |j_media_player| is NULL, |listener_| only listens to the system media
- // events. Otherwise, it also listens to the events from |j_media_player|.
- void AttachListener(jobject j_media_player);
- void DetachListener();
-
// When destroying a subclassed object on a non-UI thread
// it is still required to destroy the |listener_| related stuff
// on the UI thread.
@@ -115,6 +124,8 @@ class MEDIA_EXPORT MediaPlayerAndroid {
MediaPlayerManager* manager() { return manager_; }
+ base::WeakPtr<MediaPlayerAndroid> WeakPtrForUIThread();
+
RequestMediaResourcesCB request_media_resources_cb_;
private:
diff --git a/media/media.gyp b/media/media.gyp
index d0fabd3c..f581a46 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -1126,6 +1126,7 @@
'base/android/access_unit_queue_unittest.cc',
'base/android/media_codec_bridge_unittest.cc',
'base/android/media_codec_decoder_unittest.cc',
+ 'base/android/media_codec_player_unittest.cc',
'base/android/media_drm_bridge_unittest.cc',
'base/android/media_source_player_unittest.cc',
'base/android/test_data_factory.cc',