blob: aa6de11a47851b628f5da0c7ace0c27287adfd19 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/base/android/media_player_bridge.h"
#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/basictypes.h"
#include "base/logging.h"
#include "base/stringprintf.h"
#include "base/message_loop_proxy.h"
#include "jni/MediaPlayerBridge_jni.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;
using base::android::ConvertUTF8ToJavaString;
using base::android::GetClass;
using base::android::GetMethodID;
using base::android::JavaRef;
using base::android::ScopedJavaLocalRef;
// These constants are from the android source tree and need to be kept in
// sync with android/media/MediaMetadata.java.
static const jint kPauseAvailable = 1;
static const jint kSeekBackwardAvailable = 2;
static const jint kSeekForwardAvailable = 3;
// 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(
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);
j_media_player_class_.Reset(GetClass(env, "android/media/MediaPlayer"));
jmethodID constructor = GetMethodID(env,
j_media_player_class_,
"<init>",
"()V");
ScopedJavaLocalRef<jobject> tmp(
env, env->NewObject(j_media_player_class_.obj(), constructor));
j_media_player_.Reset(tmp);
jobject j_context = base::android::GetApplicationContext();
DCHECK(j_context);
listener_.CreateMediaPlayerListener(j_context, j_media_player_.obj());
}
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::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_cookies = ConvertUTF8ToJavaString(
env, cookies_);
jobject j_context = base::android::GetApplicationContext();
DCHECK(j_context);
if (Java_MediaPlayerBridge_setDataSource(
env, j_media_player_.obj(), j_context, j_url_string.obj(),
j_cookies.obj(), hide_url_log_)) {
if (manager_)
manager_->RequestMediaResources(this);
CallVoidMethod("prepareAsync");
} else {
media_error_cb_.Run(player_id_, MEDIA_ERROR_UNKNOWN);
}
}
void MediaPlayerBridge::Start() {
if (j_media_player_.is_null()) {
pending_play_ = true;
Prepare();
} else {
if (prepared_)
StartInternal();
else
pending_play_ = true;
}
}
void MediaPlayerBridge::Pause() {
if (j_media_player_.is_null()) {
pending_play_ = false;
} else {
if (prepared_ && IsPlaying())
PauseInternal();
else
pending_play_ = false;
}
}
bool MediaPlayerBridge::IsPlaying() {
if (!prepared_)
return pending_play_;
JNIEnv* env = AttachCurrentThread();
CHECK(env);
jmethodID method = GetMethodID(env, j_media_player_class_, "isPlaying",
"()Z");
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) {
// Record the time to seek when OnMediaPrepared() is called.
pending_seek_ = time;
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::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);
jmethodID method = GetMethodID(env,
j_media_player_class_,
"setVolume",
"(FF)V");
DCHECK(method);
env->CallVoidMethod(j_media_player_.obj(), method,
left_volume, right_volume);
CheckException(env);
}
void MediaPlayerBridge::DoTimeUpdate() {
base::TimeDelta current = GetCurrentTime();
time_update_cb_.Run(player_id_, current);
}
void MediaPlayerBridge::OnMediaError(int error_type) {
media_error_cb_.Run(player_id_, error_type);
}
void MediaPlayerBridge::OnVideoSizeChanged(int width, int height) {
width_ = width;
height_ = height;
video_size_changed_cb_.Run(player_id_, width, height);
}
void MediaPlayerBridge::OnBufferingUpdate(int percent) {
buffering_update_cb_.Run(player_id_, percent);
}
void MediaPlayerBridge::OnPlaybackComplete() {
time_update_timer_.Stop();
playback_complete_cb_.Run(player_id_);
}
void MediaPlayerBridge::OnSeekComplete() {
seek_complete_cb_.Run(player_id_, GetCurrentTime());
}
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;
}
GetMetadata();
media_prepared_cb_.Run(player_id_, duration_);
}
void MediaPlayerBridge::GetMetadata() {
JNIEnv* env = AttachCurrentThread();
CHECK(env);
jmethodID method = GetMethodID(env,
j_media_player_class_,
"getMetadata",
"(ZZ)Landroid/media/Metadata;");
ScopedJavaLocalRef<jobject> j_metadata(env,
env->CallObjectMethod(j_media_player_.obj(),
method, JNI_FALSE, JNI_FALSE));
CheckException(env);
if (j_metadata.is_null())
return;
ScopedJavaLocalRef<jclass> cls(GetClass(env, "android/media/Metadata"));
jmethodID get_boolean = GetMethodID(env, cls, "getBoolean", "(I)Z");
can_pause_ = env->CallBooleanMethod(j_metadata.obj(),
get_boolean,
kPauseAvailable);
CheckException(env);
can_seek_forward_ = env->CallBooleanMethod(j_metadata.obj(),
get_boolean,
kSeekBackwardAvailable);
CheckException(env);
can_seek_backward_ = env->CallBooleanMethod(j_metadata.obj(),
get_boolean,
kSeekForwardAvailable);
CheckException(env);
}
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);
}
// ---- JNI Helpers for repeated call patterns. ----
void MediaPlayerBridge::CallVoidMethod(std::string method_name) {
JNIEnv* env = AttachCurrentThread();
CHECK(env);
jmethodID method = GetMethodID(env,
j_media_player_class_,
method_name.c_str(),
"()V");
env->CallVoidMethod(j_media_player_.obj(), method);
CheckException(env);
}
int MediaPlayerBridge::CallIntMethod(std::string method_name) {
JNIEnv* env = AttachCurrentThread();
CHECK(env);
jmethodID method = GetMethodID(env,
j_media_player_class_,
method_name.c_str(),
"()I");
jint j_result = env->CallIntMethod(j_media_player_.obj(), method);
CheckException(env);
return j_result;
}
bool MediaPlayerBridge::RegisterMediaPlayerBridge(JNIEnv* env) {
bool ret = RegisterNativesImpl(env);
DCHECK(g_MediaPlayerBridge_clazz);
return ret;
}
} // namespace media