項目地址https://github.com/979451341/Myijkplayer前段時候我覺得FFmpeg做個視頻播放器好難,雖然播放上沒問題,但暫停還有通過拖動進度條來設置播放進度,這些都即便做得到,可以那個延緩。。。。。現在學習一下目前移動端最知名的視頻播放器的框架ijkplayer,這 ...
項目地址
https://github.com/979451341/Myijkplayer
前段時候我覺得FFmpeg做個視頻播放器好難,雖然播放上沒問題,但暫停還有通過拖動進度條來設置播放進度,這些都即便做得到,可以那個延緩。。。。。
現在學習一下目前移動端最知名的視頻播放器的框架ijkplayer,這個框架他是基於FFmpeg、SDL、還有安卓原生API
MediaCodec之類的。他是沒有播放界面的,這個需要我們去做,所以這個裡我就做個基於ijkplayer的視頻播放器,隨便淺顯的說一下ijkplayer的源碼,關於ijkplayer的源碼以後會專門出一篇博客說一下。
1.首先瞭解一下ijkplayer咋用
我這裡引入ijkplayer是通過添加依賴
implementation 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8' implementation 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.8' implementation 'tv.danmaku.ijk.media:ijkplayer-armv5:0.8.8' implementation 'tv.danmaku.ijk.media:ijkplayer-arm64:0.8.8' implementation 'tv.danmaku.ijk.media:ijkplayer-x86:0.8.8' implementation 'tv.danmaku.ijk.media:ijkplayer-x86_64:0.8.8' implementation 'tv.danmaku.ijk.media:ijkplayer-exo:0.8.8'
然後說說ijkplayer是如何播放視頻的
ijkplayer每一次播放視頻都是通過創建Mediaplayer,然後賦值到一個介面類上,這裡他創建的時候能夠挑選解碼的類型,是因為基於安卓原生API
MediaCodec的話是硬解,速度快、相容差,如果是基於FFmpeg則是軟解,速度慢、相容好,不過這個相容問題,因為我們在引入依賴的時候把各個處理器相應的依賴,所以可以使用硬解,相容問題基本都是手機處理器不同產生的。
IjkMediaPlayer ijkMediaPlayer = new IjkMediaPlayer(); ijkMediaPlayer.native_setLogLevel(IjkMediaPlayer.IJK_LOG_DEBUG); // //開啟硬解碼 ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 1); IMediaPlayer mMediaPlayer = null; mMediaPlayer = ijkMediaPlayer;
關於IjkMediaPlayer的源碼我只貼出一個函數的,從下麵幾個loadLibrary看出來,他還是基於FFmpeg、SDL底層實現的。
public static void loadLibrariesOnce(IjkLibLoader libLoader) { Class var1 = IjkMediaPlayer.class; synchronized(IjkMediaPlayer.class) { if(!mIsLibLoaded) { if(libLoader == null) { libLoader = sLocalLibLoader; } libLoader.loadLibrary("ijkffmpeg"); libLoader.loadLibrary("ijksdl"); libLoader.loadLibrary("ijkplayer"); mIsLibLoaded = true; } } }
好了,回到那個介面類的IMediaPlayer,源碼不多貼出來看一下,通過這些介面函數我們都可以知道這個ijkplayer如何使用我們都有了一個底,什麼setDataSource、setDisplay,設置播放源、設置播放的屏幕信息。還有start、stop、pause,視頻播放的開始、停止、暫停,還有一大堆的介面,這些都是為了監聽播放器的狀態
public interface IMediaPlayer { 。。。。。。 void setDisplay(SurfaceHolder var1); void setDataSource(Context var1, Uri var2) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException; @TargetApi(14) void setDataSource(Context var1, Uri var2, Map<String, String> var3) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException; void setDataSource(FileDescriptor var1) throws IOException, IllegalArgumentException, IllegalStateException; void setDataSource(String var1) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException; String getDataSource(); void prepareAsync() throws IllegalStateException; void start() throws IllegalStateException; void stop() throws IllegalStateException; void pause() throws IllegalStateException; void setScreenOnWhilePlaying(boolean var1); int getVideoWidth(); int getVideoHeight(); boolean isPlaying(); void seekTo(long var1) throws IllegalStateException; long getCurrentPosition(); long getDuration(); void release(); void reset(); void setVolume(float var1, float var2); int getAudioSessionId(); MediaInfo getMediaInfo(); /** @deprecated */ @Deprecated void setLogEnabled(boolean var1); /** @deprecated */ @Deprecated boolean isPlayable(); void setOnPreparedListener(IMediaPlayer.OnPreparedListener var1); void setOnCompletionListener(IMediaPlayer.OnCompletionListener var1); void setOnBufferingUpdateListener(IMediaPlayer.OnBufferingUpdateListener var1); void setOnSeekCompleteListener(IMediaPlayer.OnSeekCompleteListener var1); void setOnVideoSizeChangedListener(IMediaPlayer.OnVideoSizeChangedListener var1); void setOnErrorListener(IMediaPlayer.OnErrorListener var1); void setOnInfoListener(IMediaPlayer.OnInfoListener var1); void setOnTimedTextListener(IMediaPlayer.OnTimedTextListener var1); void setAudioStreamType(int var1); /** @deprecated */ @Deprecated void setKeepInBackground(boolean var1); int getVideoSarNum(); int getVideoSarDen(); /** @deprecated */ @Deprecated void setWakeMode(Context var1, int var2); void setLooping(boolean var1); boolean isLooping(); ITrackInfo[] getTrackInfo(); void setSurface(Surface var1); void setDataSource(IMediaDataSource var1); public interface OnTimedTextListener { void onTimedText(IMediaPlayer var1, IjkTimedText var2); } public interface OnInfoListener { boolean onInfo(IMediaPlayer var1, int var2, int var3); } public interface OnErrorListener { boolean onError(IMediaPlayer var1, int var2, int var3); } public interface OnVideoSizeChangedListener { void onVideoSizeChanged(IMediaPlayer var1, int var2, int var3, int var4, int var5); } public interface OnSeekCompleteListener { void onSeekComplete(IMediaPlayer var1); } public interface OnBufferingUpdateListener { void onBufferingUpdate(IMediaPlayer var1, int var2); } public interface OnCompletionListener { void onCompletion(IMediaPlayer var1); } public interface OnPreparedListener { void onPrepared(IMediaPlayer var1); } }
2.寫界面
全屏播放,潛入式
//正真的全屏,隱藏了狀態欄、AtionBar、導航欄 @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus && Build.VERSION.SDK_INT >= 19) { View decorView = getWindow().getDecorView(); decorView.setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); } }
然後上下兩個欄目,負責一些播放器的控制,頂部負責設置返回、設置按鈕,底部需要設置播放/暫停按鈕、播放進度條、停止按鈕
<com.example.zth.two.VideoPlayerIJK android:id="@+id/ijk_player" android:layout_width="match_parent" android:layout_height="match_parent"/> <include android:id="@+id/include_play_top" layout="@layout/include_play_top" android:layout_alignParentTop="true" android:layout_width="match_parent" android:layout_height="50dp" /> <include android:id="@+id/include_play_bottom" layout="@layout/include_play_bottom" android:layout_alignParentBottom="true" android:layout_width="match_parent" android:layout_height="50dp" />
關於上下兩個欄目在用戶觀看視頻時需要隱藏,在用戶點擊屏幕則顯示兩個欄目,供用戶使用
這個則是需要通過計時器來完成記錄目前距離上一次用戶點擊屏幕的時間,如果視頻超過3秒,則隱藏欄目,如果點擊屏幕則恢復,關於隱藏和恢復使用Animation來完成。
timer = new Timer(); timerTask = new TimerTask() { @Override public void run() { long t = System.currentTimeMillis(); if (t - time > 3000 && menu_visible) { time = t; handler.post(new Runnable() { @Override public void run() { Animation animation = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.move_bottom); rl_bottom.startAnimation(animation); Animation animation_top = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.move_top); rl_top.startAnimation(animation_top); menu_visible = false; } }); } } };
還需要載入框,這個在視頻載入完的介面回調里隱藏他
<ProgressBar android:id="@+id/pb_loading" android:layout_width="60dp" android:layout_height="60dp" android:layout_centerInParent="true" android:layout_marginTop="60dp" android:indeterminate="false" android:indeterminateDrawable="@drawable/video_loading" android:padding="5dp" />
3.播放實現
這裡我是使用了一個另一個博主封裝的ijkplayer的類
說一下這個類的運行過程
一開始創建MediaPlayer做一些配置,賦值給一個介面類,並且暴露了介面
IjkMediaPlayer ijkMediaPlayer = new IjkMediaPlayer(); ijkMediaPlayer.native_setLogLevel(IjkMediaPlayer.IJK_LOG_DEBUG); // //開啟硬解碼 ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 1); mMediaPlayer = ijkMediaPlayer; if (listener != null) { mMediaPlayer.setOnPreparedListener(listener); mMediaPlayer.setOnInfoListener(listener); mMediaPlayer.setOnSeekCompleteListener(listener); mMediaPlayer.setOnBufferingUpdateListener(listener); mMediaPlayer.setOnErrorListener(listener); }
然後設置播放源,這個播放源能夠是本地視頻路徑、網路視頻url、還可以是網路RTMP推流url,還要講SurfaceView的配置信息給他
try { mMediaPlayer.setDataSource(mPath); } catch (IOException e) { e.printStackTrace(); } //給mediaPlayer設置視圖 mMediaPlayer.setDisplay(surfaceView.getHolder()); mMediaPlayer.prepareAsync();
想開始播放就調用IMediaPlayer的那些控制函數
public void start() { if (mMediaPlayer != null) { mMediaPlayer.start(); } }
還有關於activity的生命周期控制,其中native_profileEnd就相當於很智能的暫停,當屏幕回的時候就繼續播放視頻。
@Override protected void onStop() { IjkMediaPlayer.native_profileEnd(); handler.removeCallbacksAndMessages(null); super.onStop(); }
結束Activity的時候就停止播放視頻並釋放資源
@Override protected void onDestroy() { if (ijkPlayer != null) { ijkPlayer.stop(); ijkPlayer.release(); ijkPlayer = null; } super.onDestroy(); }
最後還有播放進度,我這裡是通過handler自己調用自己迴圈更新播放時間顯示和進度條
handler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_REFRESH: if (ijkPlayer.isPlaying()) { refresh(); handler.sendEmptyMessageDelayed(MSG_REFRESH, 1000); } break; } } }; private void refresh() { long current = ijkPlayer.getCurrentPosition() / 1000; long duration = ijkPlayer.getDuration() / 1000; Log.v("zzw", current + " " + duration); long current_second = current % 60; long current_minute = current / 60; long total_second = duration % 60; long total_minute = duration / 60; String time = current_minute + ":" + current_second + "/" + total_minute + ":" + total_second; tvTime.setText(time); if(duration != 0){ seekBar.setProgress((int) (current * 100 / duration)); } }
在用戶拖動進度條的時候取消handler發送消息,拖動結束再接續更新播放進度
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int i, boolean b) { //進度改變 } @Override public void onStartTrackingTouch(SeekBar seekBar) { //開始拖動 handler.removeCallbacksAndMessages(null); } @Override public void onStopTrackingTouch(SeekBar seekBar) { //停止拖動 ijkPlayer.seekTo(ijkPlayer.getDuration() * seekBar.getProgress() / 100); handler.sendEmptyMessageDelayed(MSG_REFRESH, 100); } });
關於播放網路視頻和網路RTMP推流,播放本身沒問題就是不能獲取視頻時長和視頻當前時間,不過暫停和停止還有效。
還有就是如果想要重新播放需要重新setVideoPath然後start
看看效果
本地視頻播放
播放RTMP推流
播放網路視頻
ijkplayer的使用看起來是不是很簡單,但是還沒有實現小視窗和全屏之間的切換。。。。。。這兩天我再看看ijkplayer,再和各位說說