- add sources.
[platform/framework/web/crosswalk.git] / src / content / public / android / java / src / org / chromium / content / browser / ContentVideoView.java
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.content.browser;
6
7 import android.app.AlertDialog;
8 import android.content.Context;
9 import android.content.DialogInterface;
10 import android.graphics.Color;
11 import android.os.Handler;
12 import android.os.Message;
13 import android.os.RemoteException;
14 import android.util.Log;
15 import android.view.Gravity;
16 import android.view.KeyEvent;
17 import android.view.MotionEvent;
18 import android.view.Surface;
19 import android.view.SurfaceHolder;
20 import android.view.SurfaceView;
21 import android.view.View;
22 import android.view.ViewGroup;
23 import android.widget.FrameLayout;
24 import android.widget.LinearLayout;
25 import android.widget.MediaController;
26 import android.widget.ProgressBar;
27 import android.widget.TextView;
28
29 import java.lang.ref.WeakReference;
30
31 import org.chromium.base.CalledByNative;
32 import org.chromium.base.JNINamespace;
33 import org.chromium.base.ThreadUtils;
34 import org.chromium.content.common.IChildProcessService;
35 import org.chromium.content.R;
36
37 @JNINamespace("content")
38 public class ContentVideoView
39         extends FrameLayout
40         implements ContentVideoViewControls.Delegate,
41         SurfaceHolder.Callback, View.OnTouchListener, View.OnKeyListener {
42
43     private static final String TAG = "ContentVideoView";
44
45     /* Do not change these values without updating their counterparts
46      * in include/media/mediaplayer.h!
47      */
48     private static final int MEDIA_NOP = 0; // interface test message
49     private static final int MEDIA_PREPARED = 1;
50     private static final int MEDIA_PLAYBACK_COMPLETE = 2;
51     private static final int MEDIA_BUFFERING_UPDATE = 3;
52     private static final int MEDIA_SEEK_COMPLETE = 4;
53     private static final int MEDIA_SET_VIDEO_SIZE = 5;
54     private static final int MEDIA_ERROR = 100;
55     private static final int MEDIA_INFO = 200;
56
57     /**
58      * Keep these error codes in sync with the code we defined in
59      * MediaPlayerListener.java.
60      */
61     public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 2;
62     public static final int MEDIA_ERROR_INVALID_CODE = 3;
63
64     // all possible internal states
65     private static final int STATE_ERROR              = -1;
66     private static final int STATE_IDLE               = 0;
67     private static final int STATE_PLAYING            = 1;
68     private static final int STATE_PAUSED             = 2;
69     private static final int STATE_PLAYBACK_COMPLETED = 3;
70
71     private SurfaceHolder mSurfaceHolder;
72     private int mVideoWidth;
73     private int mVideoHeight;
74     private int mCurrentBufferPercentage;
75     private int mDuration;
76     private ContentVideoViewControls mControls;
77     private boolean mCanPause;
78     private boolean mCanSeekBack;
79     private boolean mCanSeekForward;
80
81     // Native pointer to C++ ContentVideoView object.
82     private int mNativeContentVideoView;
83
84     // webkit should have prepared the media
85     private int mCurrentState = STATE_IDLE;
86
87     // Strings for displaying media player errors
88     private String mPlaybackErrorText;
89     private String mUnknownErrorText;
90     private String mErrorButton;
91     private String mErrorTitle;
92     private String mVideoLoadingText;
93
94     // This view will contain the video.
95     private VideoSurfaceView mVideoSurfaceView;
96
97     // Progress view when the video is loading.
98     private View mProgressView;
99
100     private Surface mSurface;
101
102     private ContentVideoViewClient mClient;
103
104     private class VideoSurfaceView extends SurfaceView {
105
106         public VideoSurfaceView(Context context) {
107             super(context);
108         }
109
110         @Override
111         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
112             int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
113             int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
114             if (mVideoWidth > 0 && mVideoHeight > 0) {
115                 if ( mVideoWidth * height  > width * mVideoHeight ) {
116                     height = width * mVideoHeight / mVideoWidth;
117                 } else if ( mVideoWidth * height  < width * mVideoHeight ) {
118                     width = height * mVideoWidth / mVideoHeight;
119                 }
120             }
121             setMeasuredDimension(width, height);
122         }
123     }
124
125     private static class ProgressView extends LinearLayout {
126
127         private ProgressBar mProgressBar;
128         private TextView mTextView;
129
130         public ProgressView(Context context, String videoLoadingText) {
131             super(context);
132             setOrientation(LinearLayout.VERTICAL);
133             setLayoutParams(new LinearLayout.LayoutParams(
134                     LinearLayout.LayoutParams.WRAP_CONTENT,
135                     LinearLayout.LayoutParams.WRAP_CONTENT));
136             mProgressBar = new ProgressBar(context, null, android.R.attr.progressBarStyleLarge);
137             mTextView = new TextView(context);
138             mTextView.setText(videoLoadingText);
139             addView(mProgressBar);
140             addView(mTextView);
141         }
142     }
143
144     private static class FullScreenControls implements ContentVideoViewControls {
145
146         View mVideoView;
147         MediaController mMediaController;
148
149         public FullScreenControls(Context context, View video) {
150             mMediaController = new MediaController(context);
151             mVideoView = video;
152         }
153
154         @Override
155         public void show() {
156             mMediaController.show();
157             if (mVideoView != null) {
158                 mVideoView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
159             }
160         }
161
162         @Override
163         public void show(int timeout_ms) {
164             mMediaController.show(timeout_ms);
165         }
166
167         @Override
168         public void hide() {
169             if (mVideoView != null) {
170                 mVideoView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
171             }
172             mMediaController.hide();
173         }
174
175         @Override
176         public boolean isShowing() {
177             return mMediaController.isShowing();
178         }
179
180         @Override
181         public void setEnabled(boolean enabled) {
182             mMediaController.setEnabled(enabled);
183         }
184
185         @Override
186         public void setDelegate(Delegate delegate) {
187             mMediaController.setMediaPlayer(delegate);
188         }
189
190         @Override
191         public void setAnchorView(View view) {
192             mMediaController.setAnchorView(view);
193         }
194     }
195
196     private Runnable mExitFullscreenRunnable = new Runnable() {
197         @Override
198         public void run() {
199             exitFullscreen(true);
200         }
201     };
202
203     private ContentVideoView(Context context, int nativeContentVideoView,
204             ContentVideoViewClient client) {
205         super(context);
206         mNativeContentVideoView = nativeContentVideoView;
207         mClient = client;
208         initResources(context);
209         mCurrentBufferPercentage = 0;
210         mVideoSurfaceView = new VideoSurfaceView(context);
211         setBackgroundColor(Color.BLACK);
212         showContentVideoView();
213         setVisibility(View.VISIBLE);
214         mClient.onShowCustomView(this);
215     }
216
217     private void initResources(Context context) {
218         if (mPlaybackErrorText != null) return;
219         mPlaybackErrorText = context.getString(
220                 org.chromium.content.R.string.media_player_error_text_invalid_progressive_playback);
221         mUnknownErrorText = context.getString(
222                 org.chromium.content.R.string.media_player_error_text_unknown);
223         mErrorButton = context.getString(
224                 org.chromium.content.R.string.media_player_error_button);
225         mErrorTitle = context.getString(
226                 org.chromium.content.R.string.media_player_error_title);
227         mVideoLoadingText = context.getString(
228                 org.chromium.content.R.string.media_player_loading_video);
229     }
230
231     private void showContentVideoView() {
232         FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
233                 ViewGroup.LayoutParams.MATCH_PARENT,
234                 ViewGroup.LayoutParams.MATCH_PARENT,
235                 Gravity.CENTER);
236         this.addView(mVideoSurfaceView, layoutParams);
237         View progressView = mClient.getVideoLoadingProgressView();
238         if (progressView != null) {
239             mProgressView = progressView;
240         } else {
241             mProgressView = new ProgressView(getContext(), mVideoLoadingText);
242         }
243         this.addView(mProgressView, new FrameLayout.LayoutParams(
244                 ViewGroup.LayoutParams.WRAP_CONTENT,
245                 ViewGroup.LayoutParams.WRAP_CONTENT,
246                 Gravity.CENTER));
247         mVideoSurfaceView.setZOrderOnTop(true);
248         mVideoSurfaceView.setOnKeyListener(this);
249         mVideoSurfaceView.setOnTouchListener(this);
250         mVideoSurfaceView.getHolder().addCallback(this);
251         mVideoSurfaceView.setFocusable(true);
252         mVideoSurfaceView.setFocusableInTouchMode(true);
253         mVideoSurfaceView.requestFocus();
254     }
255
256     @CalledByNative
257     public void onMediaPlayerError(int errorType) {
258         Log.d(TAG, "OnMediaPlayerError: " + errorType);
259         if (mCurrentState == STATE_ERROR || mCurrentState == STATE_PLAYBACK_COMPLETED) {
260             return;
261         }
262
263         // Ignore some invalid error codes.
264         if (errorType == MEDIA_ERROR_INVALID_CODE) {
265             return;
266         }
267
268         mCurrentState = STATE_ERROR;
269         if (mControls != null) {
270             mControls.hide();
271         }
272
273         /* Pop up an error dialog so the user knows that
274          * something bad has happened. Only try and pop up the dialog
275          * if we're attached to a window. When we're going away and no
276          * longer have a window, don't bother showing the user an error.
277          *
278          * TODO(qinmin): We need to review whether this Dialog is OK with
279          * the rest of the browser UI elements.
280          */
281         if (getWindowToken() != null) {
282             String message;
283
284             if (errorType == MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
285                 message = mPlaybackErrorText;
286             } else {
287                 message = mUnknownErrorText;
288             }
289
290             new AlertDialog.Builder(getContext())
291                 .setTitle(mErrorTitle)
292                 .setMessage(message)
293                 .setPositiveButton(mErrorButton,
294                         new DialogInterface.OnClickListener() {
295                     public void onClick(DialogInterface dialog, int whichButton) {
296                         /* Inform that the video is over.
297                          */
298                         onCompletion();
299                     }
300                 })
301                 .setCancelable(false)
302                 .show();
303         }
304     }
305
306     @CalledByNative
307     private void onVideoSizeChanged(int width, int height) {
308         mVideoWidth = width;
309         mVideoHeight = height;
310         // This will trigger the SurfaceView.onMeasure() call.
311         mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight);
312     }
313
314     @CalledByNative
315     private void onBufferingUpdate(int percent) {
316         mCurrentBufferPercentage = percent;
317     }
318
319     @CalledByNative
320     private void onPlaybackComplete() {
321         onCompletion();
322     }
323
324     @CalledByNative
325     private void onUpdateMediaMetadata(
326             int videoWidth,
327             int videoHeight,
328             int duration,
329             boolean canPause,
330             boolean canSeekBack,
331             boolean canSeekForward) {
332         mProgressView.setVisibility(View.GONE);
333         mDuration = duration;
334         mCanPause = canPause;
335         mCanSeekBack = canSeekBack;
336         mCanSeekForward = canSeekForward;
337         mCurrentState = isPlaying() ? STATE_PLAYING : STATE_PAUSED;
338         if (mControls != null) {
339             mControls.setEnabled(true);
340             // If paused , should show the controller for ever.
341             if (isPlaying())
342                 mControls.show();
343             else
344                 mControls.show(0);
345         }
346
347         onVideoSizeChanged(videoWidth, videoHeight);
348     }
349
350     @Override
351     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
352         mVideoSurfaceView.setFocusable(true);
353         mVideoSurfaceView.setFocusableInTouchMode(true);
354         if (isInPlaybackState() && mControls != null) {
355             mControls.show();
356         }
357     }
358
359     @Override
360     public void surfaceCreated(SurfaceHolder holder) {
361         mSurfaceHolder = holder;
362         openVideo();
363     }
364
365     @Override
366     public void surfaceDestroyed(SurfaceHolder holder) {
367         if (mNativeContentVideoView != 0) {
368             nativeSetSurface(mNativeContentVideoView, null);
369         }
370         mSurfaceHolder = null;
371         post(mExitFullscreenRunnable);
372     }
373
374     private void setControls(ContentVideoViewControls controls) {
375         if (mControls != null) {
376             mControls.hide();
377         }
378         mControls = controls;
379         attachControls();
380     }
381
382     private void attachControls() {
383         if (mControls != null) {
384             mControls.setDelegate(this);
385             mControls.setAnchorView(mVideoSurfaceView);
386             mControls.setEnabled(false);
387         }
388     }
389
390     @CalledByNative
391     private void openVideo() {
392         if (mSurfaceHolder != null) {
393             mCurrentState = STATE_IDLE;
394             mCurrentBufferPercentage = 0;
395             ContentVideoViewControls controls = mClient.createControls();
396             if (controls == null) {
397                 controls = new FullScreenControls(getContext(), this);
398             }
399             setControls(controls);
400             if (mNativeContentVideoView != 0) {
401                 nativeUpdateMediaMetadata(mNativeContentVideoView);
402                 nativeSetSurface(mNativeContentVideoView,
403                         mSurfaceHolder.getSurface());
404             }
405         }
406     }
407
408     private void onCompletion() {
409         mCurrentState = STATE_PLAYBACK_COMPLETED;
410         if (mControls != null) {
411             mControls.hide();
412         }
413     }
414
415     @Override
416     public boolean onTouch(View v, MotionEvent event) {
417         if (isInPlaybackState() && mControls != null &&
418                 event.getAction() == MotionEvent.ACTION_DOWN) {
419             toggleMediaControlsVisiblity();
420         }
421         return true;
422     }
423
424     @Override
425     public boolean onTrackballEvent(MotionEvent ev) {
426         if (isInPlaybackState() && mControls != null) {
427             toggleMediaControlsVisiblity();
428         }
429         return false;
430     }
431
432     @Override
433     public boolean onKey(View v, int keyCode, KeyEvent event) {
434         boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK &&
435                                      keyCode != KeyEvent.KEYCODE_VOLUME_UP &&
436                                      keyCode != KeyEvent.KEYCODE_VOLUME_DOWN &&
437                                      keyCode != KeyEvent.KEYCODE_VOLUME_MUTE &&
438                                      keyCode != KeyEvent.KEYCODE_CALL &&
439                                      keyCode != KeyEvent.KEYCODE_MENU &&
440                                      keyCode != KeyEvent.KEYCODE_SEARCH &&
441                                      keyCode != KeyEvent.KEYCODE_ENDCALL;
442         if (isInPlaybackState() && isKeyCodeSupported && mControls != null) {
443             if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||
444                     keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
445                 if (isPlaying()) {
446                     pause();
447                     mControls.show();
448                 } else {
449                     start();
450                     mControls.hide();
451                 }
452                 return true;
453             } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
454                 if (!isPlaying()) {
455                     start();
456                     mControls.hide();
457                 }
458                 return true;
459             } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
460                     || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
461                 if (isPlaying()) {
462                     pause();
463                     mControls.show();
464                 }
465                 return true;
466             } else {
467                 toggleMediaControlsVisiblity();
468             }
469         } else if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
470             exitFullscreen(false);
471             return true;
472         } else if (keyCode == KeyEvent.KEYCODE_MENU || keyCode == KeyEvent.KEYCODE_SEARCH) {
473             return true;
474         }
475         return super.onKeyDown(keyCode, event);
476     }
477
478     private void toggleMediaControlsVisiblity() {
479         if (mControls.isShowing()) {
480             mControls.hide();
481         } else {
482             mControls.show();
483         }
484     }
485
486     private boolean isInPlaybackState() {
487         return (mCurrentState != STATE_ERROR && mCurrentState != STATE_IDLE);
488     }
489
490     @Override
491     public void start() {
492         if (isInPlaybackState()) {
493             if (mNativeContentVideoView != 0) {
494                 nativePlay(mNativeContentVideoView);
495             }
496             mCurrentState = STATE_PLAYING;
497         }
498     }
499
500     @Override
501     public void pause() {
502         if (isInPlaybackState()) {
503             if (isPlaying()) {
504                 if (mNativeContentVideoView != 0) {
505                     nativePause(mNativeContentVideoView);
506                 }
507                 mCurrentState = STATE_PAUSED;
508             }
509         }
510     }
511
512     // cache duration as mDuration for faster access
513     @Override
514     public int getDuration() {
515         if (isInPlaybackState()) {
516             if (mDuration > 0) {
517                 return mDuration;
518             }
519             if (mNativeContentVideoView != 0) {
520                 mDuration = nativeGetDurationInMilliSeconds(mNativeContentVideoView);
521             } else {
522                 mDuration = 0;
523             }
524             return mDuration;
525         }
526         mDuration = -1;
527         return mDuration;
528     }
529
530     @Override
531     public int getCurrentPosition() {
532         if (isInPlaybackState() && mNativeContentVideoView != 0) {
533             return nativeGetCurrentPosition(mNativeContentVideoView);
534         }
535         return 0;
536     }
537
538     @Override
539     public void seekTo(int msec) {
540         if (mNativeContentVideoView != 0) {
541             nativeSeekTo(mNativeContentVideoView, msec);
542         }
543     }
544
545     @Override
546     public boolean isPlaying() {
547         return mNativeContentVideoView != 0 && nativeIsPlaying(mNativeContentVideoView);
548     }
549
550     @Override
551     public int getBufferPercentage() {
552         return mCurrentBufferPercentage;
553     }
554
555     @Override
556     public boolean canPause() {
557         return mCanPause;
558     }
559
560     @Override
561     public boolean canSeekBackward() {
562         return mCanSeekBack;
563     }
564
565     @Override
566     public boolean canSeekForward() {
567         return mCanSeekForward;
568     }
569
570     public int getAudioSessionId() {
571         return 0;
572     }
573
574     @CalledByNative
575     private static ContentVideoView createContentVideoView(
576             Context context, int nativeContentVideoView, ContentVideoViewClient client) {
577         ThreadUtils.assertOnUiThread();
578         return new ContentVideoView(context, nativeContentVideoView, client);
579     }
580
581     private void removeControls() {
582         if (mControls != null) {
583             mControls.setEnabled(false);
584             mControls.hide();
585             mControls = null;
586         }
587     }
588
589     public void removeSurfaceView() {
590         removeView(mVideoSurfaceView);
591         removeView(mProgressView);
592         mVideoSurfaceView = null;
593         mProgressView = null;
594     }
595
596     public void exitFullscreen(boolean relaseMediaPlayer) {
597         destroyContentVideoView(false);
598         if (mNativeContentVideoView != 0) {
599             nativeExitFullscreen(mNativeContentVideoView, relaseMediaPlayer);
600             mNativeContentVideoView = 0;
601         }
602     }
603
604     /**
605      * This method shall only be called by native and exitFullscreen,
606      * To exit fullscreen, use exitFullscreen in Java.
607      */
608     @CalledByNative
609     private void destroyContentVideoView(boolean nativeViewDestroyed) {
610         if (mVideoSurfaceView != null) {
611             removeControls();
612             removeSurfaceView();
613             setVisibility(View.GONE);
614
615             // To prevent re-entrance, call this after removeSurfaceView.
616             mClient.onDestroyContentVideoView();
617         }
618         if (nativeViewDestroyed) {
619             mNativeContentVideoView = 0;
620         }
621     }
622
623     public static ContentVideoView getContentVideoView() {
624         return nativeGetSingletonJavaContentVideoView();
625     }
626
627     @Override
628     public boolean onTouchEvent(MotionEvent ev) {
629         return true;
630     }
631
632     @Override
633     public boolean onKeyDown(int keyCode, KeyEvent event) {
634         if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
635             exitFullscreen(false);
636             return true;
637         }
638         return super.onKeyDown(keyCode, event);
639     }
640
641     private static native ContentVideoView nativeGetSingletonJavaContentVideoView();
642     private native void nativeExitFullscreen(int nativeContentVideoView, boolean relaseMediaPlayer);
643     private native int nativeGetCurrentPosition(int nativeContentVideoView);
644     private native int nativeGetDurationInMilliSeconds(int nativeContentVideoView);
645     private native void nativeUpdateMediaMetadata(int nativeContentVideoView);
646     private native int nativeGetVideoWidth(int nativeContentVideoView);
647     private native int nativeGetVideoHeight(int nativeContentVideoView);
648     private native boolean nativeIsPlaying(int nativeContentVideoView);
649     private native void nativePause(int nativeContentVideoView);
650     private native void nativePlay(int nativeContentVideoView);
651     private native void nativeSeekTo(int nativeContentVideoView, int msec);
652     private native void nativeSetSurface(int nativeContentVideoView, Surface surface);
653 }