| Index: media/base/android/media_player_bridge.cc
|
| diff --git a/media/base/android/media_player_bridge.cc b/media/base/android/media_player_bridge.cc
|
| index 2728271217a8e6f0793f305707309465e80ed00a..423ae7a5e949011800423199de490949e26e3bce 100644
|
| --- a/media/base/android/media_player_bridge.cc
|
| +++ b/media/base/android/media_player_bridge.cc
|
| @@ -9,10 +9,9 @@
|
| #include "base/basictypes.h"
|
| #include "base/logging.h"
|
| #include "base/stringprintf.h"
|
| -
|
| -// Auto generated jni class from MediaPlayerListener.java.
|
| -// Check base/android/jni_generator/golden_sample_for_tests_jni.h for example.
|
| -#include "jni/MediaPlayerListener_jni.h"
|
| +#include "base/message_loop_proxy.h"
|
| +#include "media/base/android/cookie_getter.h"
|
| +#include "media/base/android/media_player_bridge_manager.h"
|
|
|
| using base::android::AttachCurrentThread;
|
| using base::android::CheckException;
|
| @@ -31,9 +30,65 @@ static const jint kSeekForwardAvailable = 3;
|
| // This needs to be kept in sync with android.os.PowerManager
|
| static const int kAndroidFullWakeLock = 26;
|
|
|
| +// Time update happens every 250ms.
|
| +static const int kTimeUpdateInterval = 250;
|
| +
|
| +// Because we create the media player lazily on android, the duration of the
|
| +// media is initially unknown to us. This makes the user unable to perform
|
| +// seek. To solve this problem, we use a temporary duration of 100 seconds when
|
| +// the duration is unknown. And we scale the seek position later when duration
|
| +// is available.
|
| +// TODO(qinmin): create a thread and use android MediaMetadataRetriever
|
| +// class to extract the duration.
|
| +static const int kTemporaryDuration = 100;
|
| +
|
| namespace media {
|
|
|
| -MediaPlayerBridge::MediaPlayerBridge() {
|
| +MediaPlayerBridge::MediaPlayerBridge(
|
| + int player_id,
|
| + const std::string& url,
|
| + const std::string& first_party_for_cookies,
|
| + CookieGetter* cookie_getter,
|
| + bool hide_url_log,
|
| + MediaPlayerBridgeManager* manager,
|
| + const MediaErrorCB& media_error_cb,
|
| + const VideoSizeChangedCB& video_size_changed_cb,
|
| + const BufferingUpdateCB& buffering_update_cb,
|
| + const MediaPreparedCB& media_prepared_cb,
|
| + const PlaybackCompleteCB& playback_complete_cb,
|
| + const SeekCompleteCB& seek_complete_cb,
|
| + const TimeUpdateCB& time_update_cb)
|
| + : media_error_cb_(media_error_cb),
|
| + video_size_changed_cb_(video_size_changed_cb),
|
| + buffering_update_cb_(buffering_update_cb),
|
| + media_prepared_cb_(media_prepared_cb),
|
| + playback_complete_cb_(playback_complete_cb),
|
| + seek_complete_cb_(seek_complete_cb),
|
| + time_update_cb_(time_update_cb),
|
| + player_id_(player_id),
|
| + prepared_(false),
|
| + pending_play_(false),
|
| + url_(url),
|
| + first_party_for_cookies_(first_party_for_cookies),
|
| + has_cookies_(false),
|
| + hide_url_log_(hide_url_log),
|
| + duration_(base::TimeDelta::FromSeconds(kTemporaryDuration)),
|
| + width_(0),
|
| + height_(0),
|
| + can_pause_(true),
|
| + can_seek_forward_(true),
|
| + can_seek_backward_(true),
|
| + manager_(manager),
|
| + cookie_getter_(cookie_getter),
|
| + ALLOW_THIS_IN_INITIALIZER_LIST(weak_this_(this)),
|
| + listener_(base::MessageLoopProxy::current(),
|
| + weak_this_.GetWeakPtr()) {}
|
| +
|
| +MediaPlayerBridge::~MediaPlayerBridge() {
|
| + Release();
|
| +}
|
| +
|
| +void MediaPlayerBridge::InitializePlayer() {
|
| JNIEnv* env = AttachCurrentThread();
|
| CHECK(env);
|
|
|
| @@ -48,16 +103,13 @@ MediaPlayerBridge::MediaPlayerBridge() {
|
| j_media_player_.Reset(tmp);
|
|
|
| ScopedJavaLocalRef<jobject> j_listener(
|
| - Java_MediaPlayerListener_create(env,
|
| - reinterpret_cast<intptr_t>(this)));
|
| - DCHECK(!j_listener.is_null());
|
| + listener_.CreateMediaPlayerListener());
|
|
|
| // Set it as the various listeners.
|
| const char* listeners[] = {
|
| "OnBufferingUpdateListener",
|
| "OnCompletionListener",
|
| "OnErrorListener",
|
| - "OnInfoListener",
|
| "OnPreparedListener",
|
| "OnSeekCompleteListener",
|
| "OnVideoSizeChangedListener",
|
| @@ -73,22 +125,52 @@ MediaPlayerBridge::MediaPlayerBridge() {
|
| env->CallVoidMethod(j_media_player_.obj(), method, j_listener.obj());
|
| CheckException(env);
|
| }
|
| +
|
| + jobject j_context = base::android::GetApplicationContext();
|
| + DCHECK(j_context);
|
| + jmethodID method = GetMethodID(env, j_media_player_class_,
|
| + "setWakeMode", "(Landroid/content/Context;I)V");
|
| + env->CallVoidMethod(j_media_player_.obj(), method, j_context,
|
| + kAndroidFullWakeLock);
|
| + CheckException(env);
|
| }
|
|
|
| -MediaPlayerBridge::~MediaPlayerBridge() {
|
| - SetVideoSurface(NULL);
|
| - CallVoidMethod("release");
|
| +void MediaPlayerBridge::SetVideoSurface(jobject surface) {
|
| + if (j_media_player_.is_null() && surface != NULL)
|
| + Prepare();
|
| +
|
| + JNIEnv* env = AttachCurrentThread();
|
| + CHECK(env);
|
| +
|
| + jmethodID method = GetMethodID(env,
|
| + j_media_player_class_,
|
| + "setSurface",
|
| + "(Landroid/view/Surface;)V");
|
| + env->CallVoidMethod(j_media_player_.obj(), method, surface);
|
| + CheckException(env);
|
| }
|
|
|
| -void MediaPlayerBridge::SetDataSource(
|
| - const std::string& url,
|
| - const std::string& cookies,
|
| - bool hide_url_log) {
|
| +void MediaPlayerBridge::Prepare() {
|
| + if (j_media_player_.is_null())
|
| + InitializePlayer();
|
| +
|
| + if (has_cookies_) {
|
| + GetCookiesCallback(cookies_);
|
| + } else {
|
| + cookie_getter_->GetCookies(url_, first_party_for_cookies_, base::Bind(
|
| + &MediaPlayerBridge::GetCookiesCallback, weak_this_.GetWeakPtr()));
|
| + }
|
| +}
|
| +
|
| +void MediaPlayerBridge::GetCookiesCallback(const std::string& cookies) {
|
| + cookies_ = cookies;
|
| + has_cookies_ = true;
|
| +
|
| JNIEnv* env = AttachCurrentThread();
|
| CHECK(env);
|
|
|
| // Create a Java String for the URL.
|
| - ScopedJavaLocalRef<jstring> j_url_string = ConvertUTF8ToJavaString(env, url);
|
| + ScopedJavaLocalRef<jstring> j_url_string = ConvertUTF8ToJavaString(env, url_);
|
|
|
| // Create the android.net.Uri object.
|
| ScopedJavaLocalRef<jclass> cls(GetClass(env, "android/net/Uri"));
|
| @@ -108,11 +190,11 @@ void MediaPlayerBridge::SetDataSource(
|
| // Construct headers that needs to be sent with the url.
|
| HeadersMap headers;
|
| // For incognito mode, we need a header to hide url log.
|
| - if (hide_url_log)
|
| + if (hide_url_log_)
|
| headers.insert(std::make_pair("x-hide-urls-from-log", "true"));
|
| // If cookies are present, add them in the header.
|
| - if (!cookies.empty())
|
| - headers.insert(std::make_pair("Cookie", cookies));
|
| + if (!cookies_.empty())
|
| + headers.insert(std::make_pair("Cookie", cookies_));
|
|
|
| // Fill the Map with the headers.
|
| for (HeadersMap::const_iterator iter = headers.begin();
|
| @@ -131,101 +213,112 @@ void MediaPlayerBridge::SetDataSource(
|
| jmethodID set_data_source =
|
| GetMethodID(env, j_media_player_class_, "setDataSource",
|
| "(Landroid/content/Context;Landroid/net/Uri;Ljava/util/Map;)V");
|
| - DCHECK(set_data_source);
|
| env->CallVoidMethod(j_media_player_.obj(), set_data_source, j_context,
|
| j_uri.obj(), j_map.obj());
|
| - CheckException(env);
|
| -}
|
| -
|
| -void MediaPlayerBridge::SetVideoSurface(jobject surface) {
|
| - JNIEnv* env = AttachCurrentThread();
|
| - CHECK(env);
|
| -
|
| - jmethodID method = GetMethodID(env,
|
| - j_media_player_class_,
|
| - "setSurface",
|
| - "(Landroid/view/Surface;)V");
|
| - env->CallVoidMethod(j_media_player_.obj(), method, surface);
|
| - CheckException(env);
|
| -}
|
| -
|
| -void MediaPlayerBridge::Prepare(const MediaInfoCB& media_info_cb,
|
| - const MediaErrorCB& media_error_cb,
|
| - const VideoSizeChangedCB& video_size_changed_cb,
|
| - const BufferingUpdateCB& buffering_update_cb,
|
| - const base::Closure& media_prepared_cb) {
|
| - media_info_cb_ = media_info_cb;
|
| - media_error_cb_ = media_error_cb,
|
| - video_size_changed_cb_ = video_size_changed_cb;
|
| - buffering_update_cb_ = buffering_update_cb;
|
| - media_prepared_cb_ = media_prepared_cb;
|
| - CallVoidMethod("prepareAsync");
|
| + bool is_data_source_set_ = !base::android::ClearException(env);
|
| +
|
| + if (is_data_source_set_) {
|
| + if (manager_)
|
| + manager_->RequestMediaResources(this);
|
| + CallVoidMethod("prepareAsync");
|
| + } else {
|
| + media_error_cb_.Run(player_id_, MEDIA_ERROR_UNKNOWN);
|
| + }
|
| }
|
|
|
| -void MediaPlayerBridge::Start(const base::Closure& playback_complete_cb) {
|
| - playback_complete_cb_ = playback_complete_cb;
|
| - CallVoidMethod("start");
|
| +void MediaPlayerBridge::Start() {
|
| + if (j_media_player_.is_null()) {
|
| + pending_play_ = true;
|
| + Prepare();
|
| + } else {
|
| + if (prepared_)
|
| + StartInternal();
|
| + else
|
| + pending_play_ = true;
|
| + }
|
| }
|
|
|
| void MediaPlayerBridge::Pause() {
|
| - CallVoidMethod("pause");
|
| + if (j_media_player_.is_null()) {
|
| + pending_play_ = false;
|
| + } else {
|
| + if (prepared_ && IsPlaying())
|
| + PauseInternal();
|
| + else
|
| + pending_play_ = false;
|
| + }
|
| }
|
|
|
| -void MediaPlayerBridge::Stop() {
|
| - CallVoidMethod("stop");
|
| -}
|
|
|
| bool MediaPlayerBridge::IsPlaying() {
|
| + if (!prepared_)
|
| + return pending_play_;
|
| +
|
| JNIEnv* env = AttachCurrentThread();
|
| CHECK(env);
|
|
|
| - jmethodID method = GetMethodID(env,
|
| - j_media_player_class_,
|
| - "isPlaying",
|
| + jmethodID method = GetMethodID(env, j_media_player_class_, "isPlaying",
|
| "()Z");
|
| - jint result = env->CallBooleanMethod(j_media_player_.obj(), method);
|
| + jboolean result = env->CallBooleanMethod(j_media_player_.obj(), method);
|
| CheckException(env);
|
|
|
| return result;
|
| }
|
|
|
| int MediaPlayerBridge::GetVideoWidth() {
|
| + if (!prepared_)
|
| + return width_;
|
| return CallIntMethod("getVideoWidth");
|
| }
|
|
|
| int MediaPlayerBridge::GetVideoHeight() {
|
| + if (!prepared_)
|
| + return height_;
|
| return CallIntMethod("getVideoHeight");
|
| }
|
|
|
| -void MediaPlayerBridge::SeekTo(base::TimeDelta time,
|
| - const base::Closure& seek_complete_cb) {
|
| - seek_complete_cb_ = seek_complete_cb;
|
| - JNIEnv* env = AttachCurrentThread();
|
| - CHECK(env);
|
| +void MediaPlayerBridge::SeekTo(base::TimeDelta time) {
|
| + // Record the time to seek when OnMediaPrepared() is called.
|
| + pending_seek_ = time;
|
|
|
| - jmethodID method = GetMethodID(env, j_media_player_class_, "seekTo", "(I)V");
|
| - DCHECK(method);
|
| - int time_msec = static_cast<int>(time.InMilliseconds());
|
| - DCHECK_EQ(time.InMilliseconds(), static_cast<int64>(time_msec));
|
| - env->CallVoidMethod(j_media_player_.obj(),
|
| - method,
|
| - time_msec);
|
| - CheckException(env);
|
| + if (j_media_player_.is_null())
|
| + Prepare();
|
| + else if (prepared_)
|
| + SeekInternal(time);
|
| }
|
|
|
| base::TimeDelta MediaPlayerBridge::GetCurrentTime() {
|
| + if (!prepared_)
|
| + return pending_seek_;
|
| return base::TimeDelta::FromMilliseconds(CallIntMethod("getCurrentPosition"));
|
| }
|
|
|
| base::TimeDelta MediaPlayerBridge::GetDuration() {
|
| + if (!prepared_)
|
| + return duration_;
|
| return base::TimeDelta::FromMilliseconds(CallIntMethod("getDuration"));
|
| }
|
|
|
| -void MediaPlayerBridge::Reset() {
|
| - CallVoidMethod("reset");
|
| +void MediaPlayerBridge::Release() {
|
| + if (j_media_player_.is_null())
|
| + return;
|
| +
|
| + time_update_timer_.Stop();
|
| + if (prepared_)
|
| + pending_seek_ = GetCurrentTime();
|
| + if (manager_)
|
| + manager_->ReleaseMediaResources(this);
|
| + prepared_ = false;
|
| + pending_play_ = false;
|
| + SetVideoSurface(NULL);
|
| + CallVoidMethod("release");
|
| + j_media_player_.Reset();
|
| }
|
|
|
| void MediaPlayerBridge::SetVolume(float left_volume, float right_volume) {
|
| + if (j_media_player_.is_null())
|
| + return;
|
| +
|
| JNIEnv* env = AttachCurrentThread();
|
| CHECK(env);
|
|
|
| @@ -239,62 +332,64 @@ void MediaPlayerBridge::SetVolume(float left_volume, float right_volume) {
|
| CheckException(env);
|
| }
|
|
|
| -void MediaPlayerBridge::SetStayAwakeWhilePlaying() {
|
| - JNIEnv* env = AttachCurrentThread();
|
| - CHECK(env);
|
| -
|
| - jobject j_context = base::android::GetApplicationContext();
|
| - DCHECK(j_context);
|
| - jint j_mode = kAndroidFullWakeLock;
|
| - jmethodID method = GetMethodID(env, j_media_player_class_,
|
| - "setWakeMode", "(Landroid/content/Context;I)V");
|
| - env->CallVoidMethod(j_media_player_.obj(), method, j_context, j_mode);
|
| - CheckException(env);
|
| +void MediaPlayerBridge::DoTimeUpdate() {
|
| + base::TimeDelta current = GetCurrentTime();
|
| + time_update_cb_.Run(player_id_, current);
|
| }
|
|
|
| -void MediaPlayerBridge::OnMediaError(JNIEnv* /* env */,
|
| - jobject /* obj */,
|
| - jint error_type) {
|
| - media_error_cb_.Run(error_type);
|
| +void MediaPlayerBridge::OnMediaError(int error_type) {
|
| + media_error_cb_.Run(player_id_, error_type);
|
| }
|
|
|
| -void MediaPlayerBridge::OnMediaInfo(JNIEnv* /* env */,
|
| - jobject /* obj */,
|
| - jint info_type) {
|
| - media_info_cb_.Run(info_type);
|
| +void MediaPlayerBridge::OnVideoSizeChanged(int width, int height) {
|
| + width_ = width;
|
| + height_ = height_;
|
| + video_size_changed_cb_.Run(player_id_, width, height);
|
| }
|
|
|
| -void MediaPlayerBridge::OnVideoSizeChanged(JNIEnv* /* env */,
|
| - jobject /* obj */,
|
| - jint width,
|
| - jint height) {
|
| - video_size_changed_cb_.Run(width, height);
|
| +void MediaPlayerBridge::OnBufferingUpdate(int percent) {
|
| + buffering_update_cb_.Run(player_id_, percent);
|
| }
|
|
|
| -void MediaPlayerBridge::OnBufferingUpdate(JNIEnv* /* env */,
|
| - jobject /* obj */,
|
| - jint percent) {
|
| - buffering_update_cb_.Run(percent);
|
| +void MediaPlayerBridge::OnPlaybackComplete() {
|
| + time_update_timer_.Stop();
|
| + playback_complete_cb_.Run(player_id_);
|
| }
|
|
|
| -void MediaPlayerBridge::OnPlaybackComplete(JNIEnv* /* env */,
|
| - jobject /* obj */) {
|
| - playback_complete_cb_.Run();
|
| +void MediaPlayerBridge::OnSeekComplete() {
|
| + seek_complete_cb_.Run(player_id_, GetCurrentTime());
|
| }
|
|
|
| -void MediaPlayerBridge::OnSeekComplete(JNIEnv* /* env */,
|
| - jobject /* obj */) {
|
| - seek_complete_cb_.Run();
|
| -}
|
| +void MediaPlayerBridge::OnMediaPrepared() {
|
| + if (j_media_player_.is_null())
|
| + return;
|
| +
|
| + prepared_ = true;
|
| +
|
| + base::TimeDelta dur = duration_;
|
| + duration_ = GetDuration();
|
| +
|
| + if (duration_ != dur && 0 != dur.InMilliseconds()) {
|
| + // Scale the |pending_seek_| according to the new duration.
|
| + pending_seek_ = base::TimeDelta::FromSeconds(
|
| + pending_seek_.InSecondsF() * duration_.InSecondsF() / dur.InSecondsF());
|
| + }
|
| +
|
| + // If media player was recovered from a saved state, consume all the pending
|
| + // events.
|
| + SeekInternal(pending_seek_);
|
| +
|
| + if (pending_play_) {
|
| + StartInternal();
|
| + pending_play_ = false;
|
| + }
|
|
|
| -void MediaPlayerBridge::OnMediaPrepared(JNIEnv* /* env */,
|
| - jobject /* obj */) {
|
| - media_prepared_cb_.Run();
|
| + GetMetadata();
|
| +
|
| + media_prepared_cb_.Run(player_id_, duration_);
|
| }
|
|
|
| -void MediaPlayerBridge::GetMetadata(bool* can_pause,
|
| - bool* can_seek_forward,
|
| - bool* can_seek_backward) {
|
| +void MediaPlayerBridge::GetMetadata() {
|
| JNIEnv* env = AttachCurrentThread();
|
| CHECK(env);
|
|
|
| @@ -311,17 +406,46 @@ void MediaPlayerBridge::GetMetadata(bool* can_pause,
|
|
|
| ScopedJavaLocalRef<jclass> cls(GetClass(env, "android/media/Metadata"));
|
| jmethodID get_boolean = GetMethodID(env, cls, "getBoolean", "(I)Z");
|
| - *can_pause = env->CallBooleanMethod(j_metadata.obj(),
|
| + can_pause_ = env->CallBooleanMethod(j_metadata.obj(),
|
| get_boolean,
|
| kPauseAvailable);
|
| CheckException(env);
|
| - *can_seek_backward = env->CallBooleanMethod(j_metadata.obj(),
|
| + can_seek_forward_ = env->CallBooleanMethod(j_metadata.obj(),
|
| + get_boolean,
|
| + kSeekBackwardAvailable);
|
| + CheckException(env);
|
| + can_seek_backward_ = env->CallBooleanMethod(j_metadata.obj(),
|
| get_boolean,
|
| - kSeekBackwardAvailable);
|
| + kSeekForwardAvailable);
|
| CheckException(env);
|
| - *can_seek_forward = env->CallBooleanMethod(j_metadata.obj(),
|
| - get_boolean,
|
| - kSeekForwardAvailable);
|
| +}
|
| +
|
| +void MediaPlayerBridge::StartInternal() {
|
| + CallVoidMethod("start");
|
| + if (!time_update_timer_.IsRunning()) {
|
| + time_update_timer_.Start(
|
| + FROM_HERE,
|
| + base::TimeDelta::FromMilliseconds(kTimeUpdateInterval),
|
| + this, &MediaPlayerBridge::DoTimeUpdate);
|
| + }
|
| +}
|
| +
|
| +void MediaPlayerBridge::PauseInternal() {
|
| + CallVoidMethod("pause");
|
| + time_update_timer_.Stop();
|
| +}
|
| +
|
| +void MediaPlayerBridge::SeekInternal(base::TimeDelta time) {
|
| + JNIEnv* env = AttachCurrentThread();
|
| + CHECK(env);
|
| +
|
| + jmethodID method = GetMethodID(env, j_media_player_class_, "seekTo", "(I)V");
|
| + DCHECK(method);
|
| + int time_msec = static_cast<int>(time.InMilliseconds());
|
| + DCHECK_EQ(time.InMilliseconds(), static_cast<int64>(time_msec));
|
| + env->CallVoidMethod(j_media_player_.obj(),
|
| + method,
|
| + time_msec);
|
| CheckException(env);
|
| }
|
|
|
| @@ -352,10 +476,4 @@ int MediaPlayerBridge::CallIntMethod(std::string method_name) {
|
| return j_result;
|
| }
|
|
|
| -bool MediaPlayerBridge::RegisterMediaPlayerListener(JNIEnv* env) {
|
| - bool ret = RegisterNativesImpl(env);
|
| - DCHECK(g_MediaPlayerListener_clazz);
|
| - return ret;
|
| -}
|
| -
|
| } // namespace media
|
|
|