blob: 039769b82b101caa38d62eaf93a34b13dab118f8 [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 <utility>
#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "jni/MediaPlayerBridge_jni.h"
#include "media/base/android/media_common_android.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"
#include "media/base/timestamp_constants.h"
using base::android::ConvertUTF8ToJavaString;
using base::android::JavaParamRef;
using base::android::JavaRef;
using base::android::ScopedJavaLocalRef;
namespace media {
namespace {
enum UMAExitStatus {
UMA_EXIT_SUCCESS = 0,
UMA_EXIT_ERROR,
UMA_EXIT_STATUS_MAX = UMA_EXIT_ERROR,
};
} // namespace
MediaPlayerBridge::MediaPlayerBridge(
int player_id,
const GURL& url,
const GURL& site_for_cookies,
const std::string& user_agent,
bool hide_url_log,
MediaPlayerManager* manager,
const OnDecoderResourcesReleasedCB& on_decoder_resources_released_cb,
const GURL& frame_url,
bool allow_credentials)
: MediaPlayerAndroid(player_id,
manager,
on_decoder_resources_released_cb,
frame_url),
prepared_(false),
pending_play_(false),
should_seek_on_prepare_(false),
url_(url),
site_for_cookies_(site_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),
allow_credentials_(allow_credentials),
is_active_(false),
has_error_(false),
has_ever_started_(false),
weak_factory_(this) {}
MediaPlayerBridge::~MediaPlayerBridge() {
if (!j_media_player_bridge_.is_null()) {
JNIEnv* env = base::android::AttachCurrentThread();
CHECK(env);
Java_MediaPlayerBridge_destroy(env, j_media_player_bridge_);
}
Release();
if (has_ever_started_) {
UMA_HISTOGRAM_ENUMERATION("Media.Android.MediaPlayerSuccess",
has_error_ ? UMA_EXIT_ERROR : UMA_EXIT_SUCCESS,
UMA_EXIT_STATUS_MAX + 1);
}
}
void MediaPlayerBridge::Initialize() {
cookies_.clear();
if (url_.SchemeIsBlob()) {
NOTREACHED();
return;
}
if (allow_credentials_) {
media::MediaResourceGetter* resource_getter =
manager()->GetMediaResourceGetter();
resource_getter->GetCookies(
url_, site_for_cookies_,
base::BindOnce(&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)));
UpdateEffectiveVolume();
AttachListener(j_media_player_bridge_);
}
void MediaPlayerBridge::SetDuration(base::TimeDelta duration) {
duration_ = duration;
}
void MediaPlayerBridge::SetVideoSurface(gl::ScopedJavaSurface surface) {
surface_ = std::move(surface);
if (j_media_player_bridge_.is_null())
return;
JNIEnv* env = base::android::AttachCurrentThread();
CHECK(env);
Java_MediaPlayerBridge_setSurface(env, j_media_player_bridge_,
surface_.j_surface());
}
void MediaPlayerBridge::Prepare() {
DCHECK(j_media_player_bridge_.is_null());
if (url_.SchemeIsBlob()) {
NOTREACHED();
return;
}
CreateJavaMediaPlayerBridge();
if (url_.SchemeIsFileSystem()) {
manager()->GetMediaResourceGetter()->GetPlatformPathFromURL(
url_, base::BindOnce(&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_t offset;
int64_t size;
if (InterceptMediaUrl(url, &fd, &offset, &size)) {
if (!Java_MediaPlayerBridge_setDataSourceFromFd(env, j_media_player_bridge_,
fd, offset, size)) {
OnMediaError(MEDIA_ERROR_FORMAT);
return;
}
} else {
// Create a Java String for the URL.
ScopedJavaLocalRef<jstring> j_url_string =
ConvertUTF8ToJavaString(env, url);
const std::string data_uri_prefix("data:");
if (base::StartsWith(url, data_uri_prefix, base::CompareCase::SENSITIVE)) {
if (!Java_MediaPlayerBridge_setDataUriDataSource(
env, j_media_player_bridge_, j_url_string)) {
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_,
j_url_string, j_cookies,
j_user_agent, hide_url_log_)) {
OnMediaError(MEDIA_ERROR_FORMAT);
return;
}
}
if (!Java_MediaPlayerBridge_prepareAsync(env, j_media_player_bridge_))
OnMediaError(MEDIA_ERROR_FORMAT);
}
bool MediaPlayerBridge::InterceptMediaUrl(const std::string& url,
int* fd,
int64_t* offset,
int64_t* 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,
const JavaParamRef<jobject>& obj,
jboolean success) {
if (!success) {
OnMediaError(MEDIA_ERROR_FORMAT);
return;
}
if (!Java_MediaPlayerBridge_prepareAsync(env, j_media_player_bridge_))
OnMediaError(MEDIA_ERROR_FORMAT);
}
void MediaPlayerBridge::OnCookiesRetrieved(const std::string& cookies) {
cookies_ = cookies;
manager()->GetMediaResourceGetter()->GetAuthCredentials(
url_,
base::Bind(&MediaPlayerBridge::OnAuthCredentialsRetrieved,
weak_factory_.GetWeakPtr()));
}
void MediaPlayerBridge::OnAuthCredentialsRetrieved(
const base::string16& username, const base::string16& password) {
GURL::ReplacementsW replacements;
if (!username.empty()) {
replacements.SetUsernameStr(username);
if (!password.empty())
replacements.SetPasswordStr(password);
url_ = url_.ReplaceComponents(replacements);
}
}
void MediaPlayerBridge::Start() {
// A second Start() call after an error is considered another attempt for UMA
// and causes UMA reporting.
if (has_ever_started_ && has_error_) {
UMA_HISTOGRAM_ENUMERATION("Media.Android.MediaPlayerSuccess",
UMA_EXIT_ERROR, UMA_EXIT_STATUS_MAX + 1);
}
has_ever_started_ = true;
has_error_ = false;
is_active_ = true;
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;
}
is_active_ = 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_);
return result;
}
bool MediaPlayerBridge::HasVideo() const {
DCHECK(prepared_);
JNIEnv* env = base::android::AttachCurrentThread();
return Java_MediaPlayerBridge_hasVideo(env, j_media_player_bridge_);
}
bool MediaPlayerBridge::HasAudio() const {
DCHECK(prepared_);
JNIEnv* env = base::android::AttachCurrentThread();
return Java_MediaPlayerBridge_hasAudio(env, j_media_player_bridge_);
}
int MediaPlayerBridge::GetVideoWidth() {
if (!prepared_)
return width_;
JNIEnv* env = base::android::AttachCurrentThread();
return Java_MediaPlayerBridge_getVideoWidth(env, j_media_player_bridge_);
}
int MediaPlayerBridge::GetVideoHeight() {
if (!prepared_)
return height_;
JNIEnv* env = base::android::AttachCurrentThread();
return Java_MediaPlayerBridge_getVideoHeight(env, j_media_player_bridge_);
}
void MediaPlayerBridge::SeekTo(base::TimeDelta timestamp) {
// Record the time to seek when OnMediaPrepared() is called.
pending_seek_ = timestamp;
should_seek_on_prepare_ = true;
if (prepared_)
SeekInternal(GetCurrentTime(), 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_));
}
base::TimeDelta MediaPlayerBridge::GetDuration() {
if (!prepared_)
return duration_;
JNIEnv* env = base::android::AttachCurrentThread();
const int duration_ms =
Java_MediaPlayerBridge_getDuration(env, j_media_player_bridge_);
return duration_ms < 0 ? media::kInfiniteDuration
: base::TimeDelta::FromMilliseconds(duration_ms);
}
void MediaPlayerBridge::Release() {
is_active_ = false;
on_decoder_resources_released_cb_.Run(player_id());
if (j_media_player_bridge_.is_null())
return;
time_update_timer_.Stop();
if (prepared_) {
pending_seek_ = GetCurrentTime();
should_seek_on_prepare_ = true;
}
prepared_ = false;
pending_play_ = false;
SetVideoSurface(gl::ScopedJavaSurface());
JNIEnv* env = base::android::AttachCurrentThread();
Java_MediaPlayerBridge_release(env, j_media_player_bridge_);
j_media_player_bridge_.Reset();
DetachListener();
}
void MediaPlayerBridge::UpdateEffectiveVolumeInternal(double effective_volume) {
if (j_media_player_bridge_.is_null()) {
return;
}
JNIEnv* env = base::android::AttachCurrentThread();
CHECK(env);
Java_MediaPlayerBridge_setVolume(env, j_media_player_bridge_,
effective_volume);
}
void MediaPlayerBridge::OnVideoSizeChanged(int width, int height) {
width_ = width;
height_ = height;
MediaPlayerAndroid::OnVideoSizeChanged(width, height);
}
void MediaPlayerBridge::OnMediaError(int error_type) {
// Gather errors for UMA only in the active state.
// The MEDIA_ERROR_INVALID_CODE is reported by MediaPlayerListener.java in
// the situations that are considered normal, and is ignored by upper level.
if (is_active_ && error_type != MEDIA_ERROR_INVALID_CODE)
has_error_ = true;
// Do not propagate MEDIA_ERROR_SERVER_DIED. If it happens in the active state
// we want the playback to stall. It can be recovered by pressing the Play
// button again.
if (error_type == MEDIA_ERROR_SERVER_DIED)
error_type = MEDIA_ERROR_INVALID_CODE;
MediaPlayerAndroid::OnMediaError(error_type);
}
void MediaPlayerBridge::OnPlaybackComplete() {
time_update_timer_.Stop();
MediaPlayerAndroid::OnPlaybackComplete();
}
void MediaPlayerBridge::OnMediaInterrupted() {
time_update_timer_.Stop();
MediaPlayerAndroid::OnMediaInterrupted();
}
void MediaPlayerBridge::OnMediaPrepared() {
if (j_media_player_bridge_.is_null())
return;
prepared_ = true;
duration_ = GetDuration();
UpdateAllowedOperations();
// If media player was recovered from a saved state, consume all the pending
// events.
if (should_seek_on_prepare_) {
PendingSeekInternal(pending_seek_);
pending_seek_ = base::TimeDelta::FromMilliseconds(0);
should_seek_on_prepare_ = false;
}
if (!surface_.IsEmpty())
SetVideoSurface(std::move(surface_));
if (pending_play_) {
StartInternal();
pending_play_ = false;
}
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_);
}
void MediaPlayerBridge::UpdateAllowedOperations() {
JNIEnv* env = base::android::AttachCurrentThread();
CHECK(env);
ScopedJavaLocalRef<jobject> allowedOperations = GetAllowedOperations();
can_pause_ = Java_AllowedOperations_canPause(env, allowedOperations);
can_seek_forward_ =
Java_AllowedOperations_canSeekForward(env, allowedOperations);
can_seek_backward_ =
Java_AllowedOperations_canSeekBackward(env, allowedOperations);
}
void MediaPlayerBridge::StartInternal() {
if (!manager()->RequestPlay(player_id(), duration_, HasAudio())) {
Pause(true);
return;
}
JNIEnv* env = base::android::AttachCurrentThread();
Java_MediaPlayerBridge_start(env, j_media_player_bridge_);
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_);
time_update_timer_.Stop();
}
void MediaPlayerBridge::PendingSeekInternal(const base::TimeDelta& time) {
SeekInternal(GetCurrentTime(), time);
}
bool MediaPlayerBridge::SeekInternal(base::TimeDelta current_time,
base::TimeDelta time) {
// Seeking on content like live streams may cause the media player to
// get stuck in an error state.
if (time < current_time && !CanSeekBackward())
return false;
if (time >= current_time && !CanSeekForward())
return false;
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 false;
}
JNIEnv* env = base::android::AttachCurrentThread();
CHECK(env);
int time_msec = static_cast<int>(time.InMilliseconds());
Java_MediaPlayerBridge_seekTo(env, j_media_player_bridge_, time_msec);
return true;
}
void MediaPlayerBridge::OnTimeUpdateTimerFired() {
base::TimeDelta current_timestamp = GetCurrentTime();
if (last_time_update_timestamp_ == current_timestamp)
return;
manager()->OnTimeUpdate(player_id(), current_timestamp,
base::TimeTicks::Now());
last_time_update_timestamp_ = current_timestamp;
}
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::GetSiteForCookies() {
return site_for_cookies_;
}
} // namespace media