I den här artikeln vill jag dela mina försök att strömma video via websockets utan att använda tredjeparts webbläsarplugin som Adobe Flash Player. Läs vidare för att ta reda på vad det blev.
Adobe Flash, tidigare Macromedia Flash, är en plattform för att skapa applikationer som körs i en webbläsare. Innan introduktionen av Media Stream API var det praktiskt taget den enda plattformen för att streama video och röst från en webbkamera, samt för att skapa olika typer av konferenser och chattar i webbläsaren. Protokollet för att överföra medieinformation RTMP (Real Time Messaging Protocol) var faktiskt stängt under en lång tid, vilket innebar: om du vill boosta din streamingtjänst, var vänlig nog att använda programvara från Adobe själva - Adobe Media Server (AMS).
Efter en tid 2012 "gav Adobe upp och spottade ut det" till allmänheten.
Adobe Flash-plattformen är mer än 20 år gammal, under vilken tid många kritiska sårbarheter har upptäckts, support
För mitt projekt bestämde jag mig omedelbart för att helt överge användningen av Flash i webbläsaren. Jag angav huvudorsaken ovan; Flash stöds inte alls på mobila plattformar, och jag ville verkligen inte distribuera Adobe Flash för utveckling på Windows (vinemulator). Så jag gav mig i kast med att skriva en klient i JavaScript. Det här kommer bara att vara en prototyp, eftersom jag senare lärde mig att streaming kan göras mycket mer effektivt baserat på p2p, bara för mig kommer det att vara peer - server - peers, men mer om det en annan gång, eftersom det inte är klart än.
För att komma igång behöver vi själva websockets-servern. Jag gjorde den enklaste baserad på melodi go-paketet:
Serverkod
package main
import (
"errors"
"github.com/go-chi/chi"
"gopkg.in/olahol/melody.v1"
"log"
"net/http"
"time"
)
func main() {
r := chi.NewRouter()
m := melody.New()
m.Config.MaxMessageSize = 204800
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "public/index.html")
})
r.Get("/ws", func(w http.ResponseWriter, r *http.Request) {
m.HandleRequest(w, r)
})
// Бродкастим видео поток
m.HandleMessageBinary(func(s *melody.Session, msg []byte) {
m.BroadcastBinary(msg)
})
log.Println("Starting server...")
http.ListenAndServe(":3000", r)
}
På klienten (strömningssidan) måste du först komma åt kameran. Detta görs genom
Vi får tillgång (tillstånd) till kameran/mikrofonen genom
Därefter returnerar getUserMedia() ett löfte, till vilket det skickar ett MediaStream-objekt - en ström av video-ljuddata. Vi tilldelar detta objekt till egenskapen src för videoelementet. Koda:
Sändningssidan
<style>
#videoObjectHtml5ApiServer { width: 320px; height: 240px; background: #666; }
</style>
</head>
<body>
<!-- Здесь в этом "окошечке" клиент будет видеть себя -->
<video autoplay id="videoObjectHtml5ApiServer"></video>
<script type="application/javascript">
var
video = document.getElementById('videoObjectHtml5ApiServer');
// если доступен MediaDevices API, пытаемся получить доступ к камере (можно еще и к микрофону)
// getUserMedia вернет обещание, на которое подписываемся и полученный видеопоток в колбеке направляем в video объект на странице
if (navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.getUserMedia({video: true}).then(function (stream) {
// видео поток привязываем к video тегу, чтобы клиент мог видеть себя и контролировать
video.srcObject = stream;
});
}
</script>
För att sända en videoström över uttag måste du koda den någonstans, buffra den och överföra den i delar. Råvideoströmmen kan inte överföras via websockets. Det är här det kommer till vår hjälp
Vi kodar videoströmmen, delar upp den i delar
<style>
#videoObjectHtml5ApiServer { width: 320px; height: 240px; background: #666; }
</style>
</head>
<body>
<!-- Здесь в этом "окошечке" клиент будет видеть себя -->
<video autoplay id="videoObjectHtml5ApiServer"></video>
<script type="application/javascript">
var
video = document.getElementById('videoObjectHtml5ApiServer');
// если доступен MediaDevices API, пытаемся получить доступ к камере (можно еще и к микрофону)
// getUserMedia вернет обещание, на которое подписываемся и полученный видеопоток в колбеке направляем в video объект на странице
if (navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.getUserMedia({video: true}).then(function (stream) {
// видео поток привязываем к video тегу, чтобы клиент мог видеть себя и контролировать
video.srcObject = s;
var
recorderOptions = {
mimeType: 'video/webm; codecs=vp8' // будем кодировать видеопоток в формат webm кодеком vp8
},
mediaRecorder = new MediaRecorder(s, recorderOptions ); // объект MediaRecorder
mediaRecorder.ondataavailable = function(e) {
if (e.data && e.data.size > 0) {
// получаем кусочек видеопотока в e.data
}
}
mediaRecorder.start(100); // делит поток на кусочки по 100 мс каждый
});
}
</script>
Låt oss nu lägga till överföring via websockets. Överraskande nog är allt du behöver för detta ett föremål
Vi överför videoströmmen till servern
<style>
#videoObjectHtml5ApiServer { width: 320px; height: 240px; background: #666; }
</style>
</head>
<body>
<!-- Здесь в этом "окошечке" клиент будет видеть себя -->
<video autoplay id="videoObjectHtml5ApiServer"></video>
<script type="application/javascript">
var
video = document.getElementById('videoObjectHtml5ApiServer');
// если доступен MediaDevices API, пытаемся получить доступ к камере (можно еще и к микрофону)
// getUserMedia вернет обещание, на которое подписываемся и полученный видеопоток в колбеке направляем в video объект на странице
if (navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.getUserMedia({video: true}).then(function (stream) {
// видео поток привязываем к video тегу, чтобы клиент мог видеть себя и контролировать
video.srcObject = s;
var
recorderOptions = {
mimeType: 'video/webm; codecs=vp8' // будем кодировать видеопоток в формат webm кодеком vp8
},
mediaRecorder = new MediaRecorder(s, recorderOptions ), // объект MediaRecorder
socket = new WebSocket('ws://127.0.0.1:3000/ws');
mediaRecorder.ondataavailable = function(e) {
if (e.data && e.data.size > 0) {
// получаем кусочек видеопотока в e.data
socket.send(e.data);
}
}
mediaRecorder.start(100); // делит поток на кусочки по 100 мс каждый
}).catch(function (err) { console.log(err); });
}
</script>
Sändningssidan är klar! Låt oss nu försöka ta emot en videoström och visa den på klienten. Vad behöver vi för detta? För det första, naturligtvis, uttagsanslutningen. Vi kopplar en "lyssnare" till WebSocket-objektet och prenumererar på "meddelande"-händelsen. Efter att ha fått en bit binär data sänder vår server den till abonnenter, det vill säga klienter. I det här fallet utlöses återuppringningsfunktionen associerad med "avlyssnaren" av "meddelande"-händelsen på klienten; själva objektet skickas till funktionsargumentet - en del av videoströmmen kodad av vp8.
Vi accepterar videoström
<style>
#videoObjectHtml5ApiServer { width: 320px; height: 240px; background: #666; }
</style>
</head>
<body>
<!-- Здесь в этом "окошечке" клиент будет видеть тебя -->
<video autoplay id="videoObjectHtml5ApiServer"></video>
<script type="application/javascript">
var
video = document.getElementById('videoObjectHtml5ApiServer'),
socket = new WebSocket('ws://127.0.0.1:3000/ws'),
arrayOfBlobs = [];
socket.addEventListener('message', function (event) {
// "кладем" полученный кусочек в массив
arrayOfBlobs.push(event.data);
// здесь будем читать кусочки
readChunk();
});
</script>
Under en lång tid försökte jag förstå varför det är omöjligt att omedelbart skicka de mottagna bitarna till videoelementet för uppspelning, men det visade sig att detta inte kan göras, naturligtvis måste du först lägga biten i en speciell buffert bunden till videoelementet, och först då börjar det spela upp videoströmmen. För detta behöver du
MediaSource fungerar som en slags mellanhand mellan mediauppspelningsobjektet och källan till denna mediaström. MediaSource-objektet innehåller en pluggbar buffert för källan till video-/ljudströmmen. En funktion är att bufferten bara kan innehålla Uint8-data, så du behöver en FileReader för att skapa en sådan buffert. Titta på koden så blir den tydligare:
Spelar upp videoströmmen
<style>
#videoObjectHtml5ApiServer { width: 320px; height: 240px; background: #666; }
</style>
</head>
<body>
<!-- Здесь в этом "окошечке" клиент будет видеть тебя -->
<video autoplay id="videoObjectHtml5ApiServer"></video>
<script type="application/javascript">
var
video = document.getElementById('videoObjectHtml5ApiServer'),
socket = new WebSocket('ws://127.0.0.1:3000/ws'),
mediaSource = new MediaSource(), // объект MediaSource
vid2url = URL.createObjectURL(mediaSource), // создаем объект URL для связывания видеопотока с проигрывателем
arrayOfBlobs = [],
sourceBuffer = null; // буфер, пока нуль-объект
socket.addEventListener('message', function (event) {
// "кладем" полученный кусочек в массив
arrayOfBlobs.push(event.data);
// здесь будем читать кусочки
readChunk();
});
// как только MediaSource будет оповещен , что источник готов отдавать кусочки
// видео/аудио потока
// создаем буфер , следует обратить внимание, что буфер должен знать в каком формате
// каким кодеком был закодирован поток, чтобы тем же способом прочитать видеопоток
mediaSource.addEventListener('sourceopen', function() {
var mediaSource = this;
sourceBuffer = mediaSource.addSourceBuffer("video/webm; codecs="vp8"");
});
function readChunk() {
var reader = new FileReader();
reader.onload = function(e) {
// как только FileReader будет готов, и загрузит себе кусочек видеопотока
// мы "прицепляем" перекодированный в Uint8Array (был Blob) кусочек в буфер, связанный
// с проигрывателем, и проигрыватель начинает воспроизводить полученный кусочек видео/аудио
sourceBuffer.appendBuffer(new Uint8Array(e.target.result));
reader.onload = null;
}
reader.readAsArrayBuffer(arrayOfBlobs.shift());
}
</script>
Prototypen av streamingtjänsten är klar. Den största nackdelen är att videouppspelningen släpar efter sändningssidan med 100 ms; vi ställer in detta själva när vi delar upp videoströmmen innan den överförs till servern. Dessutom, när jag kollade på min bärbara dator, ackumulerades fördröjningen mellan sändnings- och mottagningssidan gradvis, detta var tydligt synligt. Jag började leta efter sätt att övervinna denna nackdel, och... kom över
Källa: will.com