blob: a9b7d275003d7398675664b259c7cc0aa056528e [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/message_loop/message_loop_proxy.h"
#include "base/strings/string_util.h"
#include "jni/MediaPlayerBridge_jni.h"
#include "media/base/android/media_player_manager.h"
#include "media/base/android/media_resource_getter.h"
#include "media/base/android/media_url_interceptor.h"
using base::android::ConvertUTF8ToJavaString;
using base::android::ScopedJavaLocalRef;
// Time update happens every 250ms.
const int kTimeUpdateInterval = 250;
// blob url scheme.
const char kBlobScheme[] = "blob";
namespace media {
MediaPlayerBridge::MediaPlayerBridge(
int player_id,
const GURL& url,
const GURL& first_party_for_cookies,
const std::string& user_agent,
bool hide_url_log,
MediaPlayerManager* manager,
const RequestMediaResourcesCB& request_media_resources_cb,
const ReleaseMediaResourcesCB& release_media_resources_cb,
const GURL& frame_url,
bool allow_credentials)
: MediaPlayerAndroid(player_id,
manager,
request_media_resources_cb,
release_media_resources_cb,
frame_url),
prepared_(false),
pending_play_(false),
url_(url),
first_party_for_cookies_(first_party_for_cookies),
user_agent_(user_agent),
hide_url_log_(hide_url_log),
width_(0),
height_(0),
can_pause_(true),
can_seek_forward_(true),
can_seek_backward_(true),
is_surface_in_use_(false),
volume_(-1.0),
allow_credentials_(allow_credentials),
weak_factory_(this) {
listener_.reset(new MediaPlayerListener(base::MessageLoopProxy::current(),
weak_factory_.GetWeakPtr()));
}
MediaPlayerBridge::~MediaPlayerBridge() {
if (!j_media_player_bridge_.is_null()) {
JNIEnv* env = base::android::AttachCurrentThread();
CHECK(env);
Java_MediaPlayerBridge_destroy(env, j_media_player_bridge_.obj());
}
Release();
}
void MediaPlayerBridge::Initialize() {
cookies_.clear();
if (url_.SchemeIsFile()) {
ExtractMediaMetadata(url_.spec());
return;
}
media::MediaResourceGetter* resource_getter =
manager()->GetMediaResourceGetter();
if (url_.SchemeIsFileSystem() || url_.SchemeIs(kBlobScheme)) {
resource_getter->GetPlatformPathFromURL(
url_,
base::Bind(&MediaPlayerBridge::ExtractMediaMetadata,
weak_factory_.GetWeakPtr()));
return;
}
// Start extracting the metadata immediately if the request is anonymous.
// Otherwise, wait for user credentials to be retrieved first.
if (!allow_credentials_) {
ExtractMediaMetadata(url_.spec());
return;
}
resource_getter->GetCookies(url_,
first_party_for_cookies_,
base::Bind(&MediaPlayerBridge::OnCookiesRetrieved,
weak_factory_.GetWeakPtr()));
}
void MediaPlayerBridge::CreateJavaMediaPlayerBridge() {
JNIEnv* env = base::android::AttachCurrentThread();
CHECK(env);
j_media_player_bridge_.Reset(Java_MediaPlayerBridge_create(
env, reinterpret_cast<intptr_t>(this)));
if (volume_ >= 0)
SetVolume(volume_);
SetMediaPlayerListener();
}
void MediaPlayerBridge::SetJavaMediaPlayerBridge(
jobject j_media_player_bridge) {
JNIEnv* env = base::android::AttachCurrentThread();
CHECK(env);
j_media_player_bridge_.Reset(env, j_media_player_bridge);
}
base::android::ScopedJavaLocalRef<jobject> MediaPlayerBridge::
GetJavaMediaPlayerBridge() {
base::android::ScopedJavaLocalRef<jobject> j_bridge(
j_media_player_bridge_);
return j_bridge;
}
void MediaPlayerBridge::SetMediaPlayerListener() {
jobject j_context = base::android::GetApplicationContext();
DCHECK(j_context);
listener_->CreateMediaPlayerListener(j_context, j_media_player_bridge_.obj());
}
void MediaPlayerBridge::SetDuration(base::TimeDelta duration) {
duration_ = duration;
}
void MediaPlayerBridge::SetVideoSurface(gfx::ScopedJavaSurface surface) {
if (j_media_player_bridge_.is_null()) {
if (surface.IsEmpty())
return;
Prepare();
}
JNIEnv* env = base::android::AttachCurrentThread();
CHECK(env);
is_surface_in_use_ = true;
Java_MediaPlayerBridge_setSurface(
env, j_media_player_bridge_.obj(), surface.j_surface().obj());
}
void MediaPlayerBridge::Prepare() {
DCHECK(j_media_player_bridge_.is_null());
CreateJavaMediaPlayerBridge();
if (url_.SchemeIsFileSystem() || url_.SchemeIs(kBlobScheme)) {
manager()->GetMediaResourceGetter()->GetPlatformPathFromURL(
url_,
base::Bind(&MediaPlayerBridge::SetDataSource,
weak_factory_.GetWeakPtr()));
return;
}
SetDataSource(url_.spec());
}
void MediaPlayerBridge::SetDataSource(const std::string& url) {
if (j_media_player_bridge_.is_null())
return;
JNIEnv* env = base::android::AttachCurrentThread();
CHECK(env);
int fd;
int64 offset;
int64 size;
if (InterceptMediaUrl(url, &fd, &offset, &size)) {
if (!Java_MediaPlayerBridge_setDataSourceFromFd(
env, j_media_player_bridge_.obj(), fd, offset, size)) {
OnMediaError(MEDIA_ERROR_FORMAT);
return;
}
} else {
// Create a Java String for the URL.
ScopedJavaLocalRef<jstring> j_url_string =
ConvertUTF8ToJavaString(env, url);
jobject j_context = base::android::GetApplicationContext();
DCHECK(j_context);
const std::string data_uri_prefix("data:");
if (StartsWithASCII(url, data_uri_prefix, true)) {
if (!Java_MediaPlayerBridge_setDataUriDataSource(
env, j_media_player_bridge_.obj(), j_context, j_url_string.obj())) {
OnMediaError(MEDIA_ERROR_FORMAT);
}
return;
}
ScopedJavaLocalRef<jstring> j_cookies = ConvertUTF8ToJavaString(
env, cookies_);
ScopedJavaLocalRef<jstring> j_user_agent = ConvertUTF8ToJavaString(
env, user_agent_);
if (!Java_MediaPlayerBridge_setDataSource(
env, j_media_player_bridge_.obj(), j_context, j_url_string.obj(),
j_cookies.obj(), j_user_agent.obj(), hide_url_log_)) {
OnMediaError(MEDIA_ERROR_FORMAT);
return;
}
}
request_media_resources_cb_.Run(player_id());
if (!Java_MediaPlayerBridge_prepareAsync(env, j_media_player_bridge_.obj()))
OnMediaError(MEDIA_ERROR_FORMAT);
}
bool MediaPlayerBridge::InterceptMediaUrl(
const std::string& url, int* fd, int64* offset, int64* size) {
// Sentinel value to check whether the output arguments have been set.
const int kUnsetValue = -1;
*fd = kUnsetValue;
*offset = kUnsetValue;
*size = kUnsetValue;
media::MediaUrlInterceptor* url_interceptor =
manager()->GetMediaUrlInterceptor();
if (url_interceptor && url_interceptor->Intercept(url, fd, offset, size)) {
DCHECK_NE(kUnsetValue, *fd);
DCHECK_NE(kUnsetValue, *offset);
DCHECK_NE(kUnsetValue, *size);
return true;
}
return false;
}
void MediaPlayerBridge::OnDidSetDataUriDataSource(JNIEnv* env, jobject obj,
jboolean success) {
if (!success) {
OnMediaError(MEDIA_ERROR_FORMAT);
return;
}
request_media_resources_cb_.Run(player_id());
if (!Java_MediaPlayerBridge_prepareAsync(env, j_media_player_bridge_.obj()))
OnMediaError(MEDIA_ERROR_FORMAT);
}
void MediaPlayerBridge::OnCookiesRetrieved(const std::string& cookies) {
cookies_ = cookies;
ExtractMediaMetadata(url_.spec());
}
void MediaPlayerBridge::ExtractMediaMetadata(const std::string& url) {
int fd;
int64 offset;
int64 size;
if (InterceptMediaUrl(url, &fd, &offset, &size)) {
manager()->GetMediaResourceGetter()->ExtractMediaMetadata(
fd, offset, size,
base::Bind(&MediaPlayerBridge::OnMediaMetadataExtracted,
weak_factory_.GetWeakPtr()));
} else {
manager()->GetMediaResourceGetter()->ExtractMediaMetadata(
url, cookies_, user_agent_,
base::Bind(&MediaPlayerBridge::OnMediaMetadataExtracted,
weak_factory_.GetWeakPtr()));
}
}
void MediaPlayerBridge::OnMediaMetadataExtracted(
base::TimeDelta duration, int width, int height, bool success) {
if (success) {
duration_ = duration;
width_ = width;
height_ = height;
}
manager()->OnMediaMetadataChanged(
player_id(), duration_, width_, height_, success);
}
void MediaPlayerBridge::Start() {
if (j_media_player_bridge_.is_null()) {
pending_play_ = true;
Prepare();
} else {
if (prepared_)
StartInternal();
else
pending_play_ = true;
}
}
void MediaPlayerBridge::Pause(bool is_media_related_action) {
if (j_media_player_bridge_.is_null()) {
pending_play_ = false;
} else {
if (prepared_ && IsPlaying())
PauseInternal();
else
pending_play_ = false;
}
}
bool MediaPlayerBridge::IsPlaying() {
if (!prepared_)
return pending_play_;
JNIEnv* env = base::android::AttachCurrentThread();
CHECK(env);
jboolean result = Java_MediaPlayerBridge_isPlaying(
env, j_media_player_bridge_.obj());
return result;
}
int MediaPlayerBridge::GetVideoWidth() {
if (!prepared_)
return width_;
JNIEnv* env = base::android::AttachCurrentThread();
return Java_MediaPlayerBridge_getVideoWidth(
env, j_media_player_bridge_.obj());
}
int MediaPlayerBridge::GetVideoHeight() {
if (!prepared_)
return height_;
JNIEnv* env = base::android::AttachCurrentThread();
return Java_MediaPlayerBridge_getVideoHeight(
env, j_media_player_bridge_.obj());
}
void MediaPlayerBridge::SeekTo(base::TimeDelta timestamp) {
// Record the time to seek when OnMediaPrepared() is called.
pending_seek_ = timestamp;
if (j_media_player_bridge_.is_null())
Prepare();
else if (prepared_)
SeekInternal(timestamp);
}
base::TimeDelta MediaPlayerBridge::GetCurrentTime() {
if (!prepared_)
return pending_seek_;
JNIEnv* env = base::android::AttachCurrentThread();
return base::TimeDelta::FromMilliseconds(
Java_MediaPlayerBridge_getCurrentPosition(
env, j_media_player_bridge_.obj()));
}
base::TimeDelta MediaPlayerBridge::GetDuration() {
if (!prepared_)
return duration_;
JNIEnv* env = base::android::AttachCurrentThread();
return base::TimeDelta::FromMilliseconds(
Java_MediaPlayerBridge_getDuration(
env, j_media_player_bridge_.obj()));
}
void MediaPlayerBridge::Release() {
if (j_media_player_bridge_.is_null())
return;
time_update_timer_.Stop();
if (prepared_)
pending_seek_ = GetCurrentTime();
prepared_ = false;
pending_play_ = false;
is_surface_in_use_ = false;
SetVideoSurface(gfx::ScopedJavaSurface());
JNIEnv* env = base::android::AttachCurrentThread();
Java_MediaPlayerBridge_release(env, j_media_player_bridge_.obj());
j_media_player_bridge_.Reset();
release_media_resources_cb_.Run(player_id());
listener_->ReleaseMediaPlayerListenerResources();
}
void MediaPlayerBridge::SetVolume(double volume) {
if (j_media_player_bridge_.is_null()) {
volume_ = volume;
return;
}
JNIEnv* env = base::android::AttachCurrentThread();
CHECK(env);
Java_MediaPlayerBridge_setVolume(
env, j_media_player_bridge_.obj(), volume);
}
void MediaPlayerBridge::OnVideoSizeChanged(int width, int height) {
width_ = width;
height_ = height;
manager()->OnVideoSizeChanged(player_id(), width, height);
}
void MediaPlayerBridge::OnMediaError(int error_type) {
manager()->OnError(player_id(), error_type);
}
void MediaPlayerBridge::OnBufferingUpdate(int percent) {
manager()->OnBufferingUpdate(player_id(), percent);
}
void MediaPlayerBridge::OnPlaybackComplete() {
time_update_timer_.Stop();
manager()->OnPlaybackComplete(player_id());
}
void MediaPlayerBridge::OnMediaInterrupted() {
time_update_timer_.Stop();
manager()->OnMediaInterrupted(player_id());
}
void MediaPlayerBridge::OnSeekComplete() {
manager()->OnSeekComplete(player_id(), GetCurrentTime());
}
void MediaPlayerBridge::OnMediaPrepared() {
if (j_media_player_bridge_.is_null())
return;
prepared_ = true;
duration_ = GetDuration();
// If media player was recovered from a saved state, consume all the pending
// events.
PendingSeekInternal(pending_seek_);
if (pending_play_) {
StartInternal();
pending_play_ = false;
}
UpdateAllowedOperations();
manager()->OnMediaMetadataChanged(
player_id(), duration_, width_, height_, true);
}
ScopedJavaLocalRef<jobject> MediaPlayerBridge::GetAllowedOperations() {
JNIEnv* env = base::android::AttachCurrentThread();
CHECK(env);
return Java_MediaPlayerBridge_getAllowedOperations(
env, j_media_player_bridge_.obj());
}
void MediaPlayerBridge::UpdateAllowedOperations() {
JNIEnv* env = base::android::AttachCurrentThread();
CHECK(env);
ScopedJavaLocalRef<jobject> allowedOperations = GetAllowedOperations();
can_pause_ = Java_AllowedOperations_canPause(env, allowedOperations.obj());
can_seek_forward_ = Java_AllowedOperations_canSeekForward(
env, allowedOperations.obj());
can_seek_backward_ = Java_AllowedOperations_canSeekBackward(
env, allowedOperations.obj());
}
void MediaPlayerBridge::StartInternal() {
JNIEnv* env = base::android::AttachCurrentThread();
Java_MediaPlayerBridge_start(env, j_media_player_bridge_.obj());
if (!time_update_timer_.IsRunning()) {
time_update_timer_.Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(kTimeUpdateInterval),
this, &MediaPlayerBridge::OnTimeUpdateTimerFired);
}
}
void MediaPlayerBridge::PauseInternal() {
JNIEnv* env = base::android::AttachCurrentThread();
Java_MediaPlayerBridge_pause(env, j_media_player_bridge_.obj());
time_update_timer_.Stop();
}
void MediaPlayerBridge::PendingSeekInternal(const base::TimeDelta& time) {
SeekInternal(time);
}
void MediaPlayerBridge::SeekInternal(base::TimeDelta time) {
if (time > duration_)
time = duration_;
// Seeking to an invalid position may cause media player to stuck in an
// error state.
if (time < base::TimeDelta()) {
DCHECK_EQ(-1.0, time.InMillisecondsF());
return;
}
JNIEnv* env = base::android::AttachCurrentThread();
CHECK(env);
int time_msec = static_cast<int>(time.InMilliseconds());
Java_MediaPlayerBridge_seekTo(
env, j_media_player_bridge_.obj(), time_msec);
}
void MediaPlayerBridge::OnTimeUpdateTimerFired() {
manager()->OnTimeUpdate(player_id(), GetCurrentTime());
}
bool MediaPlayerBridge::RegisterMediaPlayerBridge(JNIEnv* env) {
bool ret = RegisterNativesImpl(env);
DCHECK(g_MediaPlayerBridge_clazz);
return ret;
}
bool MediaPlayerBridge::CanPause() {
return can_pause_;
}
bool MediaPlayerBridge::CanSeekForward() {
return can_seek_forward_;
}
bool MediaPlayerBridge::CanSeekBackward() {
return can_seek_backward_;
}
bool MediaPlayerBridge::IsPlayerReady() {
return prepared_;
}
GURL MediaPlayerBridge::GetUrl() {
return url_;
}
GURL MediaPlayerBridge::GetFirstPartyForCookies() {
return first_party_for_cookies_;
}
bool MediaPlayerBridge::IsSurfaceInUse() const {
return is_surface_in_use_;
}
} // namespace media