高度自定义的安卓视频框架 MediaPlayer exoplayer ijkplayer ffmpeg
目前最新的版本是 7.7.0。 GitHub:JZVideo 中文文档:JZVD文档
特点- 可以完全自定义UI和任何功能。
- 很容易切换播放引擎,支持的视频格式和协议取决于播放引擎,android.media.MediaPlayer或者ijkplayer等。
- 可以检测列表的滑动。
- 可以嵌入到多个控件中,比如ListView、Fragment,RecyclerView等等,多重嵌套也可以。
- 可以实现全屏播放和小窗播放。
- 可以在加载、暂停和播放等状态下切换全屏和退出全屏。
- 可以进行屏幕适配,铺满全屏或者全屏裁剪。
- 好像还支持重力感应自动横屏。
- 全屏后可以用手势修改进度和音量。
- Home键退出界面暂停播放,返回界面的时候继续播放。
1. 在 app/build.gradle 中添加如下依赖:
implementation 'cn.jzvd:jiaozivideoplayer:7.7.0'
2. 在清单文件中添加网络权限:
三、播放在线视频
1. 效果图
2. 使用 1). 布局中添加如下控件
2).设置视频地址、缩略图地址、标题
public class MainActivity extends BaseActivity {
private String videoUrl = "https://v-cdn.zjol.com.cn/280443.mp4";
@Override
protected int getLayoutId() {
return R.layout.activity_main;
}
@Override
protected void init(Bundle savedInstanceState) {
initView();
}
private void initView() {
JzvdStd videoPlayer = findViewById(R.id.jz_video);
videoPlayer.setUp(videoUrl,"追龙");
videoPlayer.posterImageView.setImageResource(R.mipmap.ic_cover);
// videoPlayer.startVideo();
}
@Override
public void onBackPressed() {
if (Jzvd.backPress()) {
return;
}
super.onBackPressed();
}
@Override
protected void onPause() {
super.onPause();
Jzvd.releaseAllVideos();
}
}
四、播放本地视频
1. 效果图 2. 准备本地小视频
3. JzvdStdAssert.java
/**
* Created on 2022/3/10 14:51
*
* @author Gong Youqiang
*/
public class JzvdStdAssert extends JzvdStd {
public JzvdStdAssert(Context context) {
super(context);
}
public JzvdStdAssert(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onPrepared() {
state = STATE_PREPARED;
if (!preloading) {
mediaInterface.start();
preloading = false;
}
onStatePlaying();
}
}
4. 布局文件
5. JZMediaSystemAssertFolder.java
/**
* Created on 2022/3/10 11:39
*
* @author Gong Youqiang
*/
public class JZMediaSystemAssertFolder extends JZMediaInterface implements
MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener,
MediaPlayer.OnBufferingUpdateListener, MediaPlayer.OnSeekCompleteListener,
MediaPlayer.OnErrorListener, MediaPlayer.OnInfoListener,
MediaPlayer.OnVideoSizeChangedListener {
public MediaPlayer mediaPlayer;
public JZMediaSystemAssertFolder(Jzvd jzvd) {
super(jzvd);
}
@Override
public void prepare() {
release();
mMediaHandlerThread = new HandlerThread("JZVD");
mMediaHandlerThread.start();
mMediaHandler = new Handler(mMediaHandlerThread.getLooper());//主线程还是非主线程,就在这里
handler = new Handler();
mMediaHandler.post(() -> {
try {
mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setLooping(jzvd.jzDataSource.looping);
mediaPlayer.setOnPreparedListener(JZMediaSystemAssertFolder.this);
mediaPlayer.setOnCompletionListener(JZMediaSystemAssertFolder.this);
mediaPlayer.setOnBufferingUpdateListener(JZMediaSystemAssertFolder.this);
mediaPlayer.setScreenOnWhilePlaying(true);
mediaPlayer.setOnSeekCompleteListener(JZMediaSystemAssertFolder.this);
mediaPlayer.setOnErrorListener(JZMediaSystemAssertFolder.this);
mediaPlayer.setOnInfoListener(JZMediaSystemAssertFolder.this);
mediaPlayer.setOnVideoSizeChangedListener(JZMediaSystemAssertFolder.this);
//two lines are different
AssetFileDescriptor assetFileDescriptor = jzvd.getContext().getAssets().openFd(jzvd.jzDataSource.getCurrentUrl().toString());
mediaPlayer.setDataSource(assetFileDescriptor.getFileDescriptor(), assetFileDescriptor.getStartOffset(), assetFileDescriptor.getLength());
mediaPlayer.prepareAsync();
mediaPlayer.setSurface(new Surface(jzvd.textureView.getSurfaceTexture()));
} catch (Exception e) {
e.printStackTrace();
}
});
}
@Override
public void start() {
mMediaHandler.post(() -> mediaPlayer.start());
}
@Override
public void pause() {
mMediaHandler.post(() -> mediaPlayer.pause());
}
@Override
public boolean isPlaying() {
return mediaPlayer.isPlaying();
}
@Override
public void seekTo(long time) {
mMediaHandler.post(() -> {
try {
mediaPlayer.seekTo((int) time);
} catch (IllegalStateException e) {
e.printStackTrace();
}
});
}
@Override
public void release() {
if (mMediaHandler != null && mMediaHandlerThread != null && mediaPlayer != null) {//不知道有没有妖孽
HandlerThread tmpHandlerThread = mMediaHandlerThread;
MediaPlayer tmpMediaPlayer = mediaPlayer;
JZMediaInterface.SAVED_SURFACE = null;
mMediaHandler.post(() -> {
tmpMediaPlayer.release();//release就不能放到主线程里,界面会卡顿
tmpHandlerThread.quit();
});
mediaPlayer = null;
}
}
@Override
public long getCurrentPosition() {
if (mediaPlayer != null) {
return mediaPlayer.getCurrentPosition();
} else {
return 0;
}
}
@Override
public long getDuration() {
if (mediaPlayer != null) {
return mediaPlayer.getDuration();
} else {
return 0;
}
}
@Override
public void setVolume(float leftVolume, float rightVolume) {
if (mMediaHandler == null) return;
mMediaHandler.post(() -> {
if (mediaPlayer != null) mediaPlayer.setVolume(leftVolume, rightVolume);
});
}
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public void setSpeed(float speed) {
PlaybackParams pp = mediaPlayer.getPlaybackParams();
pp.setSpeed(speed);
mediaPlayer.setPlaybackParams(pp);
}
@Override
public void onPrepared(MediaPlayer mediaPlayer) {
handler.post(() -> jzvd.onPrepared());//如果是mp3音频,走这里
}
@Override
public void onCompletion(MediaPlayer mediaPlayer) {
handler.post(() -> jzvd.onCompletion());
}
@Override
public void onBufferingUpdate(MediaPlayer mediaPlayer, final int percent) {
handler.post(() -> jzvd.setBufferProgress(percent));
}
@Override
public void onSeekComplete(MediaPlayer mediaPlayer) {
handler.post(() -> jzvd.onSeekComplete());
}
@Override
public boolean onError(MediaPlayer mediaPlayer, final int what, final int extra) {
handler.post(() -> jzvd.onError(what, extra));
return true;
}
@Override
public boolean onInfo(MediaPlayer mediaPlayer, final int what, final int extra) {
handler.post(() -> {
if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
if (jzvd.state == Jzvd.STATE_PREPARING
|| jzvd.state == Jzvd.STATE_PREPARING_CHANGE_URL) {
jzvd.onPrepared();
}
} else {
jzvd.onInfo(what, extra);
}
});
return false;
}
@Override
public void onVideoSizeChanged(MediaPlayer mediaPlayer, int width, int height) {
handler.post(() -> jzvd.onVideoSizeChanged(width, height));
}
@Override
public void setSurface(Surface surface) {
mediaPlayer.setSurface(surface);
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
Log.i("GD", "======= onSurfaceTextureAvailable =======");
if (SAVED_SURFACE == null) {
SAVED_SURFACE = surface;
prepare();
} else {
jzvd.textureView.setSurfaceTexture(SAVED_SURFACE);
}
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
}
6. VideoActivity.java
public class VideoActivity extends BaseActivity {
@BindView(R.id.title_bar)
TitleBar mTitleBar;
@BindView(R.id.jz_video)
JzvdStdAssert mJzVideo;
public String localVideoPath;
@Override
protected int getLayoutId() {
return R.layout.activity_video;
}
@Override
protected void initView() {
mTitleBar.setOnTitleBarListener(new OnTitleBarListener() {
@Override
public void onLeftClick(View view) {
finish();
}
@Override
public void onTitleClick(View view) {
}
@Override
public void onRightClick(View view) {
}
});
localVideoPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/Camera/activity_local_video.mp4";
//checkPermission
int permission = ActivityCompat.checkSelfPermission(this, "android.permission.WRITE_EXTERNAL_STORAGE");
if (permission != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{
"android.permission.READ_EXTERNAL_STORAGE",
"android.permission.WRITE_EXTERNAL_STORAGE"}, 1);
}
//cp video 防止视频被意外删除
cpAssertVideoToLocalPath();
mJzVideo.setUp("local_video.mp4", "4K视频", JzvdStd.SCREEN_NORMAL, JZMediaSystemAssertFolder.class);
mJzVideo.posterImageView.setImageResource(R.mipmap.ic_cover);
}
@OnClick({R.id.iv_machine_no,R.id.iv_machine_ok})
public void clicked(View view) {
switch (view.getId()) {
case R.id.iv_machine_no:
MessageBean messageBean = new MessageBean();
messageBean.setItemNum(5);
messageBean.setStatus(500);
EventBus.getDefault().post(messageBean);
readyGo(MainActivity.class);
finish();
break;
case R.id.iv_machine_ok:
MessageBean message = new MessageBean();
message.setItemNum(5);
message.setStatus(200);
EventBus.getDefault().post(message);
readyGo(MainActivity.class);
finish();
break;
default:
break;
}
}
public void cpAssertVideoToLocalPath() {
if (new File(localVideoPath).exists()) return;
try {
InputStream myInput;
OutputStream myOutput = new FileOutputStream(localVideoPath);
myInput = this.getAssets().open("local_video.mp4");
byte[] buffer = new byte[1024];
int length = myInput.read(buffer);
while (length > 0) {
myOutput.write(buffer, 0, length);
length = myInput.read(buffer);
}
myOutput.flush();
myInput.close();
myOutput.close();
Toast.makeText(this, "cp from assert to local path succ", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onBackPressed() {
if (Jzvd.backPress()) {
return;
}
super.onBackPressed();
}
@Override
protected void onPause() {
super.onPause();
JZUtils.clearSavedProgress(this, null);
Jzvd.releaseAllVideos();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 1) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
cpAssertVideoToLocalPath();
} else {
finish();
}
}
}
}
五、播放本地音频
1. 效果图
2. JzvdStdMp3.java
/**
* Created on 2022/3/14 14:56
*
* @author Gong Youqiang
*/
public class JzvdStdMp3 extends JzvdStd {
public JzvdStdMp3(Context context) {
super(context);
}
public JzvdStdMp3(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public int getLayoutId() {
return R.layout.jz_layout_mp3;
}
@Override
public void onClick(View v) {
if (v.getId() == cn.jzvd.R.id.poster &&
(state == STATE_PLAYING ||
state == STATE_PAUSE)) {
onClickUiToggle();
} else if (v.getId() == R.id.fullscreen) {
} else {
super.onClick(v);
}
}
//changeUiTo 真能能修改ui的方法
@Override
public void changeUiToNormal() {
super.changeUiToNormal();
}
@Override
public void changeUiToPreparing() {
super.changeUiToPreparing();
}
@Override
public void changeUiToPlayingShow() {
super.changeUiToPlayingShow();
posterImageView.setVisibility(View.VISIBLE);
}
@Override
public void changeUiToPlayingClear() {
super.changeUiToPlayingClear();
posterImageView.setVisibility(View.VISIBLE);
}
@Override
public void changeUiToPauseShow() {
super.changeUiToPauseShow();
posterImageView.setVisibility(View.VISIBLE);
}
@Override
public void changeUiToPauseClear() {
super.changeUiToPauseClear();
posterImageView.setVisibility(View.VISIBLE);
}
@Override
public void changeUiToComplete() {
super.changeUiToComplete();
}
@Override
public void changeUiToError() {
super.changeUiToError();
}
}
3. jz_layout_mp3.xml
4. 使用
public class MachineActivity extends BaseActivity{
@BindView(R.id.title_bar)
TitleBar mTitleBar;
@BindView(R.id.jz_mp3)
JzvdStdMp3 mStdMp3;
@Override
protected int getLayoutId() {
return R.layout.activity_machine;
}
@Override
protected void initView() {
mTitleBar.setOnTitleBarListener(new OnTitleBarListener() {
@Override
public void onLeftClick(View view) {
finish();
}
@Override
public void onTitleClick(View view) {
}
@Override
public void onRightClick(View view) {
}
});
mStdMp3.setUp("machine.mp3", "机震测试", JzvdStd.SCREEN_NORMAL,JZMediaSystemAssertFolder.class);
mStdMp3.posterImageView.setImageResource(R.mipmap.ic_machine_thump);
}
@Override
public void onBackPressed() {
if (Jzvd.backPress()) {
return;
}
super.onBackPressed();
}
@Override
protected void onPause() {
super.onPause();
JZUtils.clearSavedProgress(this, null);
Jzvd.releaseAllVideos();
}
}