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