背景
當下視頻直播如此紅火,打造一個在線直播間涉及到哪些技術呢?
視頻直播由主播的直播端以及觀眾的觀看端組成。一個簡單的觀看端最起碼應包含播放器以及聊天室。下面就圍繞這兩大模塊來講述相關技術。
視頻直播,可以分為視頻采集、前處理、編碼和封裝、傳輸、解封裝和解碼、播放這幾個環節。
直播端通過硬件設備采集音視頻數據,經過前處理以及編碼、封裝后,還要傳輸到觀看端。這一步一般交由CDN接力完成。推流端會把視頻流推到源站,CDN從源站拉流,拉流成功后編碼封裝成不同的格式提供各觀看端播放。簡單示意圖如下:
(網絡引用圖片)
接下來講一下觀看端的基本要求:
多終端觀看
觀看穩定、不卡頓
延遲低、高并發
多終端觀看
首先明確需要支持的終端,有移動端APP(iOS、Android)、移動端瀏覽器、小程序、PC瀏覽器、PC 客戶端。對于PC瀏覽器,一般支持到IE8。并且,由于Chrome瀏覽器默認屏蔽Flash,所以在PC瀏覽器中采用的策略為:優先在兼容H5的瀏覽器使用H5播放,否則降級使用Flash播放。兩者支持的直播播放視頻格式差異如下:
Flash支持rtmp或 http-flv 直播播放。延時~3秒。
H5支持M3u8直播播放。延時~15秒。
觀看穩定、不卡頓
造成視頻直播卡頓的原因可能是多方面的,涉及到直播端、網絡、觀看端。
直播端
主要問題有硬件配置太低、推流參數配置問題、音視頻時間戳不同步。可以通過以下措施解決:
升級硬件、軟件設置,提高兼容性和容錯率 ( 這部分是硬裝,必須有好的裝備才能有好的推流質量啊 )。
使用硬編硬解方案,充分利用GPU加速。
降低視頻碼率,選擇流暢、標清畫質或者使用動態碼率推流。
網絡
主要問題有網絡抖動、拉流服務器與觀看端鏈路過長,可以通過以下措施解決:
選用穩定的運營商網絡并合理布局CDN節點。
使用充足的網絡帶寬。
觀看端
在網絡上觀看視頻,緩沖區就是在你看視頻時提前儲存部分視頻數據,當數據到達一定的量后再播放畫面,使得播放更流暢。若設置得過小,在網絡不穩定時就無法流暢地連續播放;若設置過大則會累積時延。所以要設置一個適中的緩沖區。
// Flash
netStream.bufferTime = 1 // 單位:秒
// FLV.js
flvjs.createPlayer({
type: 'flv',
isLive: true,
url: 'video.flv'
},{
stashInitialSize: 120 // 默認:384KB
})
延時低、高并發
我們知道,視頻其實是由一幀一幀的圖像構成的,RTMP基于TCP不會丟包,所以當網絡狀態差時,服務器會將包緩存起來。等網絡狀況好了,就一起發給觀看端,導致觀看端累積太多視頻幀數據,延時隨時間增長而增加。對于這個問題,除了上文提及的設置適當的緩沖區長度外,還可以增加追幀和丟幀操作實現播放追趕。
Flash代碼:
// Flash實現追幀:定時器輪詢檢測當前緩沖區長度大于30秒時重連,重新拉取直播流
netStream.bufferTimeMax = 0.1 // 設置bufferTimeMax主動追幀
if(netStream.bufferLength > 30) {
// 緩沖區長度大于30,重新連接
reconnectStream()
}
H5代碼:
// H5實現追幀,判斷當前緩沖區結束時間與當前時間相差超過5秒,則追幀
if (video.buffered.length > 0 && video.buffered.end(0) - video.currentTime > 5) {
// 直播流時間接近緩沖時間的話畫面容易卡死,所以保險起見-1秒
video.currentTime = video.buffered.end(0) - 1;
}
另外,如果用到FLV.js播放視頻,可以開啟它的Worker特性,多進程解析優化延遲 ,并減少buffer。
// FLV.js
flvjs.createPlayer({
type: 'flv',
isLive: true,
url: 'video.flv'
},{
enableWorker: true,
enableStashBuffer: false,
stashInitialSize: 120 // 默認:384KB
})
聊天室
即時聊天IM服務既要保證實時性,可靠性,又要抗住高并發。在實現過程中我們使用以下方法解決。
1、傳輸模式優先選擇 WebSocket,若不支持則降級為輪詢。
const io = require('socket.io')({ "transports":['websocket', 'polling']})
2、Node.js 服務器因消息并發大導致性能低下。
通過以下方案極大的優化了聊天室的穩定性和可靠性。
(1)使用命名空間的功能
命名空間的作用就是把消息限定在一定范圍內傳播。對于一些不需要全局接收的消息就加上命名空間,可以極大的節約資源的傳輸。
// 創建命名空間
const io = require('socket.io')()
const adminNamespace = io.of('/admin')
adminNamespace.to('level1').emit('an event', { some: 'data' })
(2)聊天消息隊列
觀眾進入聊天室房間會廣播登錄消息,比如房間內同時有2W人,每個人登錄要對房間內所有人廣播"我"登錄了,相當于發送了2W條消息。若并發量大,對服務器性能要求極高。使用聊天隊列消息分批顯示可以防止同時處理大量消息,提高處理性能。
// 消息隊列
let scoektMsgArr = [{
EVENT: 'SPEAK',
uid: socketId,
content: '這是第一條聊天消息'
},...]
let minCount = 0
setInterval(()=>{
const maxCount = minCount + 100
const newScoektMsgArr = scoektMsgArr.slice(minCount, maxCount)
newScoektMsgArr.forEach((item) => {
socket.emit('message', JSON.stringify(item))
})
}, 1000)
(3)服務器彈性伸縮
祭出最后的大招,能優化的已經極力優化了,那剩下的事情就是配置服務器彈(jia)性(fu)伸(wu)縮(qi)。
3、掉線重連機制。
掉線會觸發disconcent 事件,監聽它再創建socket連接即可。心跳檢查即定時發送一次消息,保持連接狀態。
// 保持心跳
setInterval(()=>{
socket.emit('message', JSON.stringify({
EVENT: 'HEARTBEAT',
uid: socketId
}))
}, 30 * 60 * 1000)
// 斷開重連
socket.on('disconnect', () => {
this.connect()
})
connect() {
socket.on('connect', () => {
//TODO
})
}
POLYV SDK 快速搭建在線直播間
按前文所述,搭建直播間有非常多的細節需要考慮,包括采集推流、CDN分發、播放體驗優化、聊天室性能調優等。別擔心,調用POLYV SDK可以快速搭建直播間。
(使用POLYV視頻直播服務需要先免費注冊賬號)
STEP1 嵌入播放器:
<script src="https://player.polyv.net/livescript/liveplayer.js"></script>
<div id='player'></div>
<script>
var player = polyvObject('#player').livePlayer({
'width':'498',
'height':'409',
'uid':'e3wx706i3v',
'vid':'268682' // polyv直播頻道號
});
</script>
STEP2 嵌入聊天室
// 默認樣式
<link rel="stylesheet" href="https://player.polyv.net/jssdk/polyv-chatroom.min.css">
<script src="https://player.polyv.net/jssdk/polyv-chatroom.min.js"></script>
<div id="wrap"></div>
var chatroom = new PolyvChatRoom({
roomId: 268682,
userId: 153075602311,
nick: '游客',
pic: 'http://livestatic.videocc.net/assets/wimages/missing_face.png',
token: token,
container: '#wrap',
width: 300,
height: 600,
userType: '',
roomMessage: function(data) {
// TODO
// data為聊天室socket消息,當有聊天室消息時會觸發此方法
console.log(data);
}
});
如上圖所示,嵌入直播播放SDK + 聊天室SDK即可創建直播間。聊天室SDK中有自帶默認皮膚、發送表情、點贊、送花、在線列表、提問等功能。另外針對直播教育場景需用到的其他功能,如答題卡、簽到等,可監聽聊天室socket消息自定義實現:
1.答題卡
// 監聽答題內容
var chatroom = new PolyvChatRoom({
roomId: 268682,
userId: 153075602313,
nick: '學員_1',
pic: 'http://livestatic.videocc.net/assets/wimages/missing_face.png',
token: token,
container: '#wrap',
width: 300,
height: 600,
userType: '',
roomMessage: function(data) {
// 監聽聊天室消息
switch(data.EVENT){
case 'GET_TEST_QUESTION_RESULT':
// 獲取答題卡內容,顯示答題卡
break;
case 'GET_TEST_QUESTION_RESULT':
// 獲取答題卡結果, 顯示答題結果
break;
}
}
});
// 發送答題結果
chatroom.socket.emit('message', JSON.stringify({
EVENT: 'ANSWER_TEST_QUESTION',
roomId: channelId,
nick: nick,
userId: userId,
option: result,
questionId: questionId
}));
2.簽到
// 直播端發起簽到
roomMessage: function(data) {
// 監聽聊天室消息
switch(data.EVENT){
case 'SIGN_IN':
// 發起簽到
break;
case 'STOP_SIGN_IN':
// 停止簽到
break;
}
}
// 觀眾端簽到
chatroom.socket.emit('message', JSON.stringify({
EVENT: 'TO_SIGN_IN',
roomId: roomId,
checkinId: checkinId,
user: {
userId: 123456,
nick: 'polyv'
}
}));
小程序
除了在瀏覽器端,我們在小程序端也提供了對應的SDK
下載小程序SDK后,調用組件快速生成包含播放器、聊天室等功能的直播間。
// 嵌入polyv頁面組件
<view>
<polyv />
</view>
// 初始化數據
import plv from '*/polyv-sdk/index';
// onLoad
onLoad() {
const options = {
channelId: '', // 頻道ID
openId: '', // 用戶openId
userName: '', // 用戶名
avatarUrl: '' // 用戶頭像
};
plv.init(options);
}
// onUnload
onUnload() {
plv.destory();
}
總結:
如果以完全自研的方式實現教育直播的基礎功能,至少需要10人的技術團隊,5人的產品運營團隊,才能在3個月內完成產品的上線。算下來至少需要幾十萬甚至上百萬。POLYV為各企業提供了全面的解決方案以及相關文檔,實現快速接入,輕松開啟線上教育直播。