summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortimav <timav@chromium.org>2015-06-26 12:34:59 -0700
committerCommit bot <commit-bot@chromium.org>2015-06-26 19:36:01 +0000
commit389da2e28a6e675da59bdb5fb492cbba77df9874 (patch)
tree69d0f5678b3cf3083094dfc2fbd94bc96bd5593e
parentc670c20de33c2e868422c0fcb2b9b37d429c969a (diff)
downloadchromium_src-389da2e28a6e675da59bdb5fb492cbba77df9874.zip
chromium_src-389da2e28a6e675da59bdb5fb492cbba77df9874.tar.gz
chromium_src-389da2e28a6e675da59bdb5fb492cbba77df9874.tar.bz2
MediaCodecPlayer (stage 1 - play/pause only)
This CL implements the basic playback with pause/resume till completion only (no seek, dynamic configuration, surface change, encryption). The MediaCodecPlayer uses one long-lived Media thread for the IPC with demuxer and for the player's internal operations. The player controls two MediaCodecDecoder objects (audio and video) that work with Android MediaCodec. This CL depends on https://codereview.chromium.org/1176993005/ (the MediaCodecDecoder CL) which should be committed first. For discussion see https://codereview.chromium.org/1128383003/ BUG=407577 Review URL: https://codereview.chromium.org/1184373005 Cr-Commit-Position: refs/heads/master@{#336441}
-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',