diff options
author | cramya@chromium.org <cramya@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-07-18 19:48:37 +0000 |
---|---|---|
committer | cramya@chromium.org <cramya@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-07-18 19:48:37 +0000 |
commit | 722e0dff34891a578169a98b0d985432b66b3fbf (patch) | |
tree | 4be9c8e8ae3135d8edd00a1ab3bae54957a507e7 | |
parent | 010469ee9d819a3ad20ef072fcab090fbe182244 (diff) | |
download | chromium_src-722e0dff34891a578169a98b0d985432b66b3fbf.zip chromium_src-722e0dff34891a578169a98b0d985432b66b3fbf.tar.gz chromium_src-722e0dff34891a578169a98b0d985432b66b3fbf.tar.bz2 |
Upstream ContentVideoView.java content_video_view.* media_metadata_android.*
BUG=
TEST=
Review URL: https://chromiumcodereview.appspot.com/10680008
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@147296 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | content/browser/android/browser_jni_registrar.cc | 2 | ||||
-rw-r--r-- | content/browser/android/content_video_view.cc | 291 | ||||
-rw-r--r-- | content/browser/android/content_video_view.h | 88 | ||||
-rw-r--r-- | content/common/view_messages.h | 17 | ||||
-rw-r--r-- | content/content_browser.gypi | 2 | ||||
-rw-r--r-- | content/content_jni.gypi | 2 | ||||
-rw-r--r-- | content/public/android/java/src/org/chromium/content/app/AppResource.java | 12 | ||||
-rw-r--r-- | content/public/android/java/src/org/chromium/content/browser/ContentVideoView.java | 629 | ||||
-rw-r--r-- | webkit/media/android/media_metadata_android.cc | 39 | ||||
-rw-r--r-- | webkit/media/android/media_metadata_android.h | 38 | ||||
-rw-r--r-- | webkit/media/webkit_media.gypi | 2 |
11 files changed, 1122 insertions, 0 deletions
diff --git a/content/browser/android/browser_jni_registrar.cc b/content/browser/android/browser_jni_registrar.cc index e966930..90cacb4 100644 --- a/content/browser/android/browser_jni_registrar.cc +++ b/content/browser/android/browser_jni_registrar.cc @@ -8,6 +8,7 @@ #include "base/android/jni_registrar.h" #include "content/browser/android/android_browser_process.h" #include "content/browser/android/content_settings.h" +#include "content/browser/android/content_video_view.h" #include "content/browser/android/content_view_client.h" #include "content/browser/android/content_view_core_impl.h" #include "content/browser/android/content_view_statics.h" @@ -23,6 +24,7 @@ base::android::RegistrationMethod kContentRegisteredMethods[] = { AndroidLocationApiAdapter::RegisterGeolocationService }, { "AndroidBrowserProcess", content::RegisterAndroidBrowserProcess }, { "ContentSettings", content::ContentSettings::RegisterContentSettings }, + { "ContentVideoView", content::ContentVideoView::RegisterContentVideoView }, { "ContentViewClient", content::RegisterContentViewClient }, { "ContentViewCore", content::RegisterContentViewCore }, { "DeviceInfo", content::RegisterDeviceInfo }, diff --git a/content/browser/android/content_video_view.cc b/content/browser/android/content_video_view.cc new file mode 100644 index 0000000..88599b2 --- /dev/null +++ b/content/browser/android/content_video_view.cc @@ -0,0 +1,291 @@ +// 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 "content/browser/android/content_video_view.h" + +#include "base/android/jni_android.h" +#include "base/command_line.h" +#include "base/logging.h" +#if !defined(ANDROID_UPSTREAM_BRINGUP) +#include "content/browser/media/media_player_delegate_android.h" +#endif +#include "content/common/android/surface_callback.h" +#include "content/common/android/surface_texture_peer.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/common/content_switches.h" +#include "jni/content_video_view_jni.h" +#include "webkit/media/android/media_metadata_android.h" + +using base::android::AttachCurrentThread; +using base::android::CheckException; +using base::android::ScopedJavaGlobalRef; + +// The timeout value we should wait after user click the back button on the +// fullscreen view. +static const int kTimeoutMillseconds = 1000; + +// ---------------------------------------------------------------------------- +// Methods that call to Java via JNI +// ---------------------------------------------------------------------------- + +namespace content { + +// static +bool ContentVideoView::RegisterContentVideoView(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +void ContentVideoView::CreateContentVideoView( + MediaPlayerDelegateAndroid* player) { + player_ = player; + if (j_content_video_view_.is_null()) { + JNIEnv* env = AttachCurrentThread(); + j_content_video_view_.Reset(Java_ContentVideoView_createContentVideoView( + env, reinterpret_cast<jint>(this))); + } else { + // Just ask video view to reopen the video. + Java_ContentVideoView_openVideo(AttachCurrentThread(), + j_content_video_view_.obj()); + } +} + +void ContentVideoView::DestroyContentVideoView() { + if (!j_content_video_view_.is_null()) { + Java_ContentVideoView_destroyContentVideoView(AttachCurrentThread()); + j_content_video_view_.Reset(); + } +} + +void ContentVideoView::OnMediaPlayerError(int error_type) { + if (!j_content_video_view_.is_null()) { + Java_ContentVideoView_onMediaPlayerError(AttachCurrentThread(), + j_content_video_view_.obj(), + error_type); + } +} + +void ContentVideoView::OnVideoSizeChanged(int width, int height) { + if (!j_content_video_view_.is_null()) { + Java_ContentVideoView_onVideoSizeChanged(AttachCurrentThread(), + j_content_video_view_.obj(), + width, + height); + } +} + +void ContentVideoView::OnBufferingUpdate(int percent) { + if (!j_content_video_view_.is_null()) { + Java_ContentVideoView_onBufferingUpdate(AttachCurrentThread(), + j_content_video_view_.obj(), + percent); + } +} + +void ContentVideoView::OnPlaybackComplete() { + if (!j_content_video_view_.is_null()) { + Java_ContentVideoView_onPlaybackComplete(AttachCurrentThread(), + j_content_video_view_.obj()); + } +} + +void ContentVideoView::UpdateMediaMetadata() { + if (!j_content_video_view_.is_null()) + UpdateMediaMetadata(AttachCurrentThread(), j_content_video_view_.obj()); +} + +// ---------------------------------------------------------------------------- + +ContentVideoView::ContentVideoView() : player_(NULL) { +} + +ContentVideoView::~ContentVideoView() { + player_ = NULL; + + // If the browser process crashed, just kill the fullscreen view. + DestroyContentVideoView(); +} + +// ---------------------------------------------------------------------------- +// All these functions are called on UI thread + +int ContentVideoView::GetVideoWidth(JNIEnv*, jobject obj) const { +#if !defined(ANDROID_UPSTREAM_BRINGUP) + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + return player_ ? player_->GetVideoWidth() : 0; +#else + return 0; +#endif +} + +int ContentVideoView::GetVideoHeight(JNIEnv*, jobject obj) const { +#if !defined(ANDROID_UPSTREAM_BRINGUP) + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + return player_ ? player_->GetVideoHeight() : 0; +#else + return 0; +#endif +} + +int ContentVideoView::GetDurationInMilliSeconds(JNIEnv*, jobject obj) const { +#if !defined(ANDROID_UPSTREAM_BRINGUP) + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + return player_ ? ConvertSecondsToMilliSeconds(player_->Duration()) : -1; +#else + return -1; +#endif +} + +int ContentVideoView::GetCurrentPosition(JNIEnv*, jobject obj) const { +#if !defined(ANDROID_UPSTREAM_BRINGUP) + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + return player_ ? ConvertSecondsToMilliSeconds(player_->CurrentTime()) : 0; +#else + return 0; +#endif +} + +bool ContentVideoView::IsPlaying(JNIEnv*, jobject obj) { +#if !defined(ANDROID_UPSTREAM_BRINGUP) + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + return player_ ? player_->IsPlaying() : false; +#else + return false; +#endif +} + +void ContentVideoView::SeekTo(JNIEnv*, jobject obj, jint msec) { +#if !defined(ANDROID_UPSTREAM_BRINGUP) + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (player_) + player_->Seek(static_cast<float>(msec / 1000.0)); +#endif +} + +webkit_media::MediaMetadataAndroid* ContentVideoView::GetMediaMetadata() { +#if !defined(ANDROID_UPSTREAM_BRINGUP) + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (!player_) + return NULL; + + return new webkit_media::MediaMetadataAndroid(player_->GetVideoWidth(), + player_->GetVideoHeight(), + base::TimeDelta::FromSeconds( + player_->Duration()), + base::TimeDelta::FromSeconds( + player_->CurrentTime()), + !player_->IsPlaying(), + player_->CanPause(), + player_->CanSeekForward(), + player_->CanSeekForward()); +#else + return NULL; +#endif +} + +int ContentVideoView::GetPlayerId(JNIEnv*, jobject obj) const { +#if !defined(ANDROID_UPSTREAM_BRINGUP) + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + return player_ ? player_->player_id() : -1; +#else + return -1; +#endif +} + +int ContentVideoView::GetRouteId(JNIEnv*, jobject obj) const { +#if !defined(ANDROID_UPSTREAM_BRINGUP) + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + return player_ ? player_->route_id() : -1; +#else + return -1; +#endif +} + +int ContentVideoView::GetRenderHandle(JNIEnv*, jobject obj) const { +#if !defined(ANDROID_UPSTREAM_BRINGUP) + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + static bool single_process = + CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess); + if (!player_) + return -1; + + if (single_process) + return 0; + return player_->render_handle(); +#else + return -1; +#endif +} + +void ContentVideoView::Play(JNIEnv*, jobject obj) { +#if !defined(ANDROID_UPSTREAM_BRINGUP) + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (player_) + player_->Play(); +#endif +} + +void ContentVideoView::Pause(JNIEnv*, jobject obj) { +#if !defined(ANDROID_UPSTREAM_BRINGUP) + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (player_) + player_->Pause(); +#endif +} + +void ContentVideoView::OnTimeout() { +#if !defined(ANDROID_UPSTREAM_BRINGUP) + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); +#endif + timeout_timer_.Stop(); + DestroyContentVideoView(); +} + +int ContentVideoView::ConvertSecondsToMilliSeconds(float seconds) const { + return static_cast<int>(seconds * 1000); +} + +// ---------------------------------------------------------------------------- +// Methods called from Java via JNI +// ---------------------------------------------------------------------------- + +void ContentVideoView::DestroyContentVideoView(JNIEnv*, jobject, + jboolean release_media_player) { +#if !defined(ANDROID_UPSTREAM_BRINGUP) + if (player_) { + player_->ExitFullscreen(release_media_player); + + // Fire off a timer so that we will close the fullscreen view in case the + // renderer crashes. + timeout_timer_.Start(FROM_HERE, + base::TimeDelta::FromMilliseconds(kTimeoutMillseconds), + this, &ContentVideoView::OnTimeout); + } +#endif +} + +void ContentVideoView::SetSurface(JNIEnv* env, jobject obj, + jobject surface, + jint route_id, + jint player_id) { + SetSurfaceAsync(env, + surface, + SurfaceTexturePeer::SET_VIDEO_SURFACE_TEXTURE, + route_id, + player_id, + NULL); +} + +void ContentVideoView::UpdateMediaMetadata(JNIEnv* env, jobject obj) { + scoped_ptr<webkit_media::MediaMetadataAndroid> metadata(GetMediaMetadata()); + Java_ContentVideoView_updateMediaMetadata(env, + obj, + metadata->width, + metadata->height, + metadata->duration.InMilliseconds(), + metadata->can_pause, + metadata->can_seek_forward, + metadata->can_seek_backward); +} + +} // namespace content diff --git a/content/browser/android/content_video_view.h b/content/browser/android/content_video_view.h new file mode 100644 index 0000000..b42b3c6 --- /dev/null +++ b/content/browser/android/content_video_view.h @@ -0,0 +1,88 @@ +// 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 CONTENT_BROWSER_ANDROID_CONTENT_VIDEO_VIEW_H_ +#define CONTENT_BROWSER_ANDROID_CONTENT_VIDEO_VIEW_H_ + +#include <jni.h> + +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/timer.h" + +namespace webkit_media { +struct MediaMetadataAndroid; +} + +namespace content { + +class MediaPlayerDelegateAndroid; + +// Native mirror of ContentVideoView.java. +class ContentVideoView { + public: + ContentVideoView(); + ~ContentVideoView(); + + static bool RegisterContentVideoView(JNIEnv* env); + void Init(JNIEnv*, jobject obj, jobject weak_this); + + // -------------------------------------------------------------------------- + // All these functions are called on UI thread + int GetVideoWidth(JNIEnv*, jobject obj) const; + int GetVideoHeight(JNIEnv*, jobject obj) const; + int GetDurationInMilliSeconds(JNIEnv*, jobject obj) const; + int GetCurrentPosition(JNIEnv*, jobject obj) const; + bool IsPlaying(JNIEnv*, jobject obj); + void SeekTo(JNIEnv*, jobject obj, jint msec); + int GetPlayerId(JNIEnv*, jobject obj) const; + int GetRouteId(JNIEnv*, jobject obj) const; + int GetRenderHandle(JNIEnv*, jobject obj) const; + void Play(JNIEnv*, jobject obj); + void Pause(JNIEnv*, jobject obj); + // -------------------------------------------------------------------------- + + void PrepareAsync(); + void DestroyContentVideoView(); + void UpdateMediaMetadata(JNIEnv*, jobject obj); + void DestroyContentVideoView(JNIEnv*, jobject); + void DestroyContentVideoView(JNIEnv*, jobject, jboolean release_media_player); + void CreateContentVideoView(MediaPlayerDelegateAndroid* player); + void SetSurface(JNIEnv*, + jobject obj, + jobject surface, + jint route_id, + jint player_id); + void UpdateMediaMetadata(); + + void OnMediaPlayerError(int errorType); + void OnVideoSizeChanged(int width, int height); + void OnBufferingUpdate(int percent); + void OnPlaybackComplete(); + + private: + // In some certain cases if the renderer crashes, the ExitFullscreen message + // will never acknowledged by the renderer. + void OnTimeout(); + + webkit_media::MediaMetadataAndroid* GetMediaMetadata(); + + int ConvertSecondsToMilliSeconds(float seconds) const; + + MediaPlayerDelegateAndroid* player_; + + base::android::ScopedJavaGlobalRef<jobject> j_content_video_view_; + + // A timer to keep track of when the acknowledgement of exitfullscreen + // message times out. + base::OneShotTimer<ContentVideoView> timeout_timer_; + + DISALLOW_COPY_AND_ASSIGN(ContentVideoView); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_ANDROID_CONTENT_VIDEO_VIEW_H_ diff --git a/content/common/view_messages.h b/content/common/view_messages.h index 5aea217..31f8061 100644 --- a/content/common/view_messages.h +++ b/content/common/view_messages.h @@ -51,6 +51,10 @@ #include "content/common/mac/font_descriptor.h" #endif +#if defined(OS_ANDROID) +#include "webkit/media/android/media_metadata_android.h" +#endif + #undef IPC_MESSAGE_EXPORT #define IPC_MESSAGE_EXPORT CONTENT_EXPORT @@ -360,6 +364,19 @@ IPC_STRUCT_TRAITS_BEGIN(ui::SelectedFileInfo) IPC_STRUCT_TRAITS_MEMBER(display_name) IPC_STRUCT_TRAITS_END() +#if defined(OS_ANDROID) +IPC_STRUCT_TRAITS_BEGIN(webkit_media::MediaMetadataAndroid) + IPC_STRUCT_TRAITS_MEMBER(width) + IPC_STRUCT_TRAITS_MEMBER(height) + IPC_STRUCT_TRAITS_MEMBER(duration) + IPC_STRUCT_TRAITS_MEMBER(current_time) + IPC_STRUCT_TRAITS_MEMBER(paused) + IPC_STRUCT_TRAITS_MEMBER(can_pause) + IPC_STRUCT_TRAITS_MEMBER(can_seek_forward) + IPC_STRUCT_TRAITS_MEMBER(can_seek_backward) +IPC_STRUCT_TRAITS_END() +#endif + IPC_STRUCT_BEGIN(ViewHostMsg_CreateWindow_Params) // Routing ID of the view initiating the open. IPC_STRUCT_MEMBER(int, opener_id) diff --git a/content/content_browser.gypi b/content/content_browser.gypi index 4f013ae..fdae232 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -198,6 +198,8 @@ 'browser/android/content_startup_flags.h', 'browser/android/content_util.cc', 'browser/android/content_util.h', + 'browser/android/content_video_view.cc', + 'browser/android/content_video_view.h', 'browser/android/content_view_client.cc', 'browser/android/content_view_client.h', 'browser/android/content_view_core_impl.cc', diff --git a/content/content_jni.gypi b/content/content_jni.gypi index 6368ee2..ba35902 100644 --- a/content/content_jni.gypi +++ b/content/content_jni.gypi @@ -15,6 +15,7 @@ 'public/android/java/src/org/chromium/content/app/UserAgent.java', 'public/android/java/src/org/chromium/content/browser/AndroidBrowserProcess.java', 'public/android/java/src/org/chromium/content/browser/ContentSettings.java', + 'public/android/java/src/org/chromium/content/browser/ContentVideoView.java', 'public/android/java/src/org/chromium/content/browser/ContentViewClient.java', 'public/android/java/src/org/chromium/content/browser/ContentViewCore.java', 'public/android/java/src/org/chromium/content/browser/ContentViewStatics.java', @@ -37,6 +38,7 @@ '<(SHARED_INTERMEDIATE_DIR)/content/jni/user_agent_jni.h', '<(SHARED_INTERMEDIATE_DIR)/content/jni/android_browser_process_jni.h', '<(SHARED_INTERMEDIATE_DIR)/content/jni/content_settings_jni.h', + '<(SHARED_INTERMEDIATE_DIR)/content/jni/content_video_view_jni.h', '<(SHARED_INTERMEDIATE_DIR)/content/jni/content_view_client_jni.h', '<(SHARED_INTERMEDIATE_DIR)/content/jni/content_view_core_jni.h', '<(SHARED_INTERMEDIATE_DIR)/content/jni/content_view_statics_jni.h', diff --git a/content/public/android/java/src/org/chromium/content/app/AppResource.java b/content/public/android/java/src/org/chromium/content/app/AppResource.java index 3bd7f60..24720bc 100644 --- a/content/public/android/java/src/org/chromium/content/app/AppResource.java +++ b/content/public/android/java/src/org/chromium/content/app/AppResource.java @@ -57,6 +57,18 @@ public class AppResource { /** String for the title of the month picker dialog. */ public static int STRING_MONTH_PICKER_DIALOG_TITLE; + /** String for playback errors in the media player. */ + public static int STRING_MEDIA_PLAYER_MESSAGE_PLAYBACK_ERROR; + + /** String for unknown errors in the media player. */ + public static int STRING_MEDIA_PLAYER_MESSAGE_UNKNOWN_ERROR; + + /** String for the button contents in the media player error dialog. */ + public static int STRING_MEDIA_PLAYER_ERROR_BUTTON; + + /** String for the title of the media player error dialog. */ + public static int STRING_MEDIA_PLAYER_ERROR_TITLE; + /** * Iterates through all the resources ids and verifies they have values other than zero. * @return true if all the resources have been registered. diff --git a/content/public/android/java/src/org/chromium/content/browser/ContentVideoView.java b/content/public/android/java/src/org/chromium/content/browser/ContentVideoView.java new file mode 100644 index 0000000..47a42a4 --- /dev/null +++ b/content/public/android/java/src/org/chromium/content/browser/ContentVideoView.java @@ -0,0 +1,629 @@ +// 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.content.browser; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Color; +import android.os.Handler; +import android.os.Message; +import android.os.RemoteException; +import android.util.Log; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.MediaController; +import android.widget.MediaController.MediaPlayerControl; + +import java.lang.ref.WeakReference; + +import org.chromium.base.CalledByNative; +import org.chromium.base.JNINamespace; +import org.chromium.content.app.AppResource; +import org.chromium.content.common.ISandboxedProcessService; + +@JNINamespace("content") +public class ContentVideoView extends FrameLayout implements MediaPlayerControl, + SurfaceHolder.Callback, View.OnTouchListener, View.OnKeyListener { + + private static final String TAG = "ContentVideoView"; + + /* Do not change these values without updating their counterparts + * in include/media/mediaplayer.h! + */ + private static final int MEDIA_NOP = 0; // interface test message + private static final int MEDIA_PREPARED = 1; + private static final int MEDIA_PLAYBACK_COMPLETE = 2; + private static final int MEDIA_BUFFERING_UPDATE = 3; + private static final int MEDIA_SEEK_COMPLETE = 4; + private static final int MEDIA_SET_VIDEO_SIZE = 5; + private static final int MEDIA_ERROR = 100; + private static final int MEDIA_INFO = 200; + + // Type needs to be kept in sync with surface_texture_peer.h. + private static final int SET_VIDEO_SURFACE_TEXTURE = 1; + + /** Unspecified media player error. + * @see android.media.MediaPlayer.OnErrorListener + */ + public static final int MEDIA_ERROR_UNKNOWN = 0; + + /** Media server died. In this case, the application must release the + * MediaPlayer object and instantiate a new one. + */ + public static final int MEDIA_ERROR_SERVER_DIED = 1; + + /** The video is streamed and its container is not valid for progressive + * playback i.e the video's index (e.g moov atom) is not at the start of the + * file. + */ + public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 2; + + // all possible internal states + private static final int STATE_ERROR = -1; + private static final int STATE_IDLE = 0; + private static final int STATE_PREPARING = 1; + private static final int STATE_PREPARED = 2; + private static final int STATE_PLAYING = 3; + private static final int STATE_PAUSED = 4; + private static final int STATE_PLAYBACK_COMPLETED = 5; + + private SurfaceHolder mSurfaceHolder = null; + private int mVideoWidth; + private int mVideoHeight; + private int mCurrentBufferPercentage; + private int mDuration; + private MediaController mMediaController = null; + private boolean mCanPause; + private boolean mCanSeekBack; + private boolean mCanSeekForward; + private boolean mHasMediaMetadata = false; + + // Native pointer to C++ ContentVideoView object. + private int mNativeContentVideoView = 0; + + // webkit should have prepared the media + private int mCurrentState = STATE_IDLE; + + // Strings for displaying media player errors + static String mPlaybackErrorText; + static String mUnknownErrorText; + static String mErrorButton; + static String mErrorTitle; + + // This view will contain the video. + private static VideoSurfaceView sVideoSurfaceView; + + private Surface mSurface = null; + + private static Activity sChromeActivity; + private static FrameLayout sRootLayout; + private static ViewGroup sContentContainer; + private static ViewGroup sControlContainer; + + // There are can be at most 1 fullscreen video + // TODO(qinmin): will change this once we move the creation of this class + // to the host application + private static ContentVideoView sContentVideoView = null; + + private class VideoSurfaceView extends SurfaceView { + + public VideoSurfaceView(Context context) { + super(context); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = getDefaultSize(mVideoWidth, widthMeasureSpec); + int height = getDefaultSize(mVideoHeight, heightMeasureSpec); + if (mVideoWidth > 0 && mVideoHeight > 0) { + if ( mVideoWidth * height > width * mVideoHeight ) { + height = width * mVideoHeight / mVideoWidth; + } else if ( mVideoWidth * height < width * mVideoHeight ) { + width = height * mVideoWidth / mVideoHeight; + } + } + setMeasuredDimension(width, height); + } + } + + public ContentVideoView(Context context) { + this(context, 0); + } + + public ContentVideoView(Context context, int nativeContentVideoView) { + super(context); + initResources(context); + + if (nativeContentVideoView == 0) return; + mNativeContentVideoView = nativeContentVideoView; + + mCurrentBufferPercentage = 0; + sVideoSurfaceView = new VideoSurfaceView(context); + mCurrentState = isPlaying() ? STATE_PLAYING : STATE_PAUSED; + } + + private static void initResources(Context context) { + if (mPlaybackErrorText != null) return; + + assert AppResource.STRING_MEDIA_PLAYER_MESSAGE_PLAYBACK_ERROR != 0; + assert AppResource.STRING_MEDIA_PLAYER_MESSAGE_UNKNOWN_ERROR != 0; + assert AppResource.STRING_MEDIA_PLAYER_ERROR_BUTTON != 0; + assert AppResource.STRING_MEDIA_PLAYER_ERROR_TITLE != 0; + + mPlaybackErrorText = context.getString( + AppResource.STRING_MEDIA_PLAYER_MESSAGE_PLAYBACK_ERROR); + mUnknownErrorText = context.getString( + AppResource.STRING_MEDIA_PLAYER_MESSAGE_UNKNOWN_ERROR); + mErrorButton = context.getString(AppResource.STRING_MEDIA_PLAYER_ERROR_BUTTON); + mErrorTitle = context.getString(AppResource.STRING_MEDIA_PLAYER_ERROR_TITLE); + } + + void showContentVideoView() { + this.addView(sVideoSurfaceView, + new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT, + Gravity.CENTER)); + sVideoSurfaceView.setOnKeyListener(this); + sVideoSurfaceView.setOnTouchListener(this); + sVideoSurfaceView.getHolder().addCallback(this); + sVideoSurfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); + sVideoSurfaceView.setFocusable(true); + sVideoSurfaceView.setFocusableInTouchMode(true); + sVideoSurfaceView.requestFocus(); + } + + @CalledByNative + public void onMediaPlayerError(int errorType) { + Log.d(TAG, "OnMediaPlayerError: " + errorType); + if (mCurrentState == STATE_ERROR || mCurrentState == STATE_PLAYBACK_COMPLETED) { + return; + } + + mCurrentState = STATE_ERROR; + if (mMediaController != null) { + mMediaController.hide(); + } + + /* Pop up an error dialog so the user knows that + * something bad has happened. Only try and pop up the dialog + * if we're attached to a window. When we're going away and no + * longer have a window, don't bother showing the user an error. + * + * TODO(qinmin): We need to review whether this Dialog is OK with + * the rest of the browser UI elements. + */ + if (getWindowToken() != null) { + String message; + + if (errorType == MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) { + message = mPlaybackErrorText; + } else { + message = mUnknownErrorText; + } + + new AlertDialog.Builder(getContext()) + .setTitle(mErrorTitle) + .setMessage(message) + .setPositiveButton(mErrorButton, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + /* Inform that the video is over. + */ + onCompletion(); + } + }) + .setCancelable(false) + .show(); + } + } + + @CalledByNative + public void onVideoSizeChanged(int width, int height) { + mVideoWidth = width; + mVideoHeight = height; + if (mVideoWidth != 0 && mVideoHeight != 0) { + sVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight); + } + } + + @CalledByNative + public void onBufferingUpdate(int percent) { + mCurrentBufferPercentage = percent; + } + + @CalledByNative + public void onPlaybackComplete() { + onCompletion(); + } + + @CalledByNative + public void updateMediaMetadata( + int videoWidth, + int videoHeight, + int duration, + boolean canPause, + boolean canSeekBack, + boolean canSeekForward) { + mDuration = duration; + mCanPause = canPause; + mCanSeekBack = canSeekBack; + mCanSeekForward = canSeekForward; + mHasMediaMetadata = true; + + if (mMediaController != null) { + mMediaController.setEnabled(true); + // If paused , should show the controller for ever. + if (isPlaying()) + mMediaController.show(); + else + mMediaController.show(0); + } + + onVideoSizeChanged(videoWidth, videoHeight); + } + + public void destroyNativeView() { + if (mNativeContentVideoView != 0) { + mNativeContentVideoView = 0; + } + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + sVideoSurfaceView.setFocusable(true); + sVideoSurfaceView.setFocusableInTouchMode(true); + if (isInPlaybackState() && mMediaController != null) { + if (mMediaController.isShowing()) { + // ensure the controller will get repositioned later + mMediaController.hide(); + } + mMediaController.show(); + } + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + mSurfaceHolder = holder; + openVideo(); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + mSurfaceHolder = null; + if (mNativeContentVideoView != 0) { + nativeDestroyContentVideoView(mNativeContentVideoView, true); + destroyNativeView(); + } + if (mMediaController != null) { + mMediaController.hide(); + mMediaController = null; + } + } + + public void setMediaController(MediaController controller) { + if (mMediaController != null) { + mMediaController.hide(); + } + mMediaController = controller; + attachMediaController(); + } + + private void attachMediaController() { + if (mMediaController != null) { + mMediaController.setMediaPlayer(this); + mMediaController.setAnchorView(sVideoSurfaceView); + mMediaController.setEnabled(mHasMediaMetadata); + } + } + + @CalledByNative + public void openVideo() { + if (mSurfaceHolder != null) { + if (mNativeContentVideoView != 0) { + nativeUpdateMediaMetadata(mNativeContentVideoView); + } + mCurrentBufferPercentage = 0; + if (mNativeContentVideoView != 0) { + int renderHandle = nativeGetRenderHandle(mNativeContentVideoView); + if (renderHandle == 0) { + nativeSetSurface(mNativeContentVideoView, + mSurfaceHolder.getSurface(), + nativeGetRouteId(mNativeContentVideoView), + nativeGetPlayerId(mNativeContentVideoView)); + return; + } + ISandboxedProcessService service = + SandboxedProcessLauncher.getSandboxedService(renderHandle); + if (service == null) { + Log.e(TAG, "Unable to get SandboxedProcessService from pid."); + return; + } + try { + service.setSurface( + SET_VIDEO_SURFACE_TEXTURE, + mSurfaceHolder.getSurface(), + nativeGetRouteId(mNativeContentVideoView), + nativeGetPlayerId(mNativeContentVideoView)); + } catch (RemoteException e) { + Log.e(TAG, "Unable to call setSurfaceTexture: " + e); + return; + } + } + requestLayout(); + invalidate(); + setMediaController(new MediaController(getChromeActivity())); + + if (mMediaController != null) { + mMediaController.show(); + } + } + } + + private void onCompletion() { + mCurrentState = STATE_PLAYBACK_COMPLETED; + if (mMediaController != null) { + mMediaController.hide(); + } + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + if (isInPlaybackState() && mMediaController != null && + event.getAction() == MotionEvent.ACTION_DOWN) { + toggleMediaControlsVisiblity(); + } + return true; + } + + @Override + public boolean onTrackballEvent(MotionEvent ev) { + if (isInPlaybackState() && mMediaController != null) { + toggleMediaControlsVisiblity(); + } + return false; + } + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK && + keyCode != KeyEvent.KEYCODE_VOLUME_UP && + keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && + keyCode != KeyEvent.KEYCODE_VOLUME_MUTE && + keyCode != KeyEvent.KEYCODE_CALL && + keyCode != KeyEvent.KEYCODE_MENU && + keyCode != KeyEvent.KEYCODE_SEARCH && + keyCode != KeyEvent.KEYCODE_ENDCALL; + if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) { + if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK || + keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) { + if (isPlaying()) { + pause(); + mMediaController.show(); + } else { + start(); + mMediaController.hide(); + } + return true; + } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) { + if (!isPlaying()) { + start(); + mMediaController.hide(); + } + return true; + } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP + || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) { + if (isPlaying()) { + pause(); + mMediaController.show(); + } + return true; + } else { + toggleMediaControlsVisiblity(); + } + } else if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) { + if (mNativeContentVideoView != 0) { + nativeDestroyContentVideoView(mNativeContentVideoView, false); + destroyNativeView(); + } + return true; + } else if (keyCode == KeyEvent.KEYCODE_MENU || keyCode == KeyEvent.KEYCODE_SEARCH) { + return true; + } + return super.onKeyDown(keyCode, event); + } + + private void toggleMediaControlsVisiblity() { + if (mMediaController.isShowing()) { + mMediaController.hide(); + } else { + mMediaController.show(); + } + } + + private boolean isInPlaybackState() { + return (mCurrentState != STATE_ERROR && + mCurrentState != STATE_IDLE && + mCurrentState != STATE_PREPARING); + } + + public void start() { + if (isInPlaybackState()) { + if (mNativeContentVideoView != 0) { + nativePlay(mNativeContentVideoView); + } + mCurrentState = STATE_PLAYING; + } + } + + public void pause() { + if (isInPlaybackState()) { + if (isPlaying()) { + if (mNativeContentVideoView != 0) { + nativePause(mNativeContentVideoView); + } + mCurrentState = STATE_PAUSED; + } + } + } + + // cache duration as mDuration for faster access + public int getDuration() { + if (isInPlaybackState()) { + if (mDuration > 0) { + return mDuration; + } + if (mNativeContentVideoView != 0) { + mDuration = nativeGetDurationInMilliSeconds(mNativeContentVideoView); + } else { + mDuration = 0; + } + return mDuration; + } + mDuration = -1; + return mDuration; + } + + public int getCurrentPosition() { + if (isInPlaybackState() && mNativeContentVideoView != 0) { + return nativeGetCurrentPosition(mNativeContentVideoView); + } + return 0; + } + + public void seekTo(int msec) { + if (mNativeContentVideoView != 0) { + nativeSeekTo(mNativeContentVideoView, msec); + } + } + + public boolean isPlaying() { + return mNativeContentVideoView != 0 && nativeIsPlaying(mNativeContentVideoView); + } + + public int getBufferPercentage() { + return mCurrentBufferPercentage; + } + public boolean canPause() { + return mCanPause; + } + + public boolean canSeekBackward() { + return mCanSeekBack; + } + + public boolean canSeekForward() { + return mCanSeekForward; + } + + @CalledByNative + public static ContentVideoView createContentVideoView(int nativeContentVideoView) { + if (getChromeActivity() != null) { + ContentVideoView videoView = new ContentVideoView(getChromeActivity(), + nativeContentVideoView); + if (sContentVideoView != null) { + return videoView; + } + + sContentVideoView = videoView; + + sContentContainer.setVisibility(View.GONE); + sControlContainer.setVisibility(View.GONE); + + sChromeActivity.getWindow().setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + + sChromeActivity.getWindow().addContentView(videoView, + new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + Gravity.CENTER)); + + videoView.setBackgroundColor(Color.BLACK); + sContentVideoView.showContentVideoView(); + videoView.setVisibility(View.VISIBLE); + return videoView; + } + return null; + } + + public static Activity getChromeActivity() { + return sChromeActivity; + } + + public static void showFullScreen(ContentVideoView fullscreenView) { + + } + + @CalledByNative + public static void destroyContentVideoView() { + sChromeActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + if (sContentVideoView != null) { + sContentVideoView.removeView(sVideoSurfaceView); + sVideoSurfaceView = null; + sContentVideoView.setVisibility(View.GONE); + sRootLayout.removeView(sContentVideoView); + } + + sContentContainer.setVisibility(View.VISIBLE); + sControlContainer.setVisibility(View.VISIBLE); + + sContentVideoView = null; + } + + public static ContentVideoView getContentVideoView() { + return sContentVideoView; + } + + public static void registerChromeActivity(Activity activity, FrameLayout rootLayout, + ViewGroup controlContainer, ViewGroup contentContainer) { + sChromeActivity = activity; + sRootLayout = rootLayout; + sControlContainer = controlContainer; + sContentContainer = contentContainer; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + return false; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) { + destroyContentVideoView(); + return true; + } + return super.onKeyDown(keyCode, event); + } + + private native void nativeDestroyContentVideoView(int nativeContentVideoView, + boolean relaseMediaPlayer); + private native int nativeGetCurrentPosition(int nativeContentVideoView); + private native int nativeGetDurationInMilliSeconds(int nativeContentVideoView); + private native void nativeUpdateMediaMetadata(int nativeContentVideoView); + private native int nativeGetVideoWidth(int nativeContentVideoView); + private native int nativeGetVideoHeight(int nativeContentVideoView); + private native int nativeGetPlayerId(int nativeContentVideoView); + private native int nativeGetRouteId(int nativeContentVideoView); + private native int nativeGetRenderHandle(int nativeContentVideoView); + private native boolean nativeIsPlaying(int nativeContentVideoView); + private native void nativePause(int nativeContentVideoView); + private native void nativePlay(int nativeContentVideoView); + private native void nativeSeekTo(int nativeContentVideoView, int msec); + private native void nativeSetSurface(int nativeContentVideoView, + Surface surface, int routeId, int playerId); +} diff --git a/webkit/media/android/media_metadata_android.cc b/webkit/media/android/media_metadata_android.cc new file mode 100644 index 0000000..4f6e169 --- /dev/null +++ b/webkit/media/android/media_metadata_android.cc @@ -0,0 +1,39 @@ +// 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 "webkit/media/android/media_metadata_android.h" + +namespace webkit_media { + +MediaMetadataAndroid::MediaMetadataAndroid() + : width(0), + height(0), + paused(false), + can_pause(false), + can_seek_forward(false), + can_seek_backward(false) { +} + +MediaMetadataAndroid::MediaMetadataAndroid(int w, + int h, + base::TimeDelta d, + base::TimeDelta ct, + bool p, + bool cp, + bool csf, + bool csb) + : width(w), + height(h), + duration(d), + current_time(ct), + paused(p), + can_pause(cp), + can_seek_forward(csf), + can_seek_backward(csb) { +} + +MediaMetadataAndroid::~MediaMetadataAndroid() { +} + +} // namespace webkit_media diff --git a/webkit/media/android/media_metadata_android.h b/webkit/media/android/media_metadata_android.h new file mode 100644 index 0000000..3298f934 --- /dev/null +++ b/webkit/media/android/media_metadata_android.h @@ -0,0 +1,38 @@ +// 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 WEBKIT_MEDIA_ANDROID_MEDIA_METADATA_ANDROID_H_ +#define WEBKIT_MEDIA_ANDROID_MEDIA_METADATA_ANDROID_H_ + +#include "base/basictypes.h" +#include "base/time.h" + +namespace webkit_media { + +// Provides the initial media metadata to ContentVideoView. +struct MediaMetadataAndroid { + MediaMetadataAndroid(); + MediaMetadataAndroid(int w, + int h, + base::TimeDelta d, + base::TimeDelta ct, + bool p, + bool cp, + bool csf, + bool csb); + ~MediaMetadataAndroid(); + + int width; + int height; + base::TimeDelta duration; + base::TimeDelta current_time; + bool paused; + bool can_pause; + bool can_seek_forward; + bool can_seek_backward; +}; + +} // namespace webkit_media + +#endif // WEBKIT_MEDIA_ANDROID_MEDIA_METADATA_ANDROID_H_ diff --git a/webkit/media/webkit_media.gypi b/webkit/media/webkit_media.gypi index 702031a..203b700e 100644 --- a/webkit/media/webkit_media.gypi +++ b/webkit/media/webkit_media.gypi @@ -15,6 +15,8 @@ ], 'sources': [ 'android/audio_decoder_android.cc', + 'android/media_metadata_android.cc', + 'android/media_metadata_android.h', 'android/stream_texture_factory_android.h', 'android/webmediaplayer_android.cc', 'android/webmediaplayer_android.h', |