视频开发----之饺子播放器!
GitHub代码地址: https://github.com/lipangit/JiaoZiVideoPlayer
一. 简介:
JZVedioPlayer 是目前发现的集成和使用起来最简单,也是稳定度很高的一个库JZVedioPlayer 封装了 UI 层和播放器层,让我们可以简简单单的一键集成.
二. 集成以及使用:
compile 'cn.jzvd:jiaozivideoplayer:6.2.12' 或 jiaozivideoplayer.jar依赖库(不推荐)
JiaoZiVideoPlayer主要功能使用:
- 可以完全自定义UI和任何功能
- 多种视频适配屏幕的方式,可铺满全屏,可以全屏剪裁
- 完美检测列表滑动
- 可以加载,暂停,播放和其他正常状态进入全屏并退出全屏
- 切换播放引擎,支持的视频格式和协议的一行代码取决于播放引擎,如: android.media.MediaPlayer,IJKplayer,ExoPlayer。
- 全屏适用于多种嵌套模式,如ListView,ViewPager和ListView,ViewPager和Fragment
- 提供全屏和小窗口选项
- Home键退出界面暂停播放,返回界面继续播放
- WebView嵌套本地视频控件
- 全屏后手势修改进度和音量
- VideoCache在演示中
三. 接下来我们从三步来实现简单视频播放
1) 首先设置你的布局文件
<cn.jzvd.demo.CustomView.MyJzvdStd
android:id="@+id/jz_video"
android:layout_width="match_parent"
android:layout_height="300dp" />
2) ActivityMain中设置视频初始化操作
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化控件
myJzvdStd = findViewById(R.id.jz_video);
/**
* 参数说明
* 视频播放地址
* 设置视频标题
*/
myJzvdStd.setUp("http://jzvd.nathen.cn/342a5f7ef6124a4a8faf00e738b8bee4/cf6d9db0bd4d41f59d09ea0a81e918fd-5287d2089db37e62345123a1be272f8b.mp4"
, "饺子快长大", JzvdStd.SCREEN_WINDOW_NORMAL);
//设置视频封面
Glide.with(this).load("http://jzvd-pic.nathen.cn/jzvd-pic/1bb2ebbe-140d-4e2e-abd2-9e7e564f71ac.png").into(myJzvdStd.thumbImageView);
Jzvd.setJzUserAction(new MyUserActionStd());
}
@Override
protected void onPause() {
super.onPause();
Jzvd.releaseAllVideos();
}
@Override
public void onBackPressed() {
if (Jzvd.backPress()) {
return;
}
super.onBackPressed();
}
/**
* 这只是给埋点统计用户数据用的,不能写和播放相关的逻辑,监听事件请参考MyJzvdStd,复写函数取得相应事件
*/
class MyUserActionStd implements JZUserActionStd {
@Override
public void onEvent(int type, Object url, int screen, Object... objects) {
switch (type) {
//继承JzvdStd之后,可以通过父类的mCurrentState,取得当前的播放状态。
//CURRENT_STATE_IDLE 未知状态,指控件被new出来之后什么都没做
//CURRENT_STATE_NORMAL 普通状态
//CURRENT_STATE_PREPARING 视频准备状态
//CURRENT_STATE_PREPARING_CHANGING_URL 播放中切换url的准备状态
//CURRENT_STATE_PLAYING 播放中状态
//CURRENT_STATE_PAUSE 暂停状态
//CURRENT_STATE_AUTO_COMPLETE 自动播放完成状态
//CURRENT_STATE_ERROR 错误状态
case JZUserAction.ON_CLICK_START_ICON:
Log.i("USER_EVENT", "ON_CLICK_START_ICON" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserAction.ON_CLICK_START_ERROR:
Log.i("USER_EVENT", "ON_CLICK_START_ERROR" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserAction.ON_CLICK_START_AUTO_COMPLETE:
Log.i("USER_EVENT", "ON_CLICK_START_AUTO_COMPLETE" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserAction.ON_CLICK_PAUSE:
Log.i("USER_EVENT", "ON_CLICK_PAUSE" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserAction.ON_CLICK_RESUME:
Log.i("USER_EVENT", "ON_CLICK_RESUME" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserAction.ON_SEEK_POSITION:
Log.i("USER_EVENT", "ON_SEEK_POSITION" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserAction.ON_AUTO_COMPLETE:
Log.i("USER_EVENT", "ON_AUTO_COMPLETE" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserAction.ON_ENTER_FULLSCREEN:
Log.i("USER_EVENT", "ON_ENTER_FULLSCREEN" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserAction.ON_QUIT_FULLSCREEN:
Log.i("USER_EVENT", "ON_QUIT_FULLSCREEN" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserAction.ON_ENTER_TINYSCREEN:
Log.i("USER_EVENT", "ON_ENTER_TINYSCREEN" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserAction.ON_QUIT_TINYSCREEN:
Log.i("USER_EVENT", "ON_QUIT_TINYSCREEN" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserAction.ON_TOUCH_SCREEN_SEEK_VOLUME:
Log.i("USER_EVENT", "ON_TOUCH_SCREEN_SEEK_VOLUME" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserAction.ON_TOUCH_SCREEN_SEEK_POSITION:
Log.i("USER_EVENT", "ON_TOUCH_SCREEN_SEEK_POSITION" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserActionStd.ON_CLICK_START_THUMB:
Log.i("USER_EVENT", "ON_CLICK_START_THUMB" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserActionStd.ON_CLICK_BLANK:
Log.i("USER_EVENT", "ON_CLICK_BLANK" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
default:
Log.i("USER_EVENT", "unknow");
break;
}
}
}
3) 在清单文件中AndroidManifest.xml中添加 "android:configChanges="orientation|screenSize|keyboardHidden"
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:screenOrientation="portrait" />
四. JiaoZiVideoPlayer 基本使用
1) 自动播放(两种方法选其一)
myJzvdStd.startButton.performClick();
myJzvdStd.startVideo();
/*
*跳转制定位置播放
*这里只有开始播放时才生效
*/
mJzvdStd.seekToInAdvance = 20000;
//跳转制定位置播放
JZMediaManager.seekTo(30000);
2) 播放本地下下视频
public void getAssertVideoToLocalPath() {
try {
InputStream myInput;
OutputStream myOutput = new FileOutputStream(Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/Camera/local_video.mp4");
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();
} catch (IOException e) {
e.printStackTrace();
}
}
myJzvdStd.setUp(Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/Camera/local_video.mp4", "饺子不信",Jzvd.SCREEN_WINDOW_NORMAL, );
3) 播放assets目录下的视频
JZDataSource jzDataSource = null;
try {
jzDataSource = new JZDataSource(getAssets().openFd("local_video.mp4"));
jzDataSource.title = "饺子快长大";
} catch (IOException e) {
e.printStackTrace();
}
jzvdStd.setUp(jzDataSource, JzvdStd.SCREEN_WINDOW_NORMAL);
Glide.with(this)
.load("http://jzvd-pic.nathen.cn/jzvd-pic/1bb2ebbe-140d-4e2e-abd2-9e7e564f71ac.png")
.into(jzvdStd.thumbImageView);
Jzvd.setMediaInterface(new CustomMediaPlayerAssertFolder());
4) 切换成全屏模式以及小窗口模式
//切换全屏
JzvdStd.startFullscreen(this, JzvdStd.class, VideoConstant.videoUrlList[6], "视频的标题");
//设置小窗口模式
mJzvdStd.startWindowTiny();
5) 列表Item划出开启小窗播放
1.Listview
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
Jzvd.onScrollAutoTiny(view, firstVisibleItem, visibleItemCount, totalItemCount);
// Jzvd.onScrollReleaseAllVideos(view, firstVisibleItem, visibleItemCount, totalItemCount); 这是不开启列表划出小窗 同时也是画出屏幕释放JZ 划出暂停
}
});
2. RecyclerView 划出列表开启小窗
recyclerView.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() {
@Override
public void onChildViewAttachedToWindow(View view) {
Jzvd.onChildViewAttachedToWindow(view, R.id.videoplayer);
}
@Override
public void onChildViewDetachedFromWindow(View view) {
Jzvd.onChildViewDetachedFromWindow(view);
}
});
2.1 RecyclerView划出屏幕释放JZ,同时也是不开启列表划出显示小窗
recyclerView.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() {
@Override
public void onChildViewAttachedToWindow(View view) {
}
@Override
public void onChildViewDetachedFromWindow(View view) {
Jzvd jzvd = view.findViewById(R.id.videoplayer);
if (jzvd != null && jzvd.jzDataSource.containsTheUrl(JZMediaManager.getCurrentUrl())) {
Jzvd currentJzvd = JzvdMgr.getCurrentJzvd();
if (currentJzvd != null && currentJzvd.currentScreen != Jzvd.SCREEN_WINDOW_FULLSCREEN) {
Jzvd.releaseAllVideos();
}
}
}
});
6) 小屏播放无声音,全屏有声音
创建一个类继承JzvdStd并在XML设置
public class JzvdStdVolumeAfterFullscreen extends JzvdStd {
public JzvdStdVolumeAfterFullscreen(Context context) {
super(context);
}
public JzvdStdVolumeAfterFullscreen(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onPrepared() {
super.onPrepared();
if (currentScreen == SCREEN_WINDOW_FULLSCREEN) {
JZMediaManager.instance().jzMediaInterface.setVolume(1f, 1f);
} else {
JZMediaManager.instance().jzMediaInterface.setVolume(0f, 0f);
}
}
/**
* 进入全屏模式的时候关闭静音模式
*/
@Override
public void startWindowFullscreen() {
super.startWindowFullscreen();
JZMediaManager.instance().jzMediaInterface.setVolume(1f, 1f);
}
/**
* 退出全屏模式的时候开启静音模式
*/
@Override
public void playOnThisJzvd() {
super.playOnThisJzvd();
JZMediaManager.instance().jzMediaInterface.setVolume(0f, 0f);
}
}
7) 全屏状态播放完成,不退出全屏
创建一个类继承JzvdStd并在XML设置
public class JzvdStdAutoCompleteAfterFullscreen extends JzvdStd {
public JzvdStdAutoCompleteAfterFullscreen(Context context) {
super(context);
}
public JzvdStdAutoCompleteAfterFullscreen(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void startVideo() {
if (currentScreen == SCREEN_WINDOW_FULLSCREEN) {
Log.d(TAG, "startVideo [" + this.hashCode() + "] ");
initTextureView();
addTextureView();
AudioManager mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
mAudioManager.requestAudioFocus(onAudioFocusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
JZUtils.scanForActivity(getContext()).getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
JZMediaManager.setDataSource(jzDataSource);
JZMediaManager.instance().positionInList = positionInList;
onStatePreparing();
} else {
super.startVideo();
}
}
@Override
public void onAutoCompletion() {
if (currentScreen == SCREEN_WINDOW_FULLSCREEN) {
onStateAutoComplete();
} else {
super.onAutoCompletion();
}
}
}
8) 全屏模式下显示分享按钮
复制DEMO下的layout文件在 layout_top 布局下 添加你的分享按钮
public class JzvdStdShowShareButtonAfterFullscreen extends JzvdStd {
public ImageView shareButton;
public JzvdStdShowShareButtonAfterFullscreen(Context context) {
super(context);
}
public JzvdStdShowShareButtonAfterFullscreen(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void init(Context context) {
super.init(context);
shareButton = findViewById(R.id.share);
shareButton.setOnClickListener(this);
}
@Override
public int getLayoutId() {
return R.layout.layout_standard_with_share_button;
}
@Override
public void onClick(View v) {
super.onClick(v);
if (v.getId() == R.id.share) {
Toast.makeText(getContext(), "Whatever the icon means", Toast.LENGTH_SHORT).show();
}
}
@Override
public void setUp(JZDataSource jzDataSource, int screen) {
super.setUp(jzDataSource, screen);
if (currentScreen == SCREEN_WINDOW_FULLSCREEN) {
shareButton.setVisibility(View.VISIBLE);
} else {
shareButton.setVisibility(View.INVISIBLE);
}
}
}
9) 小屏状态下不显示标题,全屏模式下显示标题
public class JzvdStdShowTitleAfterFullscreen extends JzvdStd {
public JzvdStdShowTitleAfterFullscreen(Context context) {
super(context);
}
public JzvdStdShowTitleAfterFullscreen(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void setUp(JZDataSource jzDataSource, int screen) {
super.setUp(jzDataSource, screen);
if (currentScreen == SCREEN_WINDOW_FULLSCREEN) {
titleTextView.setVisibility(View.VISIBLE);
} else {
titleTextView.setVisibility(View.INVISIBLE);
}
}
}
10) 播放MP3
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_standard_mp3;
}
@Override
public void onClick(View v) {
if (v.getId() == cn.jzvd.R.id.thumb &&
(currentState == CURRENT_STATE_PLAYING ||
currentState == CURRENT_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();
thumbImageView.setVisibility(View.VISIBLE);
}
@Override
public void changeUiToPlayingClear() {
super.changeUiToPlayingClear();
thumbImageView.setVisibility(View.VISIBLE);
}
@Override
public void changeUiToPauseShow() {
super.changeUiToPauseShow();
thumbImageView.setVisibility(View.VISIBLE);
}
@Override
public void changeUiToPauseClear() {
super.changeUiToPauseClear();
thumbImageView.setVisibility(View.VISIBLE);
}
@Override
public void changeUiToComplete() {
super.changeUiToComplete();
}
@Override
public void changeUiToError() {
super.changeUiToError();
}
}
jzvdStdMp3 = findViewById(R.id.jz_videoplayer_mp3);
jzvdStdMp3.setUp(URL, "饺子摇摆", Jzvd.SCREEN_WINDOW_NORMAL);
Glide.with(this)
.load(VideoConstant.videoThumbs[0][1])
.into(jzvdStdMp3.thumbImageView);
11) 播放完成不显示预览图
public class JzvdStdShowTextureViewAfterAutoComplete extends JzvdStd {
public JzvdStdShowTextureViewAfterAutoComplete(Context context) {
super(context);
}
public JzvdStdShowTextureViewAfterAutoComplete(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onAutoCompletion() {
super.onAutoCompletion();
thumbImageView.setVisibility(View.GONE);
}
}
12) Home键退出界面暂停播放,返回界面继续播放
@Override
protected void onResume() {
super.onResume();
//home back
JzvdStd.goOnPlayOnResume();
}
@Override
protected void onPause() {
super.onPause();
// Jzvd.clearSavedProgress(this, null);
//home back
JzvdStd.goOnPlayOnPause();
}
13) 边播边缓存和清晰度切换
1. 集成videocache implementation 'com.danikula:videocache:2.7.0',并初始化
public class ApplicationDemo extends Application {
@Override
public void onCreate() {
super.onCreate();
// LeakCanary.install(this);
}
private HttpProxyCacheServer proxy;
public static HttpProxyCacheServer getProxy(Context context) {
ApplicationDemo app = (ApplicationDemo) context.getApplicationContext();
return app.proxy == null ? (app.proxy = app.newProxy()) : app.proxy;
}
private HttpProxyCacheServer newProxy() {
return new HttpProxyCacheServer(this);
}
}
2.引用
LinkedHashMap map = new LinkedHashMap();
String proxyUrl = ApplicationDemo.getProxy(this).getProxyUrl(VideoConstant.videoUrls[0][9]);
map.put("高清", proxyUrl);
map.put("标清", VideoConstant.videoUrls[0][6]);
map.put("普清", VideoConstant.videoUrlList[0]);
JZDataSource jzDataSource = new JZDataSource(map, "饺子不信");
jzDataSource.looping = true;
jzDataSource.currentUrlIndex = 2;
jzDataSource.headerMap.put("key", "value");//header
mJzvdStd.setUp(jzDataSource
, JzvdStd.SCREEN_WINDOW_NORMAL);
Glide.with(this).load(VideoConstant.videoThumbList[0]).into(mJzvdStd.thumbImageView);
14) 重复播放
创建一个类集成JzvdStd并在XML设置
public class JZVideoPlayerStandardLoopVideo extends JzvdStd{
public JZVideoPlayerStandardLoopVideo (Context context) {
super(context);
}
public JZVideoPlayerStandardLoopVideo (Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onAutoCompletion() {
super.onAutoCompletion();
startVideo();
}
}
还有一种方法就是上面清晰度切换loop循环标志
15) 重力感应自动进入全屏
SensorManager mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
Jzvd.JZAutoFullscreenListener mSensorEventListener = new Jzvd.JZAutoFullscreenListener();
@Override
protected void onResume() {
super.onResume();
Sensor accelerometerSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mSensorManager.registerListener(mSensorEventListener, accelerometerSensor, SensorManager.SENSOR_DELAY_NORMAL);
}
@Override
protected void onPause() {
super.onPause();
mSensorManager.unregisterListener(mSensorEventListener);
}
16) 重力感应
Jzvd.FULLSCREEN_ORIENTATION=ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
Jzvd.NORMAL_ORIENTATION = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
两个变量控制全屏前后的屏幕方向
17) 不保存播放进度
Jzvd.SAVE_PROGRESS = false;
18) 取消播放时在非WIFIDialog提示
Jzvd.WIFI_TIP_DIALOG_SHOWED=true;
19) 清除某个URL进度
Jzvd.clearSavedProgress(this, "url");
到这里基本的JiaoZiVideoPlayer基本使用差不对都列出来了 不完善的请大家多多指出:
点我有DEMO哦