我有一个RecyclerView
,每个ViewHolder
都有RecyclerView
一个MediaPlayer
对象。我正在使用 aTextureView
来保存MediaPlayer
对象,问题是当我向下滚动时它非常滞后。为什么会这样?MediaPlayer
我相信当表面被破坏时我正在正确地释放对象。
作为参考,我使用了TextureView
从这个 repo 中获取的以下实现:TextureVideoView
TextureVideoView.java
public class TextureVideoView extends TextureView implements TextureView.SurfaceTextureListener {
// Indicate if logging is on
public static final boolean LOG_ON = true;
// Log tag
private static final String TAG = TextureVideoView.class.getName();
private MediaPlayer mMediaPlayer;
private float mVideoHeight;
private float mVideoWidth;
private boolean mIsDataSourceSet;
private boolean mIsViewAvailable;
private boolean mIsVideoPrepared;
private boolean mIsPlayCalled;
private ScaleType mScaleType;
private State mState;
public enum ScaleType {
CENTER_CROP, TOP, BOTTOM
}
public enum State {
UNINITIALIZED, PLAY, STOP, PAUSE, END
}
public TextureVideoView(Context context) {
super(context);
initView();
}
public TextureVideoView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public TextureVideoView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}
private void initView() {
if (!isInEditMode()) {
initPlayer();
setScaleType(ScaleType.CENTER_CROP);
setSurfaceTextureListener(this);
}
}
public void setScaleType(ScaleType scaleType) {
mScaleType = scaleType;
}
private void updateTextureViewSize() {
float viewWidth = getWidth();
float viewHeight = getHeight();
float scaleX = 1.0f;
float scaleY = 1.0f;
if (mVideoWidth > viewWidth && mVideoHeight > viewHeight) {
scaleX = mVideoWidth / viewWidth;
scaleY = mVideoHeight / viewHeight;
} else if (mVideoWidth < viewWidth && mVideoHeight < viewHeight) {
scaleY = viewWidth / mVideoWidth;
scaleX = viewHeight / mVideoHeight;
} else if (viewWidth > mVideoWidth) {
scaleY = (viewWidth / mVideoWidth) / (viewHeight / mVideoHeight);
} else if (viewHeight > mVideoHeight) {
scaleX = (viewHeight / mVideoHeight) / (viewWidth / mVideoWidth);
}
// Calculate pivot points, in our case crop from center
int pivotPointX;
int pivotPointY;
switch (mScaleType) {
case TOP:
pivotPointX = 0;
pivotPointY = 0;
break;
case BOTTOM:
pivotPointX = (int) (viewWidth);
pivotPointY = (int) (viewHeight);
break;
case CENTER_CROP:
pivotPointX = (int) (viewWidth / 2);
pivotPointY = (int) (viewHeight / 2);
break;
default:
pivotPointX = (int) (viewWidth / 2);
pivotPointY = (int) (viewHeight / 2);
break;
}
Matrix matrix = new Matrix();
matrix.setScale(scaleX, scaleY, pivotPointX, pivotPointY);
setTransform(matrix);
}
private void initPlayer() {
if (mMediaPlayer == null) {
mMediaPlayer = new MediaPlayer();
} else {
mMediaPlayer.reset();
}
mIsVideoPrepared = false;
mIsPlayCalled = false;
mState = State.UNINITIALIZED;
}
/**
* @see android.media.MediaPlayer#setDataSource(String)
*/
public void setDataSource(final String path) {
initPlayer();
((Activity) getContext()).runOnUiThread(new Runnable() {
@Override
public void run() {
try {
mMediaPlayer.setDataSource(path);
} catch (IOException e) {
e.printStackTrace();
}
}
});
mIsDataSourceSet = true;
prepare();
}
/**
* @see android.media.MediaPlayer#setDataSource(android.content.Context, android.net.Uri)
*/
public void setDataSource(Context context, Uri uri) {
initPlayer();
try {
mMediaPlayer.setDataSource(context, uri);
mIsDataSourceSet = true;
//prepare();
mMediaPlayer.setWakeMode(context.getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
} catch (IOException e) {
Log.d(TAG, e.getMessage());
}
}
/**
* @see android.media.MediaPlayer#setDataSource(java.io.FileDescriptor)
*/
public void setDataSource(AssetFileDescriptor afd) {
initPlayer();
try {
long startOffset = afd.getStartOffset();
long length = afd.getLength();
mMediaPlayer.setDataSource(afd.getFileDescriptor(), startOffset, length);
mIsDataSourceSet = true;
prepare();
} catch (IOException e) {
Log.d(TAG, e.getMessage());
}
}
private void prepare() {
try {
// Adjust the size of the MediaPlayer based on the Screen Resolution
mMediaPlayer.setOnVideoSizeChangedListener(
new MediaPlayer.OnVideoSizeChangedListener() {
@Override
public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
mVideoWidth = width * metrics.density;
mVideoHeight = height * metrics.density;
updateTextureViewSize();
}
}
);
// Add a completion listener for when video ends and pass it to the listener
mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
mState = State.END;
if (mListener != null) {
mListener.onVideoEnd();
}
}
});
// don't forget to call MediaPlayer.prepareAsync() method when you use constructor for
// creating MediaPlayer
mMediaPlayer.prepareAsync();
// Play video when the media source is ready for playback.
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mediaPlayer) {
mIsVideoPrepared = true;
if (mIsPlayCalled && mIsViewAvailable) {
log("Player is prepared and play() was called.");
play();
}
if (mListener != null) {
mListener.onVideoPrepared();
}
}
});
} catch (IllegalArgumentException e) {
Log.d(TAG, "IllegalArgumentException");
Log.d(TAG, e.getMessage());
} catch (SecurityException e) {
Log.d(TAG, "SecurityException");
Log.d(TAG, e.getMessage());
} catch (IllegalStateException e) {
Log.d(TAG, "IllegalStateException");
Log.d(TAG, e.toString());
}
}
/**
* Play or resume video. Video will be played as soon as view is available and media player is
* prepared.
* <p/>
* If video is stopped or ended and play() method was called, video will start over.
*/
public void play() {
if (!mIsDataSourceSet) {
log("play() was called but data source was not set.");
return;
}
mIsPlayCalled = true;
if (!mIsVideoPrepared) {
log("play() was called but video is not prepared yet, waiting.");
return;
}
if (!mIsViewAvailable) {
log("play() was called but view is not available yet, waiting.");
return;
}
if (mState == State.PLAY) {
log("play() was called but video is already playing.");
return;
}
if (mState == State.PAUSE) {
log("play() was called but video is paused, resuming.");
mState = State.PLAY;
mMediaPlayer.start();
return;
}
if (mState == State.END || mState == State.STOP) {
log("play() was called but video already ended, starting over.");
mState = State.PLAY;
mMediaPlayer.seekTo(0);
mMediaPlayer.start();
return;
}
mState = State.PLAY;
mMediaPlayer.start();
}
/**
* Pause video. If video is already paused, stopped or ended nothing will happen.
*/
public void pause() {
if (mState == State.PAUSE) {
log("pause() was called but video already paused.");
return;
}
if (mState == State.STOP) {
log("pause() was called but video already stopped.");
return;
}
if (mState == State.END) {
log("pause() was called but video already ended.");
return;
}
mState = State.PAUSE;
if (mMediaPlayer.isPlaying()) {
mMediaPlayer.pause();
}
}
/**
* Stop video (pause and seek to beginning). If video is already stopped or ended nothing will
* happen.
*/
public void stop() {
if (mState == State.UNINITIALIZED) {
log("stop() was called but video already released.");
}
if (mState == State.STOP) {
log("stop() was called but video already stopped.");
return;
}
if (mState == State.END) {
log("stop() was called but video already ended.");
return;
}
mState = State.STOP;
if (mMediaPlayer.isPlaying()) {
Log.d(TAG, "MediaPlayer is playing");
mMediaPlayer.pause();
mMediaPlayer.seekTo(0);
mMediaPlayer.stop();
}
}
/**
* Reset the video (only if the video has been stopped). This will reset the mediaplayer, else
* nothing happens
*/
public void reset() {
if (mState == State.PLAY) {
log("reset() was called but video is currently playing.");
return;
}
if (mState == State.PAUSE) {
log("reset() was called but video is currently paused.");
return;
}
if (mState == State.STOP) {
mMediaPlayer.reset();
}
}
public void release() {
if (mState == State.PLAY) {
log("release() was called but video is currently playing.");
return;
}
if (mState == State.PAUSE) {
log("release() was called but video is currently paused.");
return;
}
if (mState == State.STOP) {
log("release() was called but video is currently stopped.");
return;
}
mMediaPlayer.release();
mMediaPlayer = null;
mState = State.UNINITIALIZED;
}
/**
* @see android.media.MediaPlayer#setLooping(boolean)
*/
public void setLooping(boolean looping) {
Log.d(TAG, "Looping");
mMediaPlayer.setLooping(looping);
}
/**
* @see android.media.MediaPlayer#seekTo(int)
*/
public void seekTo(int milliseconds) {
mMediaPlayer.seekTo(milliseconds);
}
/**
* @see android.media.MediaPlayer#getDuration()
*/
public int getDuration() {
return mMediaPlayer.getDuration();
}
static void log(String message) {
if (LOG_ON) {
Log.d(TAG, message);
}
}
private MediaPlayerListener mListener;
/**
* Listener trigger 'onVideoPrepared' and `onVideoEnd` events
*/
public void setListener(MediaPlayerListener listener) {
mListener = listener;
}
public interface MediaPlayerListener {
void onVideoPrepared();
void onVideoEnd();
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
Surface surface = new Surface(surfaceTexture);
mMediaPlayer.setSurface(surface);
mIsViewAvailable = true;
if (mIsDataSourceSet && mIsPlayCalled && mIsVideoPrepared) {
play();
}
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
// Deallocate MediaPlayer
if (mMediaPlayer != null) {
mMediaPlayer.pause();
mMediaPlayer.stop();
mMediaPlayer.reset();
mMediaPlayer.release();
mMediaPlayer = null;
}
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
}
当我下载资产时,我会在我的适配器中提供它,如下所示:
final TextureVideoView videoView = holder.videoview;
videoView.setDataSource(path);
videoView.setLooping(true);
videoView.setScaleType(TextureVideoView.ScaleType.CENTER_CROP);
videoView.play();
问题是当我滚动时它真的很迟钝,我该如何解决这个问题?除非视图大部分可见,否则我如何制作RecyclerView
像 Vine 一样的暂停。TextureView
寻找这个问题的非常彻底的答案和良好的优化实现。