Skip to content

Instantly share code, notes, and snippets.

@mavencode01
Last active August 29, 2015 14:27
Show Gist options
  • Save mavencode01/6d8d2a668795c6c567cf to your computer and use it in GitHub Desktop.
Save mavencode01/6d8d2a668795c6c567cf to your computer and use it in GitHub Desktop.
VideoView extends TextureView with rotation support
package com.mavencode.modules.chatwindow.widget;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.SurfaceTexture;
import android.media.AudioManager;
import android.net.Uri;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
import android.view.*;
import android.view.ViewGroup.LayoutParams;
import com.mavencode.R;
import tv.danmaku.ijk.media.player.IMediaPlayer;
import tv.danmaku.ijk.media.player.IMediaPlayer.*;
import tv.danmaku.ijk.media.player.IMediaPlayer.OnSeekCompleteListener;
import tv.danmaku.ijk.media.player.IjkMediaPlayer;
import java.io.IOException;
import java.util.List;
public class VideoView extends TextureView implements
MediaController.MediaPlayerControl {
private static final String LOG_TAG = "VideoView";
private static final int STATE_ERROR = -1;
private static final int STATE_IDLE = 0;
private static final int STATE_PREPARING = 1;
private static final int STATE_PREPARED = 2;
private static final int STATE_PLAYING = 3;
private static final int STATE_PAUSED = 4;
private static final int STATE_PLAYBACK_COMPLETED = 5;
private int mCurrentState = STATE_IDLE;
private int mTargetState = STATE_IDLE;
public int number;
private int mVideoLayout = VIDEO_LAYOUT_SCALE;
public static final int VIDEO_LAYOUT_ORIGIN = 0;
public static final int VIDEO_LAYOUT_SCALE = 1;
public static final int VIDEO_LAYOUT_STRETCH = 2;
public static final int VIDEO_LAYOUT_ZOOM = 3;
private IMediaPlayer mMediaPlayer = null;
private int videoWidth;
private int videoHeight;
private int surfaceWidth;
private int surfaceHeight;
private int mVideoSarNum;
private int mVideoSarDen;
private Surface mSurface;
private MediaController mMediaController;
private long mDuration;
private int mCurrentBufferPercentage;
private String mUserAgent;
private Uri uri;
private View mMediaBufferingIndicator;
private long mSeekWhenPrepared;
private boolean mCanPause = true;
private boolean mCanSeekBack = true;
private boolean mCanSeekForward = true;
private OnCompletionListener mOnCompletionListener;
private OnPreparedListener mOnPreparedListener;
private OnErrorListener mOnErrorListener;
private OnSeekCompleteListener mOnSeekCompleteListener;
private OnInfoListener mOnInfoListener;
private OnBufferingUpdateListener mOnBufferingUpdateListener;
private Context mContext;
private static final String TAG = VideoView.class.getName();
public VideoView(final Context context) {
super(context);
mContext = context;
initVideoView(context);
setVideoLayout(mVideoLayout);
}
public VideoView(final Context context, final AttributeSet attrs) {
super(context, attrs);
mContext = context;
initVideoView(context);
}
public VideoView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
initVideoView(context);
}
public void initVideoView(Context context) {
Log.d(LOG_TAG, "Initializing video view.");
mContext = context;
videoHeight = 0;
videoWidth = 0;
mVideoSarNum = 0;
mVideoSarDen = 0;
setFocusable(true);
setFocusableInTouchMode(true);
setSurfaceTextureListener(mSurfaceTextureListener);
requestFocus();
mCurrentState = STATE_IDLE;
mTargetState = STATE_IDLE;
if (context instanceof Activity)
((Activity) context).setVolumeControlStream(AudioManager.STREAM_MUSIC);
}
public void setVideoURI(Uri _videoURI) {
uri = _videoURI;
mSeekWhenPrepared = 0;
openVideo();
requestLayout();
invalidate();
}
public Uri getUri() {
return uri;
}
public void setUserAgent(String ua) {
mUserAgent = ua;
}
public void stopPlayback() {
if (mMediaPlayer != null) {
mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = null;
mMediaPlayer = null;
mCurrentState = STATE_IDLE;
mTargetState = STATE_IDLE;
}
}
public void openVideo() {
if ((uri == null) || (mSurface == null)) {
Log.d(LOG_TAG, "Cannot open video, uri or mSurface is null number " + number);
return;
}
Intent i = new Intent("com.android.music.musicservicecommand");
i.putExtra("command", "pause");
mContext.sendBroadcast(i);
Log.d(LOG_TAG, "Opening video.");
release(false);
try {
mDuration = -1;
mCurrentBufferPercentage = 0;
IjkMediaPlayer ijkMediaPlayer = null;
if (uri != null) {
ijkMediaPlayer = new IjkMediaPlayer();
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", IjkMediaPlayer.SDL_FCC_RV32);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 12);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "http-detect-range-support", 0);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "user_agent", mUserAgent);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48);
}
mMediaPlayer = ijkMediaPlayer;
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
Log.d(LOG_TAG, "Preparing media player.");
mMediaPlayer.setOnPreparedListener(mPreparedListener);
mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
mMediaPlayer.setOnCompletionListener(mCompletionListener);
mMediaPlayer.setOnErrorListener(mErrorListener);
mMediaPlayer.setOnInfoListener(mInfoListener);
mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
mMediaPlayer.setOnSeekCompleteListener(mSeekCompleteListener);
if (uri != null)
mMediaPlayer.setDataSource(uri.toString());
mMediaPlayer.setSurface(mSurface);
mMediaPlayer.setScreenOnWhilePlaying(true);
mMediaPlayer.prepareAsync();
mCurrentState = STATE_PREPARING;
attachMediaController();
} catch (IOException ex) {
DebugLog.e(TAG, "Unable to open content: " + uri, ex);
mCurrentState = STATE_ERROR;
mTargetState = STATE_ERROR;
mErrorListener.onError(mMediaPlayer, IMediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
return;
} catch (IllegalArgumentException ex) {
DebugLog.e(TAG, "Unable to open content: " + uri, ex);
mCurrentState = STATE_ERROR;
mTargetState = STATE_ERROR;
mErrorListener.onError(mMediaPlayer, IMediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
return;
}
}
public void setMediaController(MediaController controller) {
if (mMediaController != null)
mMediaController.hide();
mMediaController = controller;
attachMediaController();
}
public void setMediaBufferingIndicator(View mediaBufferingIndicator) {
if (mMediaBufferingIndicator != null)
mMediaBufferingIndicator.setVisibility(View.GONE);
mMediaBufferingIndicator = mediaBufferingIndicator;
}
private void attachMediaController() {
if (mMediaPlayer != null && mMediaController != null) {
mMediaController.setMediaPlayer(this);
View anchorView = this.getParent() instanceof View ? (View) this.getParent() : this;
mMediaController.setAnchorView(anchorView);
mMediaController.setEnabled(isInPlaybackState());
if (uri != null) {
List<String> paths = uri.getPathSegments();
String name = paths == null || paths.isEmpty() ? "null" : paths.get(paths.size() - 1);
mMediaController.setFileName(name);
}
}
}
private void release(boolean cleartargetstate) {
Log.d(LOG_TAG, "Releasing media player.");
if (mMediaPlayer != null) {
mMediaPlayer.reset();
mMediaPlayer.release();
mMediaPlayer = null;
mCurrentState = STATE_IDLE;
if (cleartargetstate) {
mTargetState = STATE_IDLE;
}
Log.d(LOG_TAG, "Released media player.");
} else {
Log.d(LOG_TAG, "Media player was null, did not release.");
}
}
@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
Log.d(LOG_TAG, "onMeasure number " + number);
int width = getDefaultSize(videoWidth, widthMeasureSpec);
int height = getDefaultSize(videoHeight, heightMeasureSpec);
if ((videoWidth > 0) && (videoHeight > 0)) {
if ((videoWidth * height) > (width * videoHeight)) {
Log.d(LOG_TAG, "Image too tall, correcting.");
height = (width * videoHeight) / videoWidth;
} else if ((videoWidth * height) < (width * videoHeight)) {
Log.d(LOG_TAG, "Image too wide, correcting.");
width = (height * videoWidth) / videoHeight;
} else {
Log.d(LOG_TAG, "Aspect ratio is correct.");
}
}
Log.d(LOG_TAG, "Setting size: " + width + '/' + height + " for number " + number);
setMeasuredDimension(width, height);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (isInPlaybackState() && mMediaController != null)
toggleMediaControlsVisiblity();
return false;
}
@Override
public boolean onTrackballEvent(MotionEvent ev) {
if (isInPlaybackState() && mMediaController != null)
toggleMediaControlsVisiblity();
return false;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK
&& keyCode != KeyEvent.KEYCODE_VOLUME_UP
&& keyCode != KeyEvent.KEYCODE_VOLUME_DOWN
&& keyCode != KeyEvent.KEYCODE_MENU
&& keyCode != KeyEvent.KEYCODE_CALL
&& keyCode != KeyEvent.KEYCODE_ENDCALL;
if (isInPlaybackState() && isKeyCodeSupported
&& mMediaController != null) {
if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK
|| keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
|| keyCode == KeyEvent.KEYCODE_SPACE) {
if (mMediaPlayer.isPlaying()) {
pause();
mMediaController.show();
} else {
start();
mMediaController.hide();
}
return true;
} else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
&& mMediaPlayer.isPlaying()) {
pause();
mMediaController.show();
} else {
toggleMediaControlsVisiblity();
}
}
return super.onKeyDown(keyCode, event);
}
private void toggleMediaControlsVisiblity() {
if (mMediaController.isShowing()) {
mMediaController.hide();
} else {
mMediaController.show();
}
}
public boolean isPlaying() {
return isInPlaybackState() && mMediaPlayer.isPlaying();
}
public void start() {
if (isInPlaybackState()) {
Log.d(LOG_TAG, "Starting media player for number " + number);
mMediaPlayer.start();
mCurrentState = STATE_PLAYING;
if (null != mMediaControllListener) {
mMediaControllListener.onStart();
} else {
Log.d(LOG_TAG, "Could not start. Current state " + mCurrentState);
}
mTargetState = STATE_PLAYING;
}
}
public void pause() {
if (isInPlaybackState()) {
if (mMediaPlayer.isPlaying()) {
mMediaPlayer.pause();
mCurrentState = STATE_PAUSED;
if (null != mMediaControllListener) {
mMediaControllListener.onPause();
}
}
}
mTargetState = STATE_PAUSED;
}
private boolean isInPlaybackState() {
return ((mMediaPlayer != null) &&
(mCurrentState != STATE_ERROR) &&
(mCurrentState != STATE_IDLE) &&
(mCurrentState != STATE_PREPARING));
}
public int getDuration() {
if (isInPlaybackState()) {
if (mDuration > 0)
return (int) mDuration;
mDuration = mMediaPlayer.getDuration();
return (int) mDuration;
}
mDuration = -1;
return (int) mDuration;
}
public int getCurrentPosition() {
if (isInPlaybackState()) {
long position = mMediaPlayer.getCurrentPosition();
return (int) position;
}
return 0;
}
public void seekTo(long msec) {
if (isInPlaybackState()) {
mMediaPlayer.seekTo(msec);
mSeekWhenPrepared = 0;
} else {
mSeekWhenPrepared = msec;
}
}
public int getBufferPercentage() {
if (mMediaPlayer != null)
return mCurrentBufferPercentage;
return 0;
}
@Override
public boolean canPause() {
return mCanPause;
}
@Override
public boolean canSeekBackward() {
return mCanSeekBack;
}
@Override
public boolean canSeekForward() {
return mCanSeekForward;
}
public void setVideoLayout(int layout) {
LayoutParams lp = getLayoutParams();
Pair<Integer, Integer> res = ScreenResolution.getResolution(mContext);
int windowWidth = res.first.intValue(), windowHeight = res.second.intValue();
float windowRatio = windowWidth / (float) windowHeight;
int sarNum = mVideoSarNum;
int sarDen = mVideoSarDen;
if (videoHeight > 0 && videoWidth > 0) {
float videoRatio = ((float) (videoWidth)) / videoHeight;
if (sarNum > 0 && sarDen > 0)
videoRatio = videoRatio * sarNum / sarDen;
surfaceHeight = videoHeight;
surfaceWidth = videoWidth;
if (VIDEO_LAYOUT_ORIGIN == layout && surfaceWidth < windowWidth && surfaceHeight < windowHeight) {
lp.width = (int) (surfaceHeight * videoRatio);
lp.height = surfaceHeight;
} else if (layout == VIDEO_LAYOUT_ZOOM) {
lp.width = windowRatio > videoRatio ? windowWidth : (int) (videoRatio * windowHeight);
lp.height = windowRatio < videoRatio ? windowHeight : (int) (windowWidth / videoRatio);
} else {
boolean full = layout == VIDEO_LAYOUT_STRETCH;
lp.width = (full || windowRatio < videoRatio) ? windowWidth : (int) (videoRatio * windowHeight);
lp.height = (full || windowRatio > videoRatio) ? windowHeight : (int) (windowWidth / videoRatio);
}
if (videoWidth > 800 && !isTablet(mContext)) {
setRotation(90.0f);
float scale = (videoWidth * 1.0f) / (videoHeight * 1.0f);
setScaleX(scale);
setScaleY(scale);
}
setLayoutParams(lp);
//getHolder().setFixedSize(surfaceWidth, surfaceHeight);
setMeasuredDimension(surfaceWidth, surfaceHeight);
DebugLog.dfmt(
TAG,
"VIDEO: %dx%dx%f[SAR:%d:%d], Surface: %dx%d, LP: %dx%d, Window: %dx%dx%f",
videoWidth, videoHeight, videoRatio, mVideoSarNum,
mVideoSarDen, surfaceWidth, surfaceHeight, lp.width,
lp.height, windowWidth, windowHeight, windowRatio);
}
mVideoLayout = layout;
}
public static boolean isTablet(Context context) {
return (context.getResources().getConfiguration().screenLayout
& Configuration.SCREENLAYOUT_SIZE_MASK)
>= Configuration.SCREENLAYOUT_SIZE_LARGE;
}
// Listeners
OnVideoSizeChangedListener mSizeChangedListener = new OnVideoSizeChangedListener() {
public void onVideoSizeChanged(IMediaPlayer mp, int width, int height, int sarNum, int sarDen) {
DebugLog.dfmt(TAG, "onVideoSizeChanged: (%dx%d) - (%dx%d)", width, height, sarNum, sarDen);
videoWidth = mp.getVideoWidth();
videoHeight = mp.getVideoHeight();
mVideoSarNum = sarNum;
mVideoSarDen = sarDen;
if (videoWidth != 0 && videoHeight != 0) {
setVideoLayout(mVideoLayout);
}
}
};
OnPreparedListener mPreparedListener = new OnPreparedListener() {
public void onPrepared(IMediaPlayer mp) {
DebugLog.d(TAG, "onPrepared");
mCurrentState = STATE_PREPARED;
mTargetState = STATE_PLAYING;
if (mOnPreparedListener != null)
mOnPreparedListener.onPrepared(mMediaPlayer);
if (mMediaController != null)
mMediaController.setEnabled(true);
videoWidth = mp.getVideoWidth();
videoHeight = mp.getVideoHeight();
long seekToPosition = mSeekWhenPrepared;
if (seekToPosition != 0)
seekTo(seekToPosition);
if (videoWidth != 0 && videoHeight != 0) {
setVideoLayout(mVideoLayout);
if (surfaceWidth == videoWidth && surfaceHeight == videoHeight) {
if (mTargetState == STATE_PLAYING) {
start();
if (mMediaController != null)
mMediaController.show();
} else if (!isPlaying()
&& (seekToPosition != 0 || getCurrentPosition() > 0)) {
if (mMediaController != null)
mMediaController.show(0);
}
}
} else if (mTargetState == STATE_PLAYING) {
start();
}
}
};
private OnCompletionListener mCompletionListener = new OnCompletionListener() {
public void onCompletion(IMediaPlayer mp) {
DebugLog.d(TAG, "onCompletion");
mCurrentState = STATE_PLAYBACK_COMPLETED;
mTargetState = STATE_PLAYBACK_COMPLETED;
mSurface.release();
if (mMediaController != null)
mMediaController.hide();
if (mOnCompletionListener != null)
mOnCompletionListener.onCompletion(mMediaPlayer);
}
};
private OnErrorListener mErrorListener = new OnErrorListener() {
public boolean onError(IMediaPlayer mp, int framework_err, int impl_err) {
DebugLog.dfmt(TAG, "Error: %d, %d", framework_err, impl_err);
mCurrentState = STATE_ERROR;
mTargetState = STATE_ERROR;
if (mMediaController != null)
mMediaController.hide();
if (mOnErrorListener != null) {
if (mOnErrorListener.onError(mMediaPlayer, framework_err,
impl_err))
return true;
}
if (getWindowToken() != null) {
int message = framework_err == IMediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK ? R.string.videoview_error_text_invalid_progressive_playback
: R.string.videoview_error_text_unknown;
new AlertDialog.Builder(mContext)
.setTitle(R.string.videoview_error_title)
.setMessage(message)
.setPositiveButton(
R.string.videoview_error_button,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
if (mOnCompletionListener != null)
mOnCompletionListener
.onCompletion(mMediaPlayer);
}
}).setCancelable(false).show();
}
return true;
}
};
private OnBufferingUpdateListener mBufferingUpdateListener = new OnBufferingUpdateListener() {
public void onBufferingUpdate(IMediaPlayer mp, int percent) {
mCurrentBufferPercentage = percent;
if (mOnBufferingUpdateListener != null)
mOnBufferingUpdateListener.onBufferingUpdate(mp, percent);
}
};
private OnInfoListener mInfoListener = new OnInfoListener() {
@Override
public boolean onInfo(IMediaPlayer mp, int what, int extra) {
DebugLog.dfmt(TAG, "onInfo: (%d, %d)", what, extra);
if (mOnInfoListener != null) {
mOnInfoListener.onInfo(mp, what, extra);
} else if (mMediaPlayer != null) {
if (what == IMediaPlayer.MEDIA_INFO_BUFFERING_START) {
DebugLog.dfmt(TAG, "onInfo: (MEDIA_INFO_BUFFERING_START)");
if (mMediaBufferingIndicator != null)
mMediaBufferingIndicator.setVisibility(View.VISIBLE);
} else if (what == IMediaPlayer.MEDIA_INFO_BUFFERING_END) {
DebugLog.dfmt(TAG, "onInfo: (MEDIA_INFO_BUFFERING_END)");
if (mMediaBufferingIndicator != null)
mMediaBufferingIndicator.setVisibility(View.GONE);
}
}
return true;
}
};
private OnSeekCompleteListener mSeekCompleteListener = new OnSeekCompleteListener() {
@Override
public void onSeekComplete(IMediaPlayer mp) {
DebugLog.d(TAG, "onSeekComplete");
if (mOnSeekCompleteListener != null)
mOnSeekCompleteListener.onSeekComplete(mp);
}
};
public void setOnPreparedListener(OnPreparedListener l) {
mOnPreparedListener = l;
}
public void setOnCompletionListener(OnCompletionListener l) {
mOnCompletionListener = l;
}
public void setOnErrorListener(OnErrorListener l) {
mOnErrorListener = l;
}
public void setOnBufferingUpdateListener(OnBufferingUpdateListener l) {
mOnBufferingUpdateListener = l;
}
public void setOnSeekCompleteListener(OnSeekCompleteListener l) {
mOnSeekCompleteListener = l;
}
public void setOnInfoListener(OnInfoListener l) {
mOnInfoListener = l;
}
public static interface MediaControllListener {
public void onStart();
public void onPause();
public void onStop();
public void onComplete();
}
MediaControllListener mMediaControllListener;
public void setMediaControllListener(MediaControllListener mediaControllListener) {
mMediaControllListener = mediaControllListener;
}
TextureView.SurfaceTextureListener mSurfaceTextureListener = new SurfaceTextureListener()
{
@Override
public void onSurfaceTextureSizeChanged(final SurfaceTexture surface, final int width, final int height) {
surfaceWidth = width;
surfaceHeight = height;
boolean isValidState = (mTargetState == STATE_PLAYING);
boolean hasValidSize = (videoWidth == width && videoHeight == height);
if (mMediaPlayer != null && isValidState && hasValidSize) {
if (mSeekWhenPrepared != 0) {
seekTo(mSeekWhenPrepared);
}
start();
}
}
@Override
public void onSurfaceTextureAvailable(final SurfaceTexture ssurface, final int width, final int height) {
mSurface = new Surface(ssurface);
openVideo();
}
@Override
public boolean onSurfaceTextureDestroyed(final SurfaceTexture ssurface) {
// after we return from this we can't use the mSurface any more
if (mSurface != null) {
mSurface.release();
mSurface = null;
}
if (mMediaController != null) mMediaController.hide();
release(true);
return true;
}
@Override
public void onSurfaceTextureUpdated(final SurfaceTexture surface) {
// do nothing
}
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment