diff options
Diffstat (limited to 'libs/fullscreenvideoview/src/main/java/com')
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); + } + } +} |