diff options
author | Torne (Richard Coles) <torne@google.com> | 2012-11-14 11:43:16 +0000 |
---|---|---|
committer | Torne (Richard Coles) <torne@google.com> | 2012-11-14 11:43:16 +0000 |
commit | 5821806d5e7f356e8fa4b058a389a808ea183019 (patch) | |
tree | e19f4793aac92e2c0d9a01087019a60d6657d838 /media/base/android | |
parent | 8e79a8efe247f109aafd917a69e8a392961b3687 (diff) | |
download | chromium_org-5821806d5e7f356e8fa4b058a389a808ea183019.tar.gz |
Merge from Chromium at DEPS revision r167172
This commit was generated by merge_to_master.py.
Change-Id: Ib8d56fd5ae39a2d7e8c91dcd76cc6d13f25f2aab
Diffstat (limited to 'media/base/android')
-rw-r--r-- | media/base/android/OWNERS | 4 | ||||
-rw-r--r-- | media/base/android/cookie_getter.cc | 11 | ||||
-rw-r--r-- | media/base/android/cookie_getter.h | 30 | ||||
-rw-r--r-- | media/base/android/java/src/org/chromium/media/MediaPlayerBridge.java | 35 | ||||
-rw-r--r-- | media/base/android/java/src/org/chromium/media/MediaPlayerListener.java | 138 | ||||
-rw-r--r-- | media/base/android/media_jni_registrar.cc | 28 | ||||
-rw-r--r-- | media/base/android/media_jni_registrar.h | 19 | ||||
-rw-r--r-- | media/base/android/media_player_bridge.cc | 383 | ||||
-rw-r--r-- | media/base/android/media_player_bridge.h | 224 | ||||
-rw-r--r-- | media/base/android/media_player_bridge_manager.cc | 11 | ||||
-rw-r--r-- | media/base/android/media_player_bridge_manager.h | 34 | ||||
-rw-r--r-- | media/base/android/media_player_listener.cc | 86 | ||||
-rw-r--r-- | media/base/android/media_player_listener.h | 62 |
13 files changed, 1065 insertions, 0 deletions
diff --git a/media/base/android/OWNERS b/media/base/android/OWNERS new file mode 100644 index 0000000000..5a000138b9 --- /dev/null +++ b/media/base/android/OWNERS @@ -0,0 +1,4 @@ +bulach@chromium.org +jcivelli@chromium.org +jrg@chromium.org +yfriedman@chromium.org diff --git a/media/base/android/cookie_getter.cc b/media/base/android/cookie_getter.cc new file mode 100644 index 0000000000..1db105a6e2 --- /dev/null +++ b/media/base/android/cookie_getter.cc @@ -0,0 +1,11 @@ +// 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/cookie_getter.h" + +namespace media { + +CookieGetter::~CookieGetter() {} + +} // namespace media diff --git a/media/base/android/cookie_getter.h b/media/base/android/cookie_getter.h new file mode 100644 index 0000000000..bf7cc55d7f --- /dev/null +++ b/media/base/android/cookie_getter.h @@ -0,0 +1,30 @@ +// 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. + +#ifndef MEDIA_BASE_ANDROID_COOKIE_GETTER_H_ +#define MEDIA_BASE_ANDROID_COOKIE_GETTER_H_ + +#include <string> + +#include "base/callback.h" +#include "media/base/media_export.h" + +namespace media { + +// Class for asynchronously retrieving the cookies for a given URL. +class MEDIA_EXPORT CookieGetter { + public: + typedef base::Callback<void(const std::string&)> GetCookieCB; + virtual ~CookieGetter(); + + // Method for getting the cookies for a given URL. + // The callback is executed on the caller's thread. + virtual void GetCookies(const std::string& url, + const std::string& first_party_for_cookies, + const GetCookieCB& callback) = 0; +}; + +} // namespace media + +#endif // MEDIA_BASE_ANDROID_COOKIE_GETTER_H_ diff --git a/media/base/android/java/src/org/chromium/media/MediaPlayerBridge.java b/media/base/android/java/src/org/chromium/media/MediaPlayerBridge.java new file mode 100644 index 0000000000..cd3a2b5dc3 --- /dev/null +++ b/media/base/android/java/src/org/chromium/media/MediaPlayerBridge.java @@ -0,0 +1,35 @@ +// 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. + +package org.chromium.media; + +import android.content.Context; +import android.media.MediaPlayer; +import android.net.Uri; +import android.text.TextUtils; + +import java.util.HashMap; + +import org.chromium.base.CalledByNative; +import org.chromium.base.JNINamespace; + +@JNINamespace("media") +class MediaPlayerBridge { + @CalledByNative + private static boolean setDataSource(MediaPlayer player, Context context, String url, + String cookies, boolean hideUrlLog) { + Uri uri = Uri.parse(url); + HashMap headersMap = new HashMap<String, String>(); + if (hideUrlLog) + headersMap.put("x-hide-urls-from-log", "true"); + if (!TextUtils.isEmpty(cookies)) + headersMap.put("Cookie", cookies); + try { + player.setDataSource(context, uri, headersMap); + return true; + } catch (Exception e) { + return false; + } + } +} diff --git a/media/base/android/java/src/org/chromium/media/MediaPlayerListener.java b/media/base/android/java/src/org/chromium/media/MediaPlayerListener.java new file mode 100644 index 0000000000..043dfb92ea --- /dev/null +++ b/media/base/android/java/src/org/chromium/media/MediaPlayerListener.java @@ -0,0 +1,138 @@ +// 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. + +package org.chromium.media; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.Manifest.permission; +import android.media.MediaPlayer; + +import org.chromium.base.CalledByNative; +import org.chromium.base.JNINamespace; + +// This class implements all the listener interface for android mediaplayer. +// Callbacks will be sent to the native class for processing. +@JNINamespace("media") +class MediaPlayerListener implements MediaPlayer.OnPreparedListener, + MediaPlayer.OnCompletionListener, + MediaPlayer.OnBufferingUpdateListener, + MediaPlayer.OnSeekCompleteListener, + MediaPlayer.OnVideoSizeChangedListener, + MediaPlayer.OnErrorListener { + // These values are mirrored as enums in media/base/android/media_player_bridge.h. + // Please ensure they stay in sync. + private static final int MEDIA_ERROR_FORMAT = 0; + private static final int MEDIA_ERROR_DECODE = 1; + private static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 2; + private static final int MEDIA_ERROR_INVALID_CODE = 3; + + // These values are copied from android media player. + public static final int MEDIA_ERROR_MALFORMED = -1007; + public static final int MEDIA_ERROR_TIMED_OUT = -110; + + // Used to determine the class instance to dispatch the native call to. + private int mNativeMediaPlayerListener = 0; + + private MediaPlayerListener(int nativeMediaPlayerListener) { + mNativeMediaPlayerListener = nativeMediaPlayerListener; + } + + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + int errorType; + switch (what) { + case MediaPlayer.MEDIA_ERROR_UNKNOWN: + switch (extra) { + case MEDIA_ERROR_MALFORMED: + errorType = MEDIA_ERROR_DECODE; + break; + case MEDIA_ERROR_TIMED_OUT: + errorType = MEDIA_ERROR_INVALID_CODE; + break; + default: + errorType = MEDIA_ERROR_FORMAT; + break; + } + break; + case MediaPlayer.MEDIA_ERROR_SERVER_DIED: + errorType = MEDIA_ERROR_DECODE; + break; + case MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK: + errorType = MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK; + break; + default: + // There are some undocumented error codes for android media player. + // For example, when surfaceTexture got deleted before we setVideoSuface + // to NULL, mediaplayer will report error -38. These errors should be ignored + // and not be treated as an error to webkit. + errorType = MEDIA_ERROR_INVALID_CODE; + break; + } + nativeOnMediaError(mNativeMediaPlayerListener, errorType); + return true; + } + + @Override + public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { + nativeOnVideoSizeChanged(mNativeMediaPlayerListener, width, height); + } + + @Override + public void onSeekComplete(MediaPlayer mp) { + nativeOnSeekComplete(mNativeMediaPlayerListener); + } + + @Override + public void onBufferingUpdate(MediaPlayer mp, int percent) { + nativeOnBufferingUpdate(mNativeMediaPlayerListener, percent); + } + + @Override + public void onCompletion(MediaPlayer mp) { + nativeOnPlaybackComplete(mNativeMediaPlayerListener); + } + + @Override + public void onPrepared(MediaPlayer mp) { + nativeOnMediaPrepared(mNativeMediaPlayerListener); + } + + @CalledByNative + private static void create(int nativeMediaPlayerListener, + Context context, MediaPlayer mediaPlayer) { + MediaPlayerListener listener = new MediaPlayerListener(nativeMediaPlayerListener); + mediaPlayer.setOnBufferingUpdateListener(listener); + mediaPlayer.setOnCompletionListener(listener); + mediaPlayer.setOnErrorListener(listener); + mediaPlayer.setOnPreparedListener(listener); + mediaPlayer.setOnSeekCompleteListener(listener); + mediaPlayer.setOnVideoSizeChangedListener(listener); + if (PackageManager.PERMISSION_GRANTED == + context.checkCallingPermission(permission.WAKE_LOCK)) { + mediaPlayer.setWakeMode(context, android.os.PowerManager.FULL_WAKE_LOCK); + } + } + + /** + * See media/base/android/media_player_listener.cc for all the following functions. + */ + private native void nativeOnMediaError( + int nativeMediaPlayerListener, + int errorType); + + private native void nativeOnVideoSizeChanged( + int nativeMediaPlayerListener, + int width, int height); + + private native void nativeOnBufferingUpdate( + int nativeMediaPlayerListener, + int percent); + + private native void nativeOnMediaPrepared(int nativeMediaPlayerListener); + + private native void nativeOnPlaybackComplete(int nativeMediaPlayerListener); + + private native void nativeOnSeekComplete(int nativeMediaPlayerListener); +} diff --git a/media/base/android/media_jni_registrar.cc b/media/base/android/media_jni_registrar.cc new file mode 100644 index 0000000000..671b6790ef --- /dev/null +++ b/media/base/android/media_jni_registrar.cc @@ -0,0 +1,28 @@ +// 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_jni_registrar.h" + +#include "base/basictypes.h" +#include "base/android/jni_android.h" +#include "base/android/jni_registrar.h" + +#include "media/base/android/media_player_bridge.h" +#include "media/base/android/media_player_listener.h" + +namespace media { + +static base::android::RegistrationMethod kMediaRegisteredMethods[] = { + { "MediaPlayerBridge", + MediaPlayerBridge::RegisterMediaPlayerBridge }, + { "MediaPlayerListener", + MediaPlayerListener::RegisterMediaPlayerListener }, +}; + +bool RegisterJni(JNIEnv* env) { + return base::android::RegisterNativeMethods( + env, kMediaRegisteredMethods, arraysize(kMediaRegisteredMethods)); +} + +} // namespace media diff --git a/media/base/android/media_jni_registrar.h b/media/base/android/media_jni_registrar.h new file mode 100644 index 0000000000..7e937028f8 --- /dev/null +++ b/media/base/android/media_jni_registrar.h @@ -0,0 +1,19 @@ +// 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. + +#ifndef MEDIA_BASE_ANDROID_MEDIA_JNI_REGISTRAR_H_ +#define MEDIA_BASE_ANDROID_MEDIA_JNI_REGISTRAR_H_ + +#include <jni.h> + +#include "media/base/media_export.h" + +namespace media { + +// Register all JNI bindings necessary for media. +MEDIA_EXPORT bool RegisterJni(JNIEnv* env); + +} // namespace media + +#endif // MEDIA_BASE_ANDROID_MEDIA_JNI_REGISTRAR_H_ diff --git a/media/base/android/media_player_bridge.cc b/media/base/android/media_player_bridge.cc new file mode 100644 index 0000000000..ad5e1618c1 --- /dev/null +++ b/media/base/android/media_player_bridge.cc @@ -0,0 +1,383 @@ +// 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 "jni/MediaPlayer_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::JavaRef; +using base::android::MethodID; +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_.Reset(JNI_MediaPlayer::Java_MediaPlayer_Constructor(env)); + + 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); + + JNI_MediaPlayer::Java_MediaPlayer_setSurface( + env, j_media_player_.obj(), surface); +} + +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); + JNI_MediaPlayer::Java_MediaPlayer_prepareAsync( + env, j_media_player_.obj()); + } else { + media_error_cb_.Run(player_id_, MEDIA_ERROR_FORMAT); + } +} + +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); + jboolean result = JNI_MediaPlayer::Java_MediaPlayer_isPlaying( + env, j_media_player_.obj()); + return result; +} + +int MediaPlayerBridge::GetVideoWidth() { + if (!prepared_) + return width_; + JNIEnv* env = AttachCurrentThread(); + return JNI_MediaPlayer::Java_MediaPlayer_getVideoWidth( + env, j_media_player_.obj()); +} + +int MediaPlayerBridge::GetVideoHeight() { + if (!prepared_) + return height_; + JNIEnv* env = AttachCurrentThread(); + return JNI_MediaPlayer::Java_MediaPlayer_getVideoHeight( + env, j_media_player_.obj()); +} + +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_; + JNIEnv* env = AttachCurrentThread(); + return base::TimeDelta::FromMilliseconds( + JNI_MediaPlayer::Java_MediaPlayer_getCurrentPosition( + env, j_media_player_.obj())); +} + +base::TimeDelta MediaPlayerBridge::GetDuration() { + if (!prepared_) + return duration_; + JNIEnv* env = AttachCurrentThread(); + return base::TimeDelta::FromMilliseconds( + JNI_MediaPlayer::Java_MediaPlayer_getDuration( + env, j_media_player_.obj())); +} + +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); + + JNIEnv* env = AttachCurrentThread(); + JNI_MediaPlayer::Java_MediaPlayer_release(env, j_media_player_.obj()); + 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); + JNI_MediaPlayer::Java_MediaPlayer_setVolume( + env, j_media_player_.obj(), left_volume, right_volume); +} + +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); + + ScopedJavaLocalRef<jclass> media_player_class( + GetClass(env, "android/media/MediaPlayer")); + jmethodID method = MethodID::Get<MethodID::TYPE_INSTANCE>( + env, media_player_class.obj(), "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> metadata_class( + GetClass(env, "android/media/Metadata")); + jmethodID get_boolean = MethodID::Get<MethodID::TYPE_INSTANCE>( + env, metadata_class.obj(), "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() { + JNIEnv* env = AttachCurrentThread(); + JNI_MediaPlayer::Java_MediaPlayer_start(env, j_media_player_.obj()); + if (!time_update_timer_.IsRunning()) { + time_update_timer_.Start( + FROM_HERE, + base::TimeDelta::FromMilliseconds(kTimeUpdateInterval), + this, &MediaPlayerBridge::DoTimeUpdate); + } +} + +void MediaPlayerBridge::PauseInternal() { + JNIEnv* env = AttachCurrentThread(); + JNI_MediaPlayer::Java_MediaPlayer_pause(env, j_media_player_.obj()); + time_update_timer_.Stop(); +} + +void MediaPlayerBridge::SeekInternal(base::TimeDelta time) { + JNIEnv* env = AttachCurrentThread(); + CHECK(env); + + int time_msec = static_cast<int>(time.InMilliseconds()); + JNI_MediaPlayer::Java_MediaPlayer_seekTo( + env, j_media_player_.obj(), time_msec); +} + +bool MediaPlayerBridge::RegisterMediaPlayerBridge(JNIEnv* env) { + bool ret = RegisterNativesImpl(env); + DCHECK(g_MediaPlayerBridge_clazz); + if (ret) + ret = JNI_MediaPlayer::RegisterNativesImpl(env); + return ret; +} + +} // namespace media diff --git a/media/base/android/media_player_bridge.h b/media/base/android/media_player_bridge.h new file mode 100644 index 0000000000..820707f253 --- /dev/null +++ b/media/base/android/media_player_bridge.h @@ -0,0 +1,224 @@ +// 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. + +#ifndef MEDIA_BASE_ANDROID_MEDIA_PLAYER_BRIDGE_H_ +#define MEDIA_BASE_ANDROID_MEDIA_PLAYER_BRIDGE_H_ + +#include <jni.h> +#include <map> +#include <string> + +#include "base/android/scoped_java_ref.h" +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/time.h" +#include "base/timer.h" +#include "media/base/media_export.h" +#include "media/base/android/media_player_listener.h" + +namespace media { + +class CookieGetter; +class MediaPlayerBridgeManager; + +// This class serves as a bridge for native code to call java functions inside +// android mediaplayer class. For more information on android mediaplayer, check +// http://developer.android.com/reference/android/media/MediaPlayer.html +// The actual android mediaplayer instance is created lazily when Start(), +// Pause(), SeekTo() gets called. As a result, media information may not +// be available until one of those operations is performed. After that, we +// will cache those information in case the mediaplayer gets released. +class MEDIA_EXPORT MediaPlayerBridge { + public: + // Error types for MediaErrorCB. + enum MediaErrorType { + MEDIA_ERROR_FORMAT, + MEDIA_ERROR_DECODE, + MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK, + MEDIA_ERROR_INVALID_CODE, + }; + + // Callback when error happens. Args: player ID, error type. + typedef base::Callback<void(int, int)> MediaErrorCB; + + // Callback when video size has changed. Args: player ID, width, height. + typedef base::Callback<void(int, int, int)> VideoSizeChangedCB; + + // Callback when buffering has changed. Args: player ID, percentage + // of the media. + typedef base::Callback<void(int, int)> BufferingUpdateCB; + + // Callback when player got prepared. Args: player ID, duration of the media. + typedef base::Callback<void(int, base::TimeDelta)> MediaPreparedCB; + + // Callbacks when seek completed. Args: player ID, current time. + typedef base::Callback<void(int, base::TimeDelta)> SeekCompleteCB; + + // Callbacks when playback completed. Args: player ID. + typedef base::Callback<void(int)> PlaybackCompleteCB; + + // Callback when time update messages need to be sent. Args: player ID, + // current time. + typedef base::Callback<void(int, base::TimeDelta)> TimeUpdateCB; + + static bool RegisterMediaPlayerBridge(JNIEnv* env); + + // Construct a MediaPlayerBridge object with all the needed media player + // callbacks. This object needs to call |manager|'s RequestMediaResources() + // before decoding the media stream. This allows |manager| to track + // unused resources and free them when needed. On the other hand, it needs + // to call ReleaseMediaResources() when it is done with decoding. + 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); + ~MediaPlayerBridge(); + + typedef std::map<std::string, std::string> HeadersMap; + + void SetVideoSurface(jobject surface); + + // Start playing the media. + void Start(); + + // Pause the media. + void Pause(); + + // Seek to a particular position. When succeeds, OnSeekComplete() will be + // called. Otherwise, nothing will happen. + void SeekTo(base::TimeDelta time); + + // Release the player resources. + void Release(); + + // Set the player volume. + void SetVolume(float leftVolume, float rightVolume); + + // Get the media information from the player. + int GetVideoWidth(); + int GetVideoHeight(); + base::TimeDelta GetCurrentTime(); + base::TimeDelta GetDuration(); + bool IsPlaying(); + + // Get metadata from the media. + void GetMetadata(); + + // Called by the timer to check for current time routinely and generates + // time update events. + void DoTimeUpdate(); + + // Called by the MediaPlayerListener and mirrored to corresponding + // callbacks. + void OnMediaError(int error_type); + void OnVideoSizeChanged(int width, int height); + void OnBufferingUpdate(int percent); + void OnPlaybackComplete(); + void OnSeekComplete(); + void OnMediaPrepared(); + + // Prepare the player for playback, asynchronously. When succeeds, + // OnMediaPrepared() will be called. Otherwise, OnMediaError() will + // be called with an error type. + void Prepare(); + + // Callback function passed to |cookies_retriever_|. + void GetCookiesCallback(const std::string& cookies); + + int player_id() { return player_id_; } + bool can_pause() { return can_pause_; } + bool can_seek_forward() { return can_seek_forward_; } + bool can_seek_backward() { return can_seek_backward_; } + bool prepared() { return prepared_; } + + private: + // Create the actual android media player. + void InitializePlayer(); + + // Functions that implements media player control. + void StartInternal(); + void PauseInternal(); + void SeekInternal(base::TimeDelta time); + + // Callbacks when events are received. + MediaErrorCB media_error_cb_; + VideoSizeChangedCB video_size_changed_cb_; + BufferingUpdateCB buffering_update_cb_; + MediaPreparedCB media_prepared_cb_; + PlaybackCompleteCB playback_complete_cb_; + SeekCompleteCB seek_complete_cb_; + + // Callbacks when timer events are received. + TimeUpdateCB time_update_cb_; + + // Player ID assigned to this player. + int player_id_; + + // Whether the player is prepared for playback. + bool prepared_; + + // Pending play event while player is preparing. + bool pending_play_; + + // Pending seek time while player is preparing. + base::TimeDelta pending_seek_; + + // Url for playback. + std::string url_; + + // First party url for cookies. + std::string first_party_for_cookies_; + + // Whether cookies are available. + bool has_cookies_; + + // Hide url log from media player. + bool hide_url_log_; + + // Stats about the media. + base::TimeDelta duration_; + int width_; + int height_; + + // Meta data about actions can be taken. + bool can_pause_; + bool can_seek_forward_; + bool can_seek_backward_; + + // Cookies for |url_| + std::string cookies_; + + // Resource manager for all the media players. + MediaPlayerBridgeManager* manager_; + + // Object for retrieving cookies for this media player. + scoped_ptr<CookieGetter> cookie_getter_; + + // Java MediaPlayer instance. + base::android::ScopedJavaGlobalRef<jobject> j_media_player_; + + base::RepeatingTimer<MediaPlayerBridge> time_update_timer_; + + // Weak pointer passed to |listener_| for callbacks. + base::WeakPtrFactory<MediaPlayerBridge> weak_this_; + + // Listener object that listens to all the media player events. + MediaPlayerListener listener_; + + DISALLOW_COPY_AND_ASSIGN(MediaPlayerBridge); +}; + +} // namespace media + +#endif // MEDIA_BASE_ANDROID_MEDIA_PLAYER_BRIDGE_H_ diff --git a/media/base/android/media_player_bridge_manager.cc b/media/base/android/media_player_bridge_manager.cc new file mode 100644 index 0000000000..26f2219693 --- /dev/null +++ b/media/base/android/media_player_bridge_manager.cc @@ -0,0 +1,11 @@ +// 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_manager.h" + +namespace media { + +MediaPlayerBridgeManager::~MediaPlayerBridgeManager() {} + +} // namespace media diff --git a/media/base/android/media_player_bridge_manager.h b/media/base/android/media_player_bridge_manager.h new file mode 100644 index 0000000000..93bd99cbd2 --- /dev/null +++ b/media/base/android/media_player_bridge_manager.h @@ -0,0 +1,34 @@ +// 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. + +#ifndef MEDIA_BASE_ANDROID_MEDIA_PLAYER_BRIDGE_MANAGER_H_ +#define MEDIA_BASE_ANDROID_MEDIA_PLAYER_BRIDGE_MANAGER_H_ + +#include "media/base/media_export.h" + +namespace media { + +class MediaPlayerBridge; + +// This class is responsible for managing active MediaPlayerBridge objects. +// It is implemented by webkit_media::MediaPlayerBridgeManagerImpl and +// content::MediaPlayerManagerAndroid. +class MEDIA_EXPORT MediaPlayerBridgeManager { + public: + virtual ~MediaPlayerBridgeManager(); + + // Called by a MediaPlayerBridge object when it is going to decode + // media streams. This helps the manager object maintain an array + // of active MediaPlayerBridge objects and release the resources + // when needed. + virtual void RequestMediaResources(MediaPlayerBridge* player) = 0; + + // Called when a MediaPlayerBridge object releases all its decoding + // resources. + virtual void ReleaseMediaResources(MediaPlayerBridge* player) = 0; +}; + +} // namespace media + +#endif // MEDIA_BASE_ANDROID_MEDIA_PLAYER_BRIDGE_MANAGER_H_ diff --git a/media/base/android/media_player_listener.cc b/media/base/android/media_player_listener.cc new file mode 100644 index 0000000000..de1820190c --- /dev/null +++ b/media/base/android/media_player_listener.cc @@ -0,0 +1,86 @@ +// 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_listener.h" + +#include "base/android/jni_android.h" +#include "base/bind.h" +#include "base/logging.h" +#include "base/message_loop_proxy.h" +#include "media/base/android/media_player_bridge.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" + +using base::android::AttachCurrentThread; +using base::android::CheckException; +using base::android::ScopedJavaLocalRef; + +namespace media { + +MediaPlayerListener::MediaPlayerListener( + const scoped_refptr<base::MessageLoopProxy>& message_loop, + base::WeakPtr<MediaPlayerBridge> media_player) + : message_loop_(message_loop), + media_player_(media_player) { + DCHECK(message_loop_); + DCHECK(media_player_); +} + +MediaPlayerListener::~MediaPlayerListener() {} + +void MediaPlayerListener::CreateMediaPlayerListener( + jobject context, jobject media_player) { + JNIEnv* env = AttachCurrentThread(); + CHECK(env); + + Java_MediaPlayerListener_create( + env, reinterpret_cast<intptr_t>(this), context, media_player); +} + +void MediaPlayerListener::OnMediaError( + JNIEnv* /* env */, jobject /* obj */, jint error_type) { + message_loop_->PostTask(FROM_HERE, base::Bind( + &MediaPlayerBridge::OnMediaError, media_player_, error_type)); +} + +void MediaPlayerListener::OnVideoSizeChanged( + JNIEnv* /* env */, jobject /* obj */, jint width, jint height) { + message_loop_->PostTask(FROM_HERE, base::Bind( + &MediaPlayerBridge::OnVideoSizeChanged, media_player_, + width, height)); +} + +void MediaPlayerListener::OnBufferingUpdate( + JNIEnv* /* env */, jobject /* obj */, jint percent) { + message_loop_->PostTask(FROM_HERE, base::Bind( + &MediaPlayerBridge::OnBufferingUpdate, media_player_, percent)); +} + +void MediaPlayerListener::OnPlaybackComplete( + JNIEnv* /* env */, jobject /* obj */) { + message_loop_->PostTask(FROM_HERE, base::Bind( + &MediaPlayerBridge::OnPlaybackComplete, media_player_)); +} + +void MediaPlayerListener::OnSeekComplete( + JNIEnv* /* env */, jobject /* obj */) { + message_loop_->PostTask(FROM_HERE, base::Bind( + &MediaPlayerBridge::OnSeekComplete, media_player_)); +} + +void MediaPlayerListener::OnMediaPrepared( + JNIEnv* /* env */, jobject /* obj */) { + message_loop_->PostTask(FROM_HERE, base::Bind( + &MediaPlayerBridge::OnMediaPrepared, media_player_)); +} + +bool MediaPlayerListener::RegisterMediaPlayerListener(JNIEnv* env) { + bool ret = RegisterNativesImpl(env); + DCHECK(g_MediaPlayerListener_clazz); + return ret; +} + +} // namespace media diff --git a/media/base/android/media_player_listener.h b/media/base/android/media_player_listener.h new file mode 100644 index 0000000000..e88faae041 --- /dev/null +++ b/media/base/android/media_player_listener.h @@ -0,0 +1,62 @@ +// 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. + +#ifndef MEDIA_BASE_ANDROID_MEDIA_PLAYER_LISTENER_H_ +#define MEDIA_BASE_ANDROID_MEDIA_PLAYER_LISTENER_H_ + +#include <jni.h> + +#include "base/android/scoped_java_ref.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" + +namespace base { +class MessageLoopProxy; +} + +namespace media { + +class MediaPlayerBridge; + +// Acts as a thread proxy between java MediaPlayerListener object and +// MediaPlayerBridge so that callbacks are posted onto the UI thread. +class MediaPlayerListener { + public: + // Construct a native MediaPlayerListener object. Callbacks from the java + // side object will be forwarded to |media_player| by posting a task on the + // |message_loop|. + MediaPlayerListener( + const scoped_refptr<base::MessageLoopProxy>& message_loop, + base::WeakPtr<MediaPlayerBridge> media_player); + virtual ~MediaPlayerListener(); + + // Called by the Java MediaPlayerListener and mirrored to corresponding + // callbacks. + void OnMediaError(JNIEnv* /* env */, jobject /* obj */, jint error_type); + void OnVideoSizeChanged(JNIEnv* /* env */, jobject /* obj */, + jint width, jint height); + void OnBufferingUpdate(JNIEnv* /* env */, jobject /* obj */, jint percent); + void OnPlaybackComplete(JNIEnv* /* env */, jobject /* obj */); + void OnSeekComplete(JNIEnv* /* env */, jobject /* obj */); + void OnMediaPrepared(JNIEnv* /* env */, jobject /* obj */); + + // Create a Java MediaPlayerListener object. + void CreateMediaPlayerListener(jobject context, jobject media_player); + + // Register MediaPlayerListener in the system library loader. + static bool RegisterMediaPlayerListener(JNIEnv* env); + + private: + // The message loop where |media_player_| lives. + scoped_refptr<base::MessageLoopProxy> message_loop_; + + // The MediaPlayerBridge object all the callbacks should be send to. + base::WeakPtr<MediaPlayerBridge> media_player_; + + DISALLOW_COPY_AND_ASSIGN(MediaPlayerListener); +}; + +} // namespace media + +#endif // MEDIA_BASE_ANDROID_MEDIA_PLAYER_LISTENER_H_ |