您好,登錄后才能下訂單哦!
項目地址
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,再和各位說說
博客首發地址
http://blog.csdn.net/z979451341
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。