寫在前面的話
上一篇已經介紹了關于RTMP推流相關的知識,那么推完流后視頻服務器就會對推流進行轉發,那么這一篇主要就是介紹下,關于移動端播放與彈幕評論相關的知識。
一.播放
關于播放其實一般情況我們都是使用第三方的播放庫,因為這些庫相對來說支持各種不同的協議比如RTMP,HLS或者其他的視頻協議,那我們這里就使用Bilibili開發并開源的IJKPlayer播放器,關于這方面更多的講解可以參考之前寫的文章IJKPlayer初識-編譯與使用
我們只需要把視頻轉發的地址設置進去就好了
如下
由于錄制視頻再轉GIF不太方便,所以就單獨上一張圖了,明白是什么意思就好。
其實很多直播平臺也是用的IJKPlayer做的播放器,比如斗魚呀等等
對于現在的直播來說,彈幕評論也是直播平臺中很重要的一部分,所以接下來我們來探究下關于移動端關于彈幕評論的實現
二.彈幕評論
對于彈幕評論,其實客戶端我們可以想到的方法就是輪詢來不斷的獲取最新的評論數據,但是可想而知不停的網絡請求訪問,會對整個客戶端的性能帶來一定的問題,而且也會導致對于手機電量的消耗增大,所以我們得想出另外的方案來解決這個問題,自然就是參考現在比較成熟的PC網站是如何實現這部分的需求的,其實現在有部分的網站已經在使用WebSocket來實現實時彈幕的效果了,當然在我們移動端也是可以使用WebScoket的,所以我們就用WebScoket方案來實現彈幕評論
工欲善其事必先利其器,所以我們先了解下什么是WebScoket
WebScoket
WebSocket協議是一種建立在TCP連接基礎上的全雙工通信的協議,同http一樣通過TCP來傳輸數據,但是它和http最大的不同有兩點:1.WebSocket是一種雙向通信協議,在建立連接后,WebSocket服務器和Browser/UA都能主動的向對方發送或接收數據,就像Socket一樣,不同的是WebSocket是一種建立在Web基礎上的一種簡單模擬Socket的協議;2.WebSocket需要通過握手連接,類似于TCP它也需要客戶端和服務器端進行握手連接,連接成功后才能相互通信。
協議內容組成如下
WebSocket按上面圖中協議規則進行傳輸,上圖稱為一個數據幀。
FIN,共1位,標記消息是否是最后1幀,1個消息由1個或多個數據幀構成,若消息由1幀構成,起始幀就是結束幀。
RSV1,RSV2,RSV3,各1位,預留位,用于自定義擴展。如果沒有擴展,各位值為0;如果定義了擴展,即為非0值。如果接收的幀中此處為非0,但是擴展中卻沒有該值的定義,那么關閉連接。
-
OPCODE,共4位,幀類型,分為控制幀和非控制幀。如果接收到未知幀,接收端必須關閉連接。
WebSocket的控制幀有3種,關閉幀、Ping幀、Pong幀,關閉幀很好理解,客戶端如果收到關閉幀直接關閉連接即可,當然客戶端也可以發送關閉幀給服務器端。而Ping幀和Pong幀則是WebSocket的心跳檢測,用于保證客戶端是在線的,一般來說,只有服務端給客戶端發送Ping幀,然后客戶端發送Pong幀進行回應,表示自己還在線,可以進行后續通信。
MASK,共1位,掩碼位,表示幀中的數據是否經過加密,客戶端發出的數據幀需要經過掩碼處理,這個值都是1。如果值是1,那么Masking-key域的數據就是掩碼秘鑰,用于解碼PayloadData,否則Masking-key長度為0
-
Payload len,7位或者7+16位或者7+64位,表示數據幀中數據大小,這里有好幾種情況。
如果值為0-125,那么該值就是payload data的真實長度。
如果值為126,那么該7位后面緊跟著的2個字節就是payload data的真實長度。
如果值為127,那么該7位后面緊跟著的8個字節就是payload data的真實長度。
長度遵循一個原則,就是用最少的字節表示長度,舉個例子,當payload data的真實長度是124時,在0-125之間,必須用7位表示;不允許將這7位表示成126或者127,然后后面用2個字節或者8個字節表示124,這樣做就違反了原則。 Masking-key ,0或者4個字節,當MASK位為1時,4個字節,否則0個字節。如果MASK值為1,則發出去的數據需要經過加密處理
Payload data,其大小是(x+y)個字節,x是Extension data,即擴展數據,y是Application data,即程序數據,擴展數據可能為0。 如果擴展數據不為0,必須提前進行協商,規定其長度,否則是不合法的數據幀。
以上是WebSocket數據傳輸的幀內容,大致了解即可。
對于WebSocket和前面說的RTMP一樣也是有握手協議的過程,接下來我們就看一下WebSocket握手協議
客戶端發送get請求協議升級
GET /chat HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat,superchat
Sec-WebSocket-Version: 13
該請求會在請求頭上帶上WebSocket的版本號,這里是13,以及客戶端隨機生成的Sec-WebSocket-Key,服務器端收到后根據這個key進行一些處理,返回一個Sec-WebSocket-Accept的值給客戶端。
服務端返回同意升級到WebSocket協議
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept:s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
收到響應后,響應頭中包含Sec-WebSocket-Accept值,該值表示服務器端同意握手,值的計算方式如下:
$(Sec-WebSocket-Accept)=BASE64(SHA1($(Sec-WebSocket-Key)+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
客戶端得到該值后,對本地的Sec-WebSocket-Key進行同樣的編碼,然后對比,如果相同則可以進行后續處理。
關于WebSocket協議,一般來說,如果是通過https協議開始升級而來的,那么一般是wss://開頭,如果是http協議開始升級而來的,那么一般是ws://開頭
WebScoket實現彈幕效果
前面說了相關協議的東西,那么接下來就是實現具體的效果了,對于Android平臺,我們可以Java-WebSocket庫來實現WebSocket通信,當然除了這個還可以選擇其他的庫,我這里選擇Java-WebSocket來實現我們的功能了。
WebScoket服務器搭建
對于WebScoket服務器搭建,可以使用Java API javax.websocket包中的WebSocket相關類(注意Java API只實現了標準的RFC 6455(JSR256),如果你非要選擇其它早期草案則需要用Java-WebSocket來實現,在Java-WebSocket中連接協議“Draft_17”就是標準的RFC 6455(JSR256),另外要使用Java API javax.websocket包中的WebSocket相關類要求JDK7及以上,Tomcat 7.0.49及以上):
代碼如下
@ServerEndpoint(value = "/websocket/{user}")
public class ChatServerEndpoint {
private static Set<Session> sessions = new HashSet<Session>();
private Session session;
@OnOpen
public void open(Session session, @PathParam(value = "user") String user) {
this.session = session;
sessions.add(this.session);
sendToAll(session.getRequestURI() + "進入房間");
System.out.println(session.getRequestURI() + " 進入房間");
}
@OnClose
public void close() {
sessions.remove(session);
sendToAll(session.getRequestURI() + " 離開房間");
System.out.println(session.getRequestURI() + " 離開房間");
}
@OnMessage
public void message(String message) {
sendToAll("[" + session.getRequestURI() + "]" + message);
System.out.println("[" + session.getRequestURI() + "]" + message);
}
private void sendToAll(String text) {
for (Session client : sessions) {
synchronized (client) {
client.getAsyncRemote().sendText(text);
}
}
}
}
這里服務器端的代碼主要是消息轉發功能,進入,離開,以及用戶發送信息都會轉發給連接的客戶端。
啟動tomcat就可以用Android客戶端來連接進行聊天、接收推送了。
Android端創建
首先添加Java-WebSocket的依賴如下
compile 'org.java-websocket:Java-WebSocket:1.3.0'
接下來就可以創建一個WebSockeClient
WebSocketClient client = new WebSocketClient(new URI(""), new Draft_17()) {
@Override
public void onOpen(ServerHandshake handshakedata) {
Log.e("hxy", "已經連接到服務器【" + getURI() + "】");
}
@Override
public void onMessage(String message) {
Log.e("hxy", "獲取到服務器信息【" + message + "】");
}
@Override
public void onClose(int code, String reason, boolean remote) {
Log.e("hxy", "斷開服務器連接【" + getURI() + ",狀態碼: " + code + ",斷開原因:" + reason + "】");
}
@Override
public void onError(Exception ex) {
Log.e("hxy", "連接發生了異常【異常原因:" + ex + "】");
}
};
client.connect();
這里我們只有設置前面搭建的服務器端的地址即可,然后就可以監聽到連接,斷開,異常等信息,同時也可以監聽到服務器發出的信息。
同樣我們也可以向服務器發送信息,代碼如下
client.send("message from client");
通過這樣就可以實現服務器與客戶端的雙向通信了
運行如下:
可以看到這里客戶端A連接服務器端,并發生消息,服務器端轉發連接信息與來自客戶端A的信息,客戶端A接收后將信息顯示出來,然后用另外的客戶端B進行連接發送信息,客戶端A同樣可以接收到后將信息顯示出來。
所以這里就實現了WebSocket相關的雙向通信了,
至于怎么彈幕,有了數據,彈幕只是表現形式咯,這個實現比較簡單就不做說明了,其實可以使用同樣Bilibili開發并開源的DanmakuFlameMaster。
寫在后面的話
那么到這里就完成了移動直播相關的基本的問題了,我這邊更加著重于對于原理的講解,而不是把代碼貼出來簡單的概述而過,跟著這些原理一步一步研究出來顯然會對自己提升更大,至于視頻直播的濾鏡問題,可以參考我寫的濾鏡系列文章,當然我這里僅僅是實現了直播的整套流程,但是對于直播其實還有很多的問題,這里就不做過多的研究了,有興趣的朋友可以進行進一步的研究,peace~~~