準備
作為一個常年混跡于嗶哩嗶哩和斗魚的老油條,想自己實現簡單直播的心愿已經很久了,今天正好有時間就簡單的實現下。
一、打算實現的功能
- 簡單搭建一套直播的rtmp流媒體服務器,能夠簡單實現視頻的實時推送。
- android手機端實現對rtmp流的播放。
- 實現彈幕效果,能夠發送彈幕。
二、思路
-
關于直播的rtmp流媒體服務器的搭建,可以看我的這篇文章
-
關于android手機端實現rtmp流的播放,由于android自生帶的videoview不支持rtmp協議,所以我們得找一個支持rtmp協議的播放器。我這邊使用了bilibili在github上開源項目ijkplayer
-
彈幕實現,也使用了bilibili的開源彈幕項目DanmakuFlameMaster
ijkplayer的搭建與實現
一、導入ijkplayer依賴包
- IDE和構建工具:androidstudio Gradle
- ijkplayer版本號:0.8.4
找到gradle配置文件build.gradle(Module:app),注意是app的配置文件,然后在dependencies添加如下配置,然后如下圖所示點擊sync,重新下載并導入依賴的ijkplayer包,這里我根據ijkplayer給的示例倒入了所有的依賴包,具體如下
// # required, enough for most devices.
compile 'tv.danmaku.ijk.media:ijkplayer-java:0.8.4'
compile 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.4'
// # Other ABIs: optional
compile 'tv.danmaku.ijk.media:ijkplayer-armv5:0.8.4'
compile 'tv.danmaku.ijk.media:ijkplayer-arm64:0.8.4'
compile 'tv.danmaku.ijk.media:ijkplayer-x86:0.8.4'
compile 'tv.danmaku.ijk.media:ijkplayer-x86_64:0.8.4'
// # ExoPlayer as IMediaPlayer: optional, experimental
compile 'tv.danmaku.ijk.media:ijkplayer-exo:0.8.4'
注:在導入ijkplayer依賴包的時候,由于ijkplayer是基于FFmpeg改造的,FFmpeg需要使用到ndk,請確保ndk已經下載安裝,如下圖所示,請先下載LLDB和NDK,否則上述導入依賴包時會報錯。
二、在manifest文件中申明網絡訪問權限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
三、創建IjkVideoView
導入ijkplayer包后,我們還需要在android端布局一個播放器來加載rtmp流,在ijkplayer中并沒有集成IjkVideoView,我們找到ijkplayer的android示例項目,示例項目中使用自己開發的一個IjkVideoView,我們直接使用它。
如下圖所示,由于IjkVideoView調用了其他的類和資源,我這邊都直接集成到了項目中了,具體的細節,大家可以直接看我上面的demo。
四、使用IjkVideoView加載rtmp流
首先需要初始化IjkMediaPlayer,定義IjkVideoView以及相關的播放控制器,最終通過IjkVideoView.start()開始播放,具體代碼如下:
//定義的直播地址
String path = "rtmp://10.2.103.81:1935/live/room1";
//初始化IjkMediaPlayer
IjkMediaPlayer.loadLibrariesOnce(null);
IjkMediaPlayer.native_profileBegin("libijkplayer.so");
//定義IjkVideoView
mVideoView = (IjkVideoView) findViewById(R.id.video_view);
//定義的播放按鈕的layout,用來加載定義好的播放界面
mHudView = (TableLayout) findViewById(R.id.hud_view);
//這里使用的是Demo中提供的AndroidMediaController類控制播放相關操作
mMediaController = new AndroidMediaController(this, false);
ActionBar actionBar = getSupportActionBar();
mMediaController.setSupportActionBar(actionBar);
mVideoView = (IjkVideoView) findViewById(R.id.video_view);
mVideoView.setMediaController(mMediaController);
mVideoView.setHudView(mHudView);
//設置videopath,開始播放
mVideoView.setVideoPath(path);
mVideoView.start();
五、導入DanmakuFlameMaster依賴包
- IDE和構建工具:androidstudio Gradle
- DanmakuFlameMaster版本號:0.9.12
找到gradle配置文件build.gradle(Module:app),注意是app的配置文件,然后在dependencies添加如下配置,然后如下圖所示點擊sync,重新下載并導入依賴的DanmakuFlameMaster包,具體如下
compile 'com.github.ctiao:DanmakuFlameMaster:0.9.12'
六、生成彈幕
定義彈幕解析器,用來解析彈幕輸入,代碼如下:
/**
* 創建解析器,解析彈幕輸入
*/
private BaseDanmakuParser parser = new BaseDanmakuParser() {
@Override
protected IDanmakus parse() {
return new Danmakus();
}
};
調用danmakuView.addDanmaku方法,向彈幕中添加彈幕,代碼如下:
/**
* 向彈幕View中添加一條彈幕
*/
private void addDanmaku(String content, boolean withBorder) {
BaseDanmaku danmaku = danmakuContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);
danmaku.text = content;
danmaku.padding = 5;
danmaku.textSize = sp2px(20);
danmaku.textColor = Color.WHITE;
danmaku.setTime(danmakuView.getCurrentTime());
if (withBorder) {
danmaku.borderColor = Color.GREEN;
}
danmakuView.addDanmaku(danmaku);
}
根據時間隨機生成彈幕用來測試,這里就不實現手動輸入然后產生彈幕的功能了,代碼如下:
/**
* 隨機生成一些彈幕內容以供測試
*/
private void generateSomeDanmaku() {
new Thread(new Runnable() {
@Override
public void run() {
while(showDanmaku) {
int time = new Random().nextInt(300);
String content = "" + time + time;
addDanmaku(content, false);
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
定義DanmakuView現實彈幕,代碼如下:
//加載彈幕界面
danmakuView = (DanmakuView) findViewById(R.id.danmaku_view);
danmakuView.enableDanmakuDrawingCache(true);
danmakuView.setCallback(new DrawHandler.Callback() {
@Override
public void prepared() {
showDanmaku = true;
danmakuView.start();
generateSomeDanmaku();
}
@Override
public void updateTimer(DanmakuTimer timer) {
}
@Override
public void danmakuShown(BaseDanmaku danmaku) {
}
@Override
public void drawingFinished() {
}
});
danmakuContext = DanmakuContext.create();
danmakuView.prepare(parser, danmakuContext);