1.上地址 https://github.com/lipangit/JiaoZiVideoPlayer
2.JiaoZiVideo主要特點
- 可以完全自定義UI和任何功能
- 可以完全自定義UI和任何功能
- 一行代碼切換播放引擎,支持的視頻格式和協議取決於播放引擎,android.media.MediaPlayer ijkplayer
- 完美檢測列表滑動
- 可實現全屏播放,小窗播放
- 能在ListView、ViewPager和ListView、ViewPager和Fragment等多重嵌套模式下全屏工作
- 可以在加載、暫停、播放等各種狀態中正常進入全屏和退出全屏
- 多種視頻適配屏幕的方式,可鋪滿全屏,可以全屏剪裁
- 重力感應自動進入全屏
- 全屏后手勢修改進度和音量
- Home鍵退出界面暫停播放,返回界面繼續播放
3.開始使用
引入
implementation 'cn.jzvd:jiaozivideoplayer:7.0.3'
xml
<LinearLayout android:layout_width="match_parent" android:layout_height="200dp"> <cn.jzvd.demo.CustomJzvd.MyJzvdStd android:id="@+id/jz_video" android:layout_width="match_parent" android:layout_height="200dp" /> </LinearLayout>
java
MyJzvdStd jzvdStd = (MyJzvdStd) findViewById(R.id.jz_video); jzvdStd.setUp("http://jzvd.nathen.cn/c6e3dc12a1154626b3476d9bf3bd7266/6b56c5f0dc31428083757a45764763b0-5287d2089db37e62345123a1be272f8b.mp4" , "視頻標題"); jzvdStd.thumbImageView.setImage("http://p.qpic.cn/videoyun/0/2449_43b6f696980311e59ed467f22794e792_1/640");
@Override public void onBackPressed() { if (Jzvd.backPress()) { return; } super.onBackPressed(); } @Override protected void onPause() { super.onPause(); Jzvd.releaseAllVideos(); }
AndroidManifest.xml
<activity android:name=".MainActivity" android:configChanges="orientation|screenSize|keyboardHidden" android:screenOrientation="portrait" /> <!-- or android:screenOrientation="landscape"-->
自動播放
自動播放有兩種 這里隨便選擇添加一個, 1. myJzvdStd.startButton.performClick(); 2. myJzvdStd.startVideo();
跳轉制定位置播放
//這里只有開始播放時才生效 mJzvdStd.seekToInAdvance = 20000; //跳轉制定位置播放 JZMediaManager.seekTo(30000); 2.播放sd卡下視頻 public void cpAssertVideoToLocalPath() { 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, );
播放assets目錄下的視頻
復制Demo中CustomMediaPlayerAssertFolder類到你的項目下 ---------------------------------------------------------------------------- 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());//進入此頁面修改MediaInterface,讓此頁面的jzvd正常工作
直接全屏播放
JzvdStd.startFullscreen(this, JzvdStd.class, VideoConstant.videoUrlList[6], "視頻標題");
開啟小窗播放
mJzvdStd.startWindowTiny();
列表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(); } } } });
小屏播放無聲音,全屏有聲音
創建一個類繼承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); } }
全屏狀態播放完成,不退出全屏
創建一個類繼承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(); } } }
全屏模式下顯示分享按鈕
復制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); } } }
小屏狀態下不顯示標題,全屏模式下顯示標題
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); } } }
播放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);
播放完成不顯示預覽圖
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); } }
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(); }
邊播邊緩存和清晰度切換
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);
重復播放
創建一個類集成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循環標志
重力感應自動進入全屏
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); }
重力感應
Jzvd.FULLSCREEN_ORIENTATION=ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; Jzvd.NORMAL_ORIENTATION = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; 兩個變量控制全屏前后的屏幕方向
不保存播放進度
Jzvd.SAVE_PROGRESS = false;
取消播放時在非WIFIDialog提示
Jzvd.WIFI_TIP_DIALOG_SHOWED=true;
清除某個URL進度
Jzvd.clearSavedProgress(this, "url");
切換播放內核
ijk
復制Demo中JZMediaIjkplayer類到你的項目下
implementation 'tv.danmaku.ijk.media:ijkplayer-java:0.8.4'
implementation 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.4'
Jzvd.setMediaInterface(new JZMediaIjkplayer()); // ijkMediaPlayer
Mediaplayer
Jzvd.setMediaInterface(new JZMediaSystem()); //
exo
復制Demo中JZExoPlayer類到你的項目下
implementation 'com.google.android.exoplayer:exoplayer:2.7.1' Jzvd.setMediaInterface(new JZExoPlayer()); //exo
用戶埋點統計
Jzvd.setJzUserAction(new MyUserActionStd()); /** * 這只是給埋點統計用戶數據用的,不能寫和播放相關的邏輯,監聽事件請參考MyJzvdStd,復寫函數取得相應事件 */ class MyUserActionStd implements JZUserActionStd { @Override public void onEvent(int type, Object url, int screen, Object... objects) { switch (type) { 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; } } }
相關函數回調,屏幕狀態,播放器狀態,事件
在繼承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 錯誤狀態
復寫進入播放狀態的函數,取得播放狀態的回調
- onStateNormal 進入普通狀態,通常指setUp之后
- onStatePreparing 進入准備中狀態,就是loading狀態
- onStatePlaying 進入播放狀態
- onStatePause 進入暫停狀態
- onStateError 進入錯誤狀態
- onStateAutoComplete 進入自動播放完成狀態
全屏、小窗、非全屏分別是不同的實例,在繼承JzvdStd后,通過mCurrentScreen變量,取得當前屏幕類型
- SCREEN_WINDOW_NORMAL 普通窗口(進入全屏之前的)
- SCREEN_WINDOW_LIST 列表窗口(進入全屏之前)
- SCREEN_WINDOW_FULLSCREEN 全屏
- SCREEN_WINDOW_TINY 小窗
事件
復寫onProgress函數,取得每次播放器設置底部seekBar的進度回調
調用changeUrl函數,切換url
復寫onClick函數,取得各種按鈕的點擊事件
復寫onTouch函數,取得全屏之后的手勢操作