心血?dú)v程
這部分主要是寫在寫這些的時(shí)候遇到的問題以及換思路的過程,可以之間看正文
在之前我也寫過直播功能,并且與websocket相結(jié)合實(shí)現(xiàn)了直播彈幕。只不過直播是使用的騰訊云的,而不是手寫的直播推流拉流,這次又有一個(gè)新的項(xiàng)目,和直播息息相關(guān)(直播自習(xí)平臺(tái)),所以就想著使用原生的寫直播推流、拉流,搞了兩天,實(shí)現(xiàn)了一個(gè)基礎(chǔ)版本的直播。
這兩天搞直播也挺難受的,原本我和后端想的和之前寫項(xiàng)目一樣,一個(gè)拉流地址,一個(gè)推流地址,不過后端寫好之后也用軟件測(cè)試了一下可行,興高采烈的給我說寫好了,既然后端接口寫好了,剩下的就是前端去交互了,經(jīng)過一段時(shí)間的交互,拉流報(bào)錯(cuò),推流也報(bào)錯(cuò),經(jīng)過一番百度原來是瀏覽器不支持rtmp協(xié)議,只能另尋它法,最后想到使用websocket去連接,我把采集到的音視頻流數(shù)據(jù)傳輸給后端,后端在傳輸給我,我再統(tǒng)一處理,寫好之后發(fā)現(xiàn)了一個(gè)小問題就是每次推流與拉流之間都會(huì)閃一下,原本想著使用mediaSource視頻流去搞一下,不過最后發(fā)現(xiàn)視頻格式對(duì)于不同瀏覽器的要求是不一樣的,而且瀏覽器對(duì)于視頻格式要求也比較嚴(yán),最后就不搞了。經(jīng)過與后端的商量,最終決定項(xiàng)目中寫兩版直播,一版是原生寫的,一版是使用騰訊云直播寫的。下面就進(jìn)入正文吧。
直播常見協(xié)議
- RTMP
RTMP(Real-Time Messaging Protocol)是一種用于音頻、視頻和數(shù)據(jù)傳輸?shù)膮f(xié)議。它最初由Adobe開發(fā),用于在Flash播放器和媒體服務(wù)器之間進(jìn)行實(shí)時(shí)通信。RTMP通過建立持久的連接來傳輸流媒體數(shù)據(jù),支持實(shí)時(shí)的流媒體傳輸和即時(shí)通信。 - HTTP-FLV
HTTP-FLV(HTTP-Flash Video)是一種基于HTTP協(xié)議的流媒體傳輸協(xié)議。它是由阿里巴巴開發(fā)的,用于在Web瀏覽器中播放Flash視頻。HTTP-FLV通過HTTP協(xié)議傳輸FLV(Flash Video)格式的視頻,可以實(shí)現(xiàn)低延遲的視頻直播和點(diǎn)播。 - HLS
HLS(HTTP Live Streaming)是一種由蘋果公司開發(fā)的流媒體傳輸協(xié)議。它通過將視頻和音頻切片成小的TS(Transport Stream)文件,并使用HTTP協(xié)議傳輸這些文件,實(shí)現(xiàn)了在不同網(wǎng)絡(luò)環(huán)境下的自適應(yīng)流媒體傳輸。HLS支持多種編碼格式和分辨率的視頻,可以在iOS設(shè)備和Web瀏覽器上播放。
總的來說RTMP是一種實(shí)時(shí)的流媒體傳輸協(xié)議,適用于實(shí)時(shí)通信和互動(dòng)性較高的應(yīng)用;HTTP-FLV是一種基于HTTP協(xié)議的低延遲流媒體傳輸協(xié)議,適用于Web瀏覽器中的Flash視頻播放;HLS是一種適用于不同網(wǎng)絡(luò)環(huán)境下的自適應(yīng)流媒體傳輸協(xié)議,適用于iOS設(shè)備和Web瀏覽器的播放。
直播基本流程
看上圖可知直播分為以下三個(gè)步驟:
- 視頻/音頻采集(推流)
- 流媒體服務(wù)器(用于推流/拉流的中轉(zhuǎn)服務(wù)器)
- 播放(拉流)
對(duì)于前端來說推流以及拉流都是我們所要操心的事,而后端要做的是中轉(zhuǎn)服務(wù)器作用
直播代碼
經(jīng)過以上的說明,大家對(duì)直播有了一個(gè)簡單的了解,那么我們之間上代碼
node端(需要下載ws第三方庫)
const WebSocket = require("ws");
const wss = new WebSocket.Server({
port: 3000,
});
wss.on("connection", (ws) => {
ws.on("message", (msg) => {
wss.clients.forEach((ws) => {
ws.send(msg);
});
});
});
node端我們做的很簡潔,就是把前端發(fā)給我們的數(shù)據(jù)再發(fā)給前端
那么就顯得數(shù)據(jù)很重要了,既然是直播那么我們的數(shù)據(jù)就是我們直播的數(shù)據(jù)(及視頻,音頻),另外需要補(bǔ)充兩點(diǎn)就是我們傳輸?shù)臄?shù)據(jù)一般比較大,websocket的傳輸雖然理論上是無限,但是為了不明顯的卡頓,我們前端傳輸數(shù)據(jù)采用分段上傳。另外就是為了保證數(shù)據(jù)的準(zhǔn)確,我們傳輸?shù)氖嵌M(jìn)制數(shù)據(jù),這樣就能保證了觀看與直播的相對(duì)無誤差。
在寫前端代碼之前,我來解釋一下為什么這個(gè)直播是弱雞版,我們實(shí)現(xiàn)的是推流是前端錄視頻一段時(shí)間之后發(fā)給后端,后端再轉(zhuǎn)接給前端,循環(huán)往復(fù),由此就實(shí)現(xiàn)了直播功能,這也是推流拉流時(shí)屏幕閃的原因。不過更多的是讓大家了解一下大致的流程,及一個(gè)可行的方法。
前端推流代碼
<!--用以預(yù)覽-->
<video></video>
<button onclick="onStartRecord()">開始共享</button>
<button onclick="onDownload()">手動(dòng)推流</button>
<button onclick="close()">關(guān)閉</button>
<script>
var socket = new WebSocket("ws://127.0.0.1:3000");
socket.onopen = () => {
console.log("WebSocket 連接成功");
};
socket.onclose = (event) => {
console.log("WebSocket 連接關(guān)閉", event);
};
socket.onerror = (error) => {
console.error("發(fā)生錯(cuò)誤", error);
};
socket.onmessage = (event) => {
console.log("收到信息", event.data);
};
// 想要獲取一個(gè)最接近 1280x720 的相機(jī)分辨率
var recordedChunks = [];
var stream
var onStartRecord = function () {
navigator.mediaDevices
.getDisplayMedia({
video: {
mediaSource: "screen",
},
audio: true
})
.then(async function (mediaStream) {
const audioTrack = await navigator.mediaDevices.getUserMedia({ audio: true });
// 添加聲音軌道
await mediaStream.addTrack(audioTrack.getAudioTracks()[0]);
var video = document.querySelector('video');
//本地預(yù)覽視頻
video.srcObject = mediaStream;
stream = mediaStream
video.onloadedmetadata = function (e) {
video.play();
};
//采集視頻
startRecord(mediaStream);
})
.catch(function (err) {
console.log(err.name + ': ' + err.message);
}); // 總是在最后檢查錯(cuò)誤
};
var timer = null
function startRecord(stream) {
recordedChunks = []
var options = { mimeType: 'video/webm; codecs=vp9' };
mediaRecorder = new MediaRecorder(stream, options);
mediaRecorder.ondataavailable = handleDataAvailable;
mediaRecorder.start();
clearInterval(timer)
//每隔5秒自動(dòng)推流
timer = setInterval(async () => {
await onDownload()
await startRecord(stream)
}, 5000)
}
//onDownloadClick,調(diào)用stop會(huì)停止并會(huì)觸發(fā)ondataavailable,相應(yīng)下載邏輯在回調(diào)中完成
var onDownload = function () {
mediaRecorder.stop();
};
function handleDataAvailable(event) {
if (event.data.size > 0) {
recordedChunks.push(event.data);
download();
} else {
// ...
}
}
const CHUNK_SIZE = 1024 * 5; // 每個(gè)數(shù)據(jù)塊的大小為 16KB
//分片上傳
function sliceBlob(blob) {
const chunks = [];
let offset = 0;
while (offset < blob.size) {
const chunk = blob.slice(offset, offset + CHUNK_SIZE);
chunks.push(chunk);
offset += CHUNK_SIZE;
}
return chunks;
}
async function download() {
var blob = new Blob(recordedChunks, {
type: 'video/webm',
});
console.log(blob);
const binaryChunks = sliceBlob(blob);
for (const chunk of binaryChunks) {
console.log(chunk);
await socket.send(chunk);
}
}
//關(guān)閉直播
function close() {
stream.getTracks().map((track) => track.stop());
}
</script>
前端拉流代碼
<video src="" id="mv" controls></video>
<script>
const mv = document.getElementById('mv')
var socket = new WebSocket("ws://127.0.0.1:3000");
socket.onopen = () => {
console.log("WebSocket 連接成功");
};
socket.onclose = (event) => {
console.log("WebSocket 連接關(guān)閉", event);
};
socket.onerror = (error) => {
console.error("WebSocket Error:", error);
};
let videoData = []
socket.onmessage = async (event) => {
if (event.data.size < 1024 * 5) {
//合并數(shù)據(jù)進(jìn)行渲染
const blob = new Blob(videoData, { type: 'video/webm' })
mv.src = URL.createObjectURL(blob);
videoData = []
mv.play()
} else {
// 接收數(shù)據(jù)
videoData.push(event.data)
}
}
</script>
注意事項(xiàng):
我們使用blob時(shí)要指定格式,以此方便我們播放的時(shí)候?yàn)g覽器解碼文章來源:http://www.zghlxwxcb.cn/news/detail-630907.html
總結(jié)
之后遇到項(xiàng)目的問題再繼續(xù)分享吧。之后也正式開始寫項(xiàng)目了。我們一同進(jìn)步。文章來源地址http://www.zghlxwxcb.cn/news/detail-630907.html
到了這里,關(guān)于websocket+node實(shí)現(xiàn)直播(弱雞版)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!