aboutsummaryrefslogtreecommitdiffstats
path: root/libs/fullscreenvideoview/src/main/java/com
diff options
context:
space:
mode:
Diffstat (limited to 'libs/fullscreenvideoview/src/main/java/com')
-rw-r--r--libs/fullscreenvideoview/src/main/java/com/github/rtoshiro/view/video/FullscreenVideoLayout.java336
-rw-r--r--libs/fullscreenvideoview/src/main/java/com/github/rtoshiro/view/video/FullscreenVideoView.java1014
2 files changed, 1350 insertions, 0 deletions
diff --git a/libs/fullscreenvideoview/src/main/java/com/github/rtoshiro/view/video/FullscreenVideoLayout.java b/libs/fullscreenvideoview/src/main/java/com/github/rtoshiro/view/video/FullscreenVideoLayout.java
new file mode 100644
index 000000000..f4113ce48
--- /dev/null
+++ b/libs/fullscreenvideoview/src/main/java/com/github/rtoshiro/view/video/FullscreenVideoLayout.java
@@ -0,0 +1,336 @@
+/**
+ * Copyright (C) 2016 Toshiro Sugii
+ * <p/>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.github.rtoshiro.view.video;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.media.MediaPlayer;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.RelativeLayout;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import java.util.Locale;
+
+public class FullscreenVideoLayout extends FullscreenVideoView implements View.OnClickListener, SeekBar.OnSeekBarChangeListener, MediaPlayer.OnPreparedListener {
+
+ /**
+ * Log cat TAG name
+ */
+ private final static String TAG = "FullscreenVideoLayout";
+
+ /**
+ * RelativeLayout that contains all control related views
+ */
+ public View videoControlsView;
+
+ /**
+ * SeekBar reference (from videoControlsView)
+ */
+ protected SeekBar seekBar;
+
+ /**
+ * Reference to ImageButton play
+ */
+ protected ImageButton imgplay;
+
+ /**
+ * Reference to ImageButton fullscreen
+ */
+ protected ImageButton imgfullscreen;
+
+ /**
+ * Reference to TextView for elapsed time and total time
+ */
+ protected TextView textTotal, textElapsed;
+
+ /**
+ * Handler and Runnable to keep tracking on elapsed time
+ */
+ protected static final Handler TIME_THREAD = new Handler();
+ protected Runnable updateTimeRunnable = new Runnable() {
+ public void run() {
+ updateCounter();
+
+ TIME_THREAD.postDelayed(this, 200);
+ }
+ };
+
+ public FullscreenVideoLayout(Context context) {
+ super(context);
+ }
+
+ public FullscreenVideoLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public FullscreenVideoLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ protected void release() {
+ super.release();
+ }
+
+ @Override
+ protected void initObjects() {
+ super.initObjects();
+
+ if (this.videoControlsView == null) {
+ LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ this.videoControlsView = inflater.inflate(R.layout.view_videocontrols, this, false);
+ }
+
+ if (videoControlsView != null) {
+ RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.FILL_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
+ params.addRule(ALIGN_PARENT_BOTTOM);
+ addView(videoControlsView, params);
+
+ this.seekBar = this.videoControlsView.findViewById(R.id.vcv_seekbar);
+ this.imgfullscreen = this.videoControlsView.findViewById(R.id.vcv_img_fullscreen);
+ this.imgplay = this.videoControlsView.findViewById(R.id.vcv_img_play);
+ this.textTotal = this.videoControlsView.findViewById(R.id.vcv_txt_total);
+ this.textElapsed = this.videoControlsView.findViewById(R.id.vcv_txt_elapsed);
+ }
+
+ if (this.imgplay != null)
+ this.imgplay.setOnClickListener(this);
+ if (this.imgfullscreen != null)
+ this.imgfullscreen.setOnClickListener(this);
+ if (this.seekBar != null)
+ this.seekBar.setOnSeekBarChangeListener(this);
+
+ // Start controls invisible. Make it visible when it is prepared
+ if (this.videoControlsView != null)
+ this.videoControlsView.setVisibility(View.INVISIBLE);
+ }
+
+ @Override
+ protected void releaseObjects() {
+ super.releaseObjects();
+
+ if (this.videoControlsView != null)
+ removeView(this.videoControlsView);
+ }
+
+ protected void startCounter() {
+ Log.d(TAG, "startCounter");
+
+ TIME_THREAD.postDelayed(updateTimeRunnable, 200);
+ }
+
+ protected void stopCounter() {
+ Log.d(TAG, "stopCounter");
+
+ TIME_THREAD.removeCallbacks(updateTimeRunnable);
+ }
+
+ protected void updateCounter() {
+ if (this.textElapsed == null)
+ return;
+
+ int elapsed = getCurrentPosition();
+ // getCurrentPosition is a little bit buggy :(
+ if (elapsed > 0 && elapsed < getDuration()) {
+ seekBar.setProgress(elapsed);
+
+ elapsed = Math.round(elapsed / 1000.f);
+ long s = elapsed % 60;
+ long m = (elapsed / 60) % 60;
+ long h = (elapsed / (60 * 60)) % 24;
+
+ if (h > 0)
+ textElapsed.setText(String.format(Locale.US, "%d:%02d:%02d", h, m, s));
+ else
+ textElapsed.setText(String.format(Locale.US, "%02d:%02d", m, s));
+ }
+ }
+
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ Log.d(TAG, "onCompletion");
+
+ super.onCompletion(mp);
+ stopCounter();
+ updateControls();
+ if (currentState != State.ERROR)
+ updateCounter();
+ }
+
+ @Override
+ public boolean onError(MediaPlayer mp, int what, int extra) {
+ boolean result = super.onError(mp, what, extra);
+ stopCounter();
+ updateControls();
+ return result;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (getCurrentState() == State.END) {
+ Log.d(TAG, "onDetachedFromWindow END");
+ stopCounter();
+ }
+ }
+
+ @Override
+ protected void tryToPrepare() {
+ Log.d(TAG, "tryToPrepare");
+ super.tryToPrepare();
+
+ if (getCurrentState() == State.PREPARED || getCurrentState() == State.STARTED) {
+ if (textElapsed != null && textTotal != null) {
+ int total = getDuration();
+ if (total > 0) {
+ seekBar.setMax(total);
+ seekBar.setProgress(0);
+
+ total = total / 1000;
+ long s = total % 60;
+ long m = (total / 60) % 60;
+ long h = (total / (60 * 60)) % 24;
+ if (h > 0) {
+ textElapsed.setText("00:00:00");
+ textTotal.setText(String.format(Locale.US, "%d:%02d:%02d", h, m, s));
+ } else {
+ textElapsed.setText("00:00");
+ textTotal.setText(String.format(Locale.US, "%02d:%02d", m, s));
+ }
+ }
+ }
+
+ if (videoControlsView != null)
+ videoControlsView.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void start() throws IllegalStateException {
+ Log.d(TAG, "start");
+
+ if (!isPlaying()) {
+ super.start();
+ startCounter();
+ updateControls();
+ }
+ }
+
+ @Override
+ public void pause() throws IllegalStateException {
+ Log.d(TAG, "pause");
+
+ if (isPlaying()) {
+ stopCounter();
+ super.pause();
+ updateControls();
+ }
+ }
+
+ @Override
+ public void reset() {
+ Log.d(TAG, "reset");
+
+ super.reset();
+
+ stopCounter();
+ updateControls();
+ }
+
+ @Override
+ public void stop() throws IllegalStateException {
+ Log.d(TAG, "stop");
+
+ super.stop();
+ stopCounter();
+ updateControls();
+ }
+
+ protected void updateControls() {
+ if (imgplay == null) return;
+
+ Drawable icon;
+ if (getCurrentState() == State.STARTED) {
+ icon = context.getResources().getDrawable(R.drawable.fvl_selector_pause);
+ } else {
+ icon = context.getResources().getDrawable(R.drawable.fvl_selector_play);
+ }
+ imgplay.setBackgroundDrawable(icon);
+ }
+
+ public void hideControls() {
+ Log.d(TAG, "hideControls");
+ if (videoControlsView != null) {
+ videoControlsView.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ public void showControls() {
+ Log.d(TAG, "showControls");
+ if (videoControlsView != null) {
+ videoControlsView.setVisibility(View.VISIBLE);
+ }
+ }
+
+ /**
+ * Onclick action
+ * Controls play button and fullscreen button.
+ *
+ * @param v View defined in XML
+ */
+ @Override
+ public void onClick(View v) {
+ if (v.getId() == R.id.vcv_img_play) {
+ if (isPlaying()) {
+ pause();
+ } else {
+ start();
+ }
+ } else {
+ setFullscreen(!isFullscreen());
+ }
+ }
+
+ /**
+ * SeekBar Listener
+ */
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ Log.d(TAG, "onProgressChanged " + progress);
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ stopCounter();
+ Log.d(TAG, "onStartTrackingTouch");
+
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ int progress = seekBar.getProgress();
+ seekTo(progress);
+ Log.d(TAG, "onStopTrackingTouch");
+
+ }
+}
diff --git a/libs/fullscreenvideoview/src/main/java/com/github/rtoshiro/view/video/FullscreenVideoView.java b/libs/fullscreenvideoview/src/main/java/com/github/rtoshiro/view/video/FullscreenVideoView.java
new file mode 100644
index 000000000..f068a468b
--- /dev/null
+++ b/libs/fullscreenvideoview/src/main/java/com/github/rtoshiro/view/video/FullscreenVideoView.java
@@ -0,0 +1,1014 @@
+/**
+ * Copyright (C) 2016 Toshiro Sugii
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.github.rtoshiro.view.video;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.graphics.Color;
+import android.graphics.SurfaceTexture;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnBufferingUpdateListener;
+import android.media.MediaPlayer.OnCompletionListener;
+import android.media.MediaPlayer.OnErrorListener;
+import android.media.MediaPlayer.OnInfoListener;
+import android.media.MediaPlayer.OnPreparedListener;
+import android.media.MediaPlayer.OnSeekCompleteListener;
+import android.media.MediaPlayer.OnVideoSizeChangedListener;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.TextureView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.ProgressBar;
+import android.widget.RelativeLayout;
+
+import java.io.IOException;
+
+/**
+ * Acts like a android.widget.VideoView with fullscreen functionality
+ *
+ * @author rtoshiro
+ * @version 2015.0527
+ * @since 1.0
+ */
+@SuppressLint("NewApi")
+public class FullscreenVideoView extends RelativeLayout implements SurfaceHolder.Callback, OnPreparedListener, OnErrorListener, OnSeekCompleteListener, OnCompletionListener, OnInfoListener, OnVideoSizeChangedListener, OnBufferingUpdateListener, TextureView.SurfaceTextureListener {
+
+ /**
+ * Debug Tag for use logging debug output to LogCat
+ */
+ private final static String TAG = "FullscreenVideoView";
+
+ protected Context context;
+ protected Activity activity; // Used when orientation changes is not static
+
+ protected MediaPlayer mediaPlayer;
+ protected SurfaceHolder surfaceHolder;
+ protected SurfaceView surfaceView;
+ protected boolean videoIsReady, surfaceIsReady;
+ protected boolean detachedByFullscreen;
+ protected State currentState;
+ protected State lastState; // Tells onSeekCompletion what to do
+
+ protected View onProgressView;
+
+ protected ViewGroup parentView; // Controls fullscreen container
+ protected ViewGroup.LayoutParams currentLayoutParams;
+
+ protected boolean fullscreen;
+ protected boolean shouldAutoplay;
+ protected int initialConfigOrientation;
+ protected int initialMovieWidth, initialMovieHeight;
+ protected String videoPath;
+ protected Uri videoUri;
+
+ protected OnBufferingUpdateListener bufferingUpdateListener;
+ protected OnCompletionListener completionListener;
+ protected OnErrorListener errorListener;
+ protected OnInfoListener infoListener;
+ protected OnPreparedListener preparedListener;
+ protected OnSeekCompleteListener seekCompleteListener;
+ protected OnVideoSizeChangedListener videoSizeChangedListener;
+
+ protected TextureView textureView;
+
+ /**
+ * States of MediaPlayer
+ *
+ * @see <a href="http://developer.android.com/reference/android/media/MediaPlayer.html#StateDiagram">MediaPlayer</a>
+ */
+ public enum State {
+ IDLE,
+ INITIALIZED,
+ PREPARED,
+ PREPARING,
+ STARTED,
+ STOPPED,
+ PAUSED,
+ PLAYBACKCOMPLETED,
+ ERROR,
+ END
+ }
+
+ public FullscreenVideoView(Context context) {
+ super(context);
+ this.context = context;
+
+ init();
+ }
+
+ public FullscreenVideoView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ this.context = context;
+
+ init();
+ }
+
+ public FullscreenVideoView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ this.context = context;
+
+ init();
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ Log.d(TAG, "onSizeChanged - w = " + w + " - h: " + h + " - oldw: " + oldw + " - oldh:" + oldw);
+ super.onSizeChanged(w, h, oldw, oldh);
+ resize();
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ Log.d(TAG, "onSaveInstanceState");
+ Parcelable p = super.onSaveInstanceState();
+ return p;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ Log.d(TAG, "onRestoreInstanceState");
+ super.onRestoreInstanceState(state);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ Log.d(TAG, "onDetachedFromWindow - detachedByFullscreen: " + detachedByFullscreen);
+
+ super.onDetachedFromWindow();
+
+ if (!this.detachedByFullscreen) {
+ release();
+ }
+
+ this.detachedByFullscreen = false;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ Log.d(TAG, "onAttachedToWindow");
+
+ // If Object still exists, reload the video
+ if (this.mediaPlayer == null &&
+ this.currentState == State.END) {
+ initObjects();
+ try {
+ if (this.videoPath != null)
+ setVideoPath(this.videoPath);
+ else if (this.videoUri != null)
+ setVideoURI(this.videoUri);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ // TextureView
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ @Override
+ public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
+ Log.d(TAG, "onSurfaceTextureAvailable - state: " + this.currentState);
+
+ Surface surface = new Surface(surfaceTexture);
+ if (this.mediaPlayer != null) {
+ this.mediaPlayer.setSurface(surface);
+
+ // If is not prepared yet - tryToPrepare()
+ if (!this.surfaceIsReady) {
+ this.surfaceIsReady = true;
+ if (this.currentState == State.INITIALIZED ||
+ this.currentState == State.PREPARING)
+ tryToPrepare();
+ }
+ }
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+ Log.d(TAG, "onSurfaceTextureSizeChanged - width: " + width + " - height: " + height);
+ resize();
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+ Log.d(TAG, "onSurfaceTextureDestroyed");
+ if (mediaPlayer != null && mediaPlayer.isPlaying())
+ mediaPlayer.pause();
+
+ surfaceIsReady = false;
+
+ return true;
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+ Log.d(TAG, "onSurfaceTextureUpdated");
+ }
+
+ // SurfaceView methods
+ @Override
+ synchronized public void surfaceCreated(SurfaceHolder holder) {
+ Log.d(TAG, "surfaceCreated called = " + currentState);
+
+ if (this.mediaPlayer != null) {
+ this.mediaPlayer.setDisplay(surfaceHolder);
+
+ // If is not prepared yet - tryToPrepare()
+ if (!this.surfaceIsReady) {
+ this.surfaceIsReady = true;
+ if (this.currentState == State.INITIALIZED ||
+ this.currentState == State.PREPARING)
+ tryToPrepare();
+ }
+ }
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ Log.d(TAG, "surfaceChanged called");
+ resize();
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ Log.d(TAG, "surfaceDestroyed called");
+ if (mediaPlayer != null && mediaPlayer.isPlaying())
+ mediaPlayer.pause();
+
+ surfaceIsReady = false;
+ }
+
+ @Override
+ synchronized public void onPrepared(MediaPlayer mp) {
+ Log.d(TAG, "onPrepared called");
+ videoIsReady = true;
+ tryToPrepare();
+ }
+
+ /**
+ * Restore the last State before seekTo()
+ *
+ * @param mp the MediaPlayer that issued the seek operation
+ */
+ @Override
+ public void onSeekComplete(MediaPlayer mp) {
+ Log.d(TAG, "onSeekComplete");
+
+ stopLoading();
+ if (lastState != null) {
+ switch (lastState) {
+ case STARTED: {
+ start();
+ break;
+ }
+ case PLAYBACKCOMPLETED: {
+ currentState = State.PLAYBACKCOMPLETED;
+ break;
+ }
+ case PREPARED: {
+ currentState = State.PREPARED;
+ break;
+ }
+ }
+ }
+
+ if (this.seekCompleteListener != null)
+ this.seekCompleteListener.onSeekComplete(mp);
+ }
+
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ if (this.mediaPlayer != null) {
+ if (this.currentState != State.ERROR) {
+ Log.d(TAG, "onCompletion");
+ if (!this.mediaPlayer.isLooping())
+ this.currentState = State.PLAYBACKCOMPLETED;
+ else
+ start();
+ }
+ }
+
+ if (this.completionListener != null)
+ this.completionListener.onCompletion(mp);
+ }
+
+ @Override
+ public boolean onInfo(MediaPlayer mediaPlayer, int what, int extra) {
+ Log.d(TAG, "onInfo " + what);
+
+ if (this.infoListener != null)
+ return this.infoListener.onInfo(mediaPlayer, what, extra);
+
+ return false;
+ }
+
+ @Override
+ public boolean onError(MediaPlayer mp, int what, int extra) {
+ Log.d(TAG, "onError called - " + what + " - " + extra);
+
+ stopLoading();
+ this.currentState = State.ERROR;
+
+ if (this.errorListener != null)
+ return this.errorListener.onError(mp, what, extra);
+ return false;
+ }
+
+ @Override
+ public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
+ Log.d(TAG, "onVideoSizeChanged = " + width + " - " + height);
+
+ if (this.initialMovieWidth == -1 &&
+ this.initialMovieHeight == -1 &&
+ width != 0 &&
+ height != 0) {
+ initialMovieWidth = width;
+ initialMovieHeight = height;
+ resize();
+ }
+
+ if (this.videoSizeChangedListener != null)
+ this.videoSizeChangedListener.onVideoSizeChanged(mp, width, height);
+ }
+
+ @Override
+ public void onBufferingUpdate(MediaPlayer mp, int percent) {
+// Log.d(TAG, "onBufferingUpdate = " + percent);
+
+ if (this.bufferingUpdateListener != null)
+ this.bufferingUpdateListener.onBufferingUpdate(mp, percent);
+ }
+
+ /**
+ * Initializes the default configuration
+ */
+ protected void init() {
+ Log.d(TAG, "init");
+
+ if (isInEditMode())
+ return;
+
+ this.mediaPlayer = null;
+ this.shouldAutoplay = false;
+ this.fullscreen = false;
+ this.initialConfigOrientation = -1;
+ this.videoIsReady = false;
+ this.surfaceIsReady = false;
+ this.initialMovieHeight = -1;
+ this.initialMovieWidth = -1;
+ this.setBackgroundColor(Color.BLACK);
+
+ initObjects();
+ }
+
+ /**
+ * Releases and ends the current Object
+ */
+ protected void release() {
+ Log.d(TAG, "release");
+ releaseObjects();
+
+ if (this.mediaPlayer != null) {
+ this.mediaPlayer.setOnBufferingUpdateListener(null);
+ this.mediaPlayer.setOnPreparedListener(null);
+ this.mediaPlayer.setOnErrorListener(null);
+ this.mediaPlayer.setOnSeekCompleteListener(null);
+ this.mediaPlayer.setOnCompletionListener(null);
+ this.mediaPlayer.setOnInfoListener(null);
+ this.mediaPlayer.setOnVideoSizeChangedListener(null);
+ this.mediaPlayer.release();
+ this.mediaPlayer = null;
+ }
+
+ this.currentState = State.END;
+ }
+
+ /**
+ * Initializes all objects FullscreenVideoView depends on
+ * It does not interfere with configuration properties
+ * because it is supposed to be called when this Object
+ * still exists
+ */
+ protected void initObjects() {
+ Log.d(TAG, "initObjects");
+
+ if (this.mediaPlayer == null) {
+ this.mediaPlayer = new MediaPlayer();
+ this.mediaPlayer.setOnInfoListener(this);
+ this.mediaPlayer.setOnErrorListener(this);
+ this.mediaPlayer.setOnPreparedListener(this);
+ this.mediaPlayer.setOnCompletionListener(this);
+ this.mediaPlayer.setOnSeekCompleteListener(this);
+ this.mediaPlayer.setOnBufferingUpdateListener(this);
+ this.mediaPlayer.setOnVideoSizeChangedListener(this);
+ this.mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ }
+
+ RelativeLayout.LayoutParams layoutParams;
+ View view;
+
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ if (this.textureView == null) {
+ this.textureView = new TextureView(this.context);
+ this.textureView.setSurfaceTextureListener(this);
+ }
+ view = this.textureView;
+
+ } else {
+ if (this.surfaceView == null) {
+ this.surfaceView = new SurfaceView(context);
+ }
+ view = this.surfaceView;
+
+ if (this.surfaceHolder == null) {
+ this.surfaceHolder = this.surfaceView.getHolder();
+ //noinspection deprecation
+ this.surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+ this.surfaceHolder.addCallback(this);
+ }
+ }
+
+ layoutParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ layoutParams.addRule(CENTER_IN_PARENT);
+ view.setLayoutParams(layoutParams);
+ addView(view);
+
+ // Try not reset onProgressView
+ if (this.onProgressView == null)
+ this.onProgressView = new ProgressBar(context);
+
+ layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ layoutParams.addRule(CENTER_IN_PARENT);
+ this.onProgressView.setLayoutParams(layoutParams);
+ addView(this.onProgressView);
+
+ stopLoading();
+ this.currentState = State.IDLE;
+ }
+
+ /**
+ * Releases all objects FullscreenVideoView depends on
+ * It does not interfere with configuration properties
+ * because it is supposed to be called when this Object
+ * still exists
+ */
+ protected void releaseObjects() {
+ Log.d(TAG, "releaseObjects");
+ if (this.mediaPlayer != null) {
+ this.mediaPlayer.setSurface(null);
+ this.mediaPlayer.reset();
+ }
+
+ this.videoIsReady = false;
+ this.surfaceIsReady = false;
+ this.initialMovieHeight = -1;
+ this.initialMovieWidth = -1;
+
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ if (this.textureView != null) {
+ this.textureView.setSurfaceTextureListener(null);
+ removeView(this.textureView);
+ this.textureView = null;
+ }
+ } else {
+ if (this.surfaceHolder != null) {
+ this.surfaceHolder.removeCallback(this);
+ this.surfaceHolder = null;
+ }
+ if (this.surfaceView != null) {
+ removeView(this.surfaceView);
+ this.surfaceView = null;
+ }
+ }
+
+ if (this.onProgressView != null) {
+ removeView(this.onProgressView);
+ }
+ }
+
+ /**
+ * Calls prepare() method of MediaPlayer
+ */
+
+ protected void prepare() throws IllegalStateException {
+ Log.d(TAG, "prepare");
+ startLoading();
+
+ this.currentState = State.PREPARING;
+ this.mediaPlayer.prepareAsync();
+ }
+
+ /**
+ * Try to call state PREPARED
+ * Only if SurfaceView is already created and MediaPlayer is prepared
+ * Video is loaded and is ok to play.
+ */
+ protected void tryToPrepare() {
+ Log.d(TAG, "tryToPrepare");
+ if (this.surfaceIsReady && this.videoIsReady) {
+ if (this.mediaPlayer != null &&
+ this.mediaPlayer.getVideoWidth() != 0 &&
+ this.mediaPlayer.getVideoHeight() != 0) {
+ this.initialMovieWidth = this.mediaPlayer.getVideoWidth();
+ this.initialMovieHeight = this.mediaPlayer.getVideoHeight();
+ }
+
+ resize();
+ stopLoading();
+ currentState = State.PREPARED;
+
+ if (shouldAutoplay)
+ start();
+
+ if (this.preparedListener != null)
+ this.preparedListener.onPrepared(mediaPlayer);
+ }
+ }
+
+ protected void startLoading() {
+ if (this.onProgressView != null)
+ this.onProgressView.setVisibility(View.VISIBLE);
+ }
+
+ protected void stopLoading() {
+ if (this.onProgressView != null)
+ this.onProgressView.setVisibility(View.GONE);
+ }
+
+ /**
+ * Get the current {@link FullscreenVideoView.State}.
+ *
+ * @return Current {@link FullscreenVideoView.State}
+ */
+ synchronized public State getCurrentState() {
+ return currentState;
+ }
+
+ /**
+ * Returns if VideoView is in fullscreen mode
+ *
+ * @return true if is in fullscreen mode otherwise false
+ * @since 1.1
+ */
+ public boolean isFullscreen() {
+ return fullscreen;
+ }
+
+ /**
+ * Turn VideoView fulllscreen mode on or off.
+ *
+ * @param fullscreen true to turn on fullscreen mode or false to turn off
+ * @throws RuntimeException In case of mediaPlayer doesn't exist or illegal state exception
+ * @since 1.1
+ */
+ public void setFullscreen(final boolean fullscreen) throws RuntimeException {
+
+ if (mediaPlayer == null)
+ throw new RuntimeException("Media Player is not initialized");
+
+ if (this.currentState != State.ERROR) {
+ if (FullscreenVideoView.this.fullscreen == fullscreen) return;
+ FullscreenVideoView.this.fullscreen = fullscreen;
+
+ final boolean wasPlaying = mediaPlayer.isPlaying();
+ if (wasPlaying)
+ pause();
+
+ if (FullscreenVideoView.this.fullscreen) {
+ if (activity != null)
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+
+ View rootView = getRootView();
+ View v = rootView.findViewById(android.R.id.content);
+ ViewParent viewParent = getParent();
+ if (viewParent instanceof ViewGroup) {
+ if (parentView == null)
+ parentView = (ViewGroup) viewParent;
+
+ // Prevents MediaPlayer to became invalidated and released
+ detachedByFullscreen = true;
+
+ // Saves the last state (LayoutParams) of view to restore after
+ currentLayoutParams = FullscreenVideoView.this.getLayoutParams();
+
+ parentView.removeView(FullscreenVideoView.this);
+ } else
+ Log.e(TAG, "Parent View is not a ViewGroup");
+
+ if (v instanceof ViewGroup) {
+ ((ViewGroup) v).addView(FullscreenVideoView.this);
+ } else
+ Log.e(TAG, "RootView is not a ViewGroup");
+ } else {
+ if (activity != null)
+ activity.setRequestedOrientation(initialConfigOrientation);
+
+ ViewParent viewParent = getParent();
+ if (viewParent instanceof ViewGroup) {
+ // Check if parent view is still available
+ boolean parentHasParent = false;
+ if (parentView != null && parentView.getParent() != null) {
+ parentHasParent = true;
+ detachedByFullscreen = true;
+ }
+
+ ((ViewGroup) viewParent).removeView(FullscreenVideoView.this);
+ if (parentHasParent) {
+ parentView.addView(FullscreenVideoView.this);
+ FullscreenVideoView.this.setLayoutParams(currentLayoutParams);
+ }
+ }
+ }
+
+ resize();
+
+ Handler handler = new Handler(Looper.getMainLooper());
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (wasPlaying && mediaPlayer != null)
+ start();
+ }
+ });
+ }
+ }
+
+ /**
+ * Binds an Activity to VideoView. This is necessary to keep tracking on orientation changes
+ *
+ * @param activity The activity that VideoView is related to
+ */
+ public void setActivity(Activity activity) {
+ this.activity = activity;
+ this.initialConfigOrientation = activity.getRequestedOrientation();
+ }
+
+ public void resize() {
+ if (initialMovieHeight == -1 || initialMovieWidth == -1 || (surfaceView == null && this.textureView == null))
+ return;
+
+ Handler handler = new Handler(Looper.getMainLooper());
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+
+ View currentParent = (View) getParent();
+ if (currentParent != null) {
+ float videoProportion = (float) initialMovieWidth / (float) initialMovieHeight;
+
+ int screenWidth = currentParent.getWidth();
+ int screenHeight = currentParent.getHeight();
+ float screenProportion = (float) screenWidth / (float) screenHeight;
+
+ int newWidth, newHeight;
+ if (videoProportion > screenProportion) {
+ newWidth = screenWidth;
+ newHeight = (int) ((float) screenWidth / videoProportion);
+ } else {
+ newWidth = (int) (videoProportion * (float) screenHeight);
+ newHeight = screenHeight;
+ }
+
+ View currentView = null;
+ RelativeLayout.LayoutParams lp;
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ currentView = textureView;
+ } else {
+ currentView = surfaceView;
+ }
+
+ if (currentView != null) {
+ lp = (RelativeLayout.LayoutParams) currentView.getLayoutParams();
+ lp.addRule(CENTER_IN_PARENT);
+ lp.width = newWidth;
+ lp.height = newHeight;
+ currentView.setLayoutParams(lp);
+ }
+
+ Log.d(TAG, "Resizing: initialMovieWidth: " + initialMovieWidth + " - initialMovieHeight: " + initialMovieHeight);
+ Log.d(TAG, "Resizing: screenWidth: " + screenWidth + " - screenHeight: " + screenHeight);
+ Log.d(TAG, "Resizing To: newWidth: " + newWidth + " - newHeight: " + newHeight);
+ }
+ }
+ });
+ }
+
+ /**
+ * Tells if application should autoplay videos as soon as it is prepared
+ *
+ * @return true if application are going to play videos as soon as it is prepared
+ */
+ public boolean isShouldAutoplay() {
+ return shouldAutoplay;
+ }
+
+ /**
+ * Tells application that it should begin playing as soon as buffering
+ * is ok
+ *
+ * @param shouldAutoplay If true, call start() method when getCurrentState() == PREPARED. Default is false.
+ */
+ public void setShouldAutoplay(boolean shouldAutoplay) {
+ this.shouldAutoplay = shouldAutoplay;
+ }
+
+ /**
+ * Toggles view to fullscreen mode
+ * It saves currentState and calls pause() method.
+ * When fullscreen is finished, it calls the saved currentState before pause()
+ * In practice, it only affects STARTED state.
+ * If currenteState was STARTED when fullscreen() is called, it calls start() method
+ * after fullscreen() has ended.
+ *
+ * @deprecated As of release 1.1.0, replaced by {@link #setFullscreen(boolean)}
+ */
+ @Deprecated
+ public void fullscreen() throws IllegalStateException {
+ setFullscreen(!fullscreen);
+ }
+
+ /**
+ * MediaPlayer method (getCurrentPosition)
+ *
+ * @see <a href="http://developer.android.com/reference/android/media/MediaPlayer.html#getCurrentPosition%28%29">getCurrentPosition</a>
+ */
+ public int getCurrentPosition() {
+ if (mediaPlayer != null)
+ return mediaPlayer.getCurrentPosition();
+ else throw new RuntimeException("Media Player is not initialized");
+ }
+
+ /**
+ * MediaPlayer method (getDuration)
+ *
+ * @see <a href="http://developer.android.com/reference/android/media/MediaPlayer.html#getDuration%28%29">getDuration</a>
+ */
+ public int getDuration() {
+ if (mediaPlayer != null)
+ return mediaPlayer.getDuration();
+ else throw new RuntimeException("Media Player is not initialized");
+ }
+
+ /**
+ * MediaPlayer method (getVideoHeight)
+ *
+ * @see <a href="http://developer.android.com/reference/android/media/MediaPlayer.html#getVideoHeight%28%29">getVideoHeight</a>
+ */
+ public int getVideoHeight() {
+ if (mediaPlayer != null)
+ return mediaPlayer.getVideoHeight();
+ else throw new RuntimeException("Media Player is not initialized");
+ }
+
+ /**
+ * MediaPlayer method (getVideoWidth)
+ *
+ * @see <a href="http://developer.android.com/reference/android/media/MediaPlayer.html#getVideoWidth%28%29">getVideoWidth</a>
+ */
+ public int getVideoWidth() {
+ if (mediaPlayer != null)
+ return mediaPlayer.getVideoWidth();
+ else throw new RuntimeException("Media Player is not initialized");
+ }
+
+
+ /**
+ * MediaPlayer method (isLooping)
+ *
+ * @see <a href="http://developer.android.com/reference/android/media/MediaPlayer.html#isLooping%28%29">isLooping</a>
+ */
+ public boolean isLooping() {
+ if (mediaPlayer != null)
+ return mediaPlayer.isLooping();
+ else throw new RuntimeException("Media Player is not initialized");
+ }
+
+ /**
+ * MediaPlayer method (isPlaying)
+ *
+ * @see <a href="http://developer.android.com/reference/android/media/MediaPlayer.html#isPlaying%28%29">isPlaying</a>
+ */
+ public boolean isPlaying() throws IllegalStateException {
+ if (mediaPlayer != null)
+ return mediaPlayer.isPlaying();
+ else throw new RuntimeException("Media Player is not initialized");
+ }
+
+ /**
+ * MediaPlayer method (pause)
+ *
+ * @see <a href="http://developer.android.com/reference/android/media/MediaPlayer.html#pause%28%29">pause</a>
+ */
+ public void pause() throws IllegalStateException {
+ Log.d(TAG, "pause");
+ if (mediaPlayer != null) {
+ currentState = State.PAUSED;
+ mediaPlayer.pause();
+ } else throw new RuntimeException("Media Player is not initialized");
+ }
+
+ /**
+ * Due to a lack of access of SurfaceView, it rebuilds mediaPlayer and all
+ * views to update SurfaceView canvas
+ */
+ public void reset() {
+ Log.d(TAG, "reset");
+
+ releaseObjects();
+ initObjects();
+ }
+
+ /**
+ * MediaPlayer method (start)
+ *
+ * @see <a href="http://developer.android.com/reference/android/media/MediaPlayer.html#start%28%29">start</a>
+ */
+ public void start() throws IllegalStateException {
+ Log.d(TAG, "start");
+
+ if (mediaPlayer != null) {
+ currentState = State.STARTED;
+ mediaPlayer.setOnCompletionListener(this);
+ mediaPlayer.start();
+ } else throw new RuntimeException("Media Player is not initialized");
+ }
+
+ /**
+ * MediaPlayer method (stop)
+ *
+ * @see <a href="http://developer.android.com/reference/android/media/MediaPlayer.html#stop%28%29">stop</a>
+ */
+ public void stop() throws IllegalStateException {
+ Log.d(TAG, "stop");
+
+ if (mediaPlayer != null) {
+ currentState = State.STOPPED;
+ mediaPlayer.stop();
+ } else throw new RuntimeException("Media Player is not initialized");
+ }
+
+ /**
+ * MediaPlayer method (seekTo)
+ * It calls pause() method before calling MediaPlayer.seekTo()
+ *
+ * @param msec the offset in milliseconds from the start to seek to
+ * @throws IllegalStateException if the internal player engine has not been initialized
+ * @see <a href="http://developer.android.com/reference/android/media/MediaPlayer.html#seekTo%28%29">seekTo</a>
+ */
+ public void seekTo(int msec) throws IllegalStateException {
+ Log.d(TAG, "seekTo = " + msec);
+
+ if (mediaPlayer != null) {
+ // No live streaming
+ if (mediaPlayer.getDuration() > -1 && msec <= mediaPlayer.getDuration()) {
+ lastState = currentState;
+ pause();
+ mediaPlayer.seekTo(msec);
+
+ startLoading();
+ }
+ } else throw new RuntimeException("Media Player is not initialized");
+ }
+
+ public void setOnCompletionListener(OnCompletionListener l) {
+ if (mediaPlayer != null)
+ this.completionListener = l;
+ else throw new RuntimeException("Media Player is not initialized");
+ }
+
+ public void setOnErrorListener(OnErrorListener l) {
+ if (mediaPlayer != null)
+ errorListener = l;
+ else throw new RuntimeException("Media Player is not initialized");
+ }
+
+ public void setOnBufferingUpdateListener(OnBufferingUpdateListener l) {
+ if (mediaPlayer != null) {
+ this.bufferingUpdateListener = l;
+ this.mediaPlayer.setOnBufferingUpdateListener(this);
+ } else throw new RuntimeException("Media Player is not initialized");
+ }
+
+ public void setOnInfoListener(OnInfoListener l) {
+ if (mediaPlayer != null)
+ this.infoListener = l;
+ else throw new RuntimeException("Media Player is not initialized");
+ }
+
+ public void setOnSeekCompleteListener(OnSeekCompleteListener l) {
+ if (mediaPlayer != null)
+ this.seekCompleteListener = l;
+ else throw new RuntimeException("Media Player is not initialized");
+ }
+
+ public void setOnVideoSizeChangedListener(OnVideoSizeChangedListener l) {
+ if (mediaPlayer != null)
+ this.videoSizeChangedListener = l;
+ else throw new RuntimeException("Media Player is not initialized");
+ }
+
+ public void setOnPreparedListener(OnPreparedListener l) {
+ if (mediaPlayer != null)
+ this.preparedListener = l;
+ else throw new RuntimeException("Media Player is not initialized");
+ }
+
+ public void setLooping(boolean looping) {
+ if (mediaPlayer != null)
+ mediaPlayer.setLooping(looping);
+ else throw new RuntimeException("Media Player is not initialized");
+ }
+
+ public void setVolume(float leftVolume, float rightVolume) {
+ if (mediaPlayer != null)
+ mediaPlayer.setVolume(leftVolume, rightVolume);
+ else throw new RuntimeException("Media Player is not initialized");
+ }
+
+ /**
+ * VideoView method (setVideoPath)
+ */
+ public void setVideoPath(String path) throws IOException, IllegalStateException, SecurityException, IllegalArgumentException, RuntimeException {
+ Log.d(TAG, "setVideoPath");
+ if (mediaPlayer != null) {
+ if (currentState != State.IDLE)
+ throw new IllegalStateException("FullscreenVideoView Invalid State: " + currentState);
+
+ this.videoPath = path;
+ this.videoUri = null;
+ this.mediaPlayer.setDataSource(path);
+
+ this.currentState = State.INITIALIZED;
+ prepare();
+ } else throw new RuntimeException("Media Player is not initialized");
+ }
+
+ /**
+ * VideoView method (setVideoURI)
+ */
+ public void setVideoURI(Uri uri) throws IOException, IllegalStateException, SecurityException, IllegalArgumentException, RuntimeException {
+ Log.d(TAG, "setVideoURI");
+ if (mediaPlayer != null) {
+ if (currentState != State.IDLE)
+ throw new IllegalStateException("FullscreenVideoView Invalid State: " + currentState);
+
+ this.videoUri = uri;
+ this.videoPath = null;
+ this.mediaPlayer.setDataSource(context, uri);
+
+ this.currentState = State.INITIALIZED;
+ prepare();
+ } else throw new RuntimeException("Media Player is not initialized");
+ }
+
+ /**
+ * Overwrite the default ProgressView to represent loading progress state
+ * It is controlled by stopLoading and startLoading methods, that only sets it to VISIBLE and GONE
+ * <p>
+ * Remember to set RelativeLayout.LayoutParams before setting the view.
+ *
+ * @param v The custom View that will be used as progress view.
+ * Set it to null to remove the default one
+ */
+ public void setOnProgressView(View v) {
+ int progressViewVisibility = -1;
+ if (this.onProgressView != null) {
+ progressViewVisibility = this.onProgressView.getVisibility();
+ removeView(this.onProgressView);
+ }
+
+ this.onProgressView = v;
+ if (this.onProgressView != null) {
+ addView(this.onProgressView);
+ if (progressViewVisibility != -1)
+ this.onProgressView.setVisibility(progressViewVisibility);
+ }
+ }
+}