In questu articulu vogliu sparte i mo tentativi di streaming video via websockets senza utilizà plugins di navigatore di terzu partitu cum'è Adobe Flash Player. Leghjite per sapè ciò chì hè vinutu.
Adobe Flash, prima Macromedia Flash, hè una piattaforma per creà applicazioni chì funzionanu in un navigatore web. Prima di l'intruduzioni di l'API Media Stream, era praticamente l'unica piattaforma per streaming video è voce da una webcam, è ancu per creà diversi tipi di cunferenze è chats in u navigatore. U protokollu per a trasmissione di l'infurmazioni media RTMP (Protocolu di Messaging in Tempu Reale) hè statu in realtà chjusu per un bellu pezzu, chì significava: se vulete rinfurzà u vostru serviziu di streaming, sia abbastanza gentile per aduprà software da Adobe stessi - Adobe Media Server (AMS).
Dopu qualchì tempu in u 2012, Adobe "abbandunò è sputò" à u publicu.
A piattaforma Adobe Flash hè più di 20 anni d'età, durante quale tempu sò scuperti assai vulnerabili critichi, supportu
Per u mo prughjettu, aghju subitu decisu di abbandunà completamente l'usu di Flash in u navigatore. Aghju indicatu u mutivu principale sopra; Flash hè ancu micca supportatu in tuttu in e plataforme mobili, è ùn vulia micca implementà Adobe Flash per u sviluppu in Windows (emulatore di vinu). Allora aghju stabilitu per scrive un cliente in JavaScript. Questu serà solu un prototipu, postu chì dopu aghju amparatu chì u streaming pò esse fattu assai più efficacemente basatu nantu à p2p, solu per mè serà peer - server - peers, ma più nantu à questu un'altra volta, perchè ùn hè micca pronta.
Per cumincià, avemu bisognu di u servitore websockets attuale. Aghju fattu u più simplice basatu annantu à u pacchettu di melodia:
U codice di u servitore
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)
}
Nantu à u cliente (latu streaming), avete prima bisognu di accede à a camera. Questu hè fattu attraversu
Avemu accessu (permissione) à a camera / microfonu attraversu
Dopu, getUserMedia() torna una Promessa, à quale passa un oggettu MediaStream - un flussu di dati video-audio. Assignemu stu oggettu à a pruprietà src di l'elementu video. Codice:
Latu di trasmissione
<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>
Per trasmette un flussu di video nantu à sockets, avete bisognu di codificà in un locu, buffer, è trasmette in parte. U flussu di video crudu ùn pò micca esse trasmessu via websockets. Hè quì chì vene à u nostru aiutu
Codificamu u flussu di video, rompemu in parti
<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>
Avà aghjunghje a trasmissione via websockets. Sorprendentemente, tuttu ciò chì avete bisognu per questu hè un ughjettu
Trasmettimu u flussu di video à u servitore
<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>
U latu di trasmissione hè pronta! Avà pruvemu à riceve un flussu di video è vede nantu à u cliente. Chì avemu bisognu per questu? Prima, sicuru, a cunnessione socket. Attachemu un "ascoltatore" à l'ughjettu WebSocket è abbonate à l'avvenimentu "messagiu". Dopu avè ricivutu un pezzu di dati binari, u nostru servitore trasmette à l'abbonati, vale à dì i clienti. In questu casu, a funzione di callback assuciata à l'"ascoltatore" di l'avvenimentu di "messagiu" hè attivata nantu à u cliente; l'ughjettu stessu hè passatu in l'argumentu di a funzione - un pezzu di u flussu video codificatu da vp8.
Accettamu u flussu di video
<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>
Per un bellu pezzu aghju pruvatu à capisce perchè hè impussibile di mandà immediatamente i pezzi ricivuti à l'elementu video per a riproduzione, ma hè risultatu chì questu ùn pò micca esse fattu, sicuru, prima deve mette u pezzu in un buffer speciale ligatu à l'elementu video, è solu allora accuminciarà à ghjucà u flussu di video. Per questu avete bisognu
MediaSource agisce cum'è un tipu d'intermediariu trà l'ughjettu di riproduzione media è a fonte di stu flussu media. L'ughjettu MediaSource cuntene un buffer pluggable per a fonte di u flussu video / audio. Una funziunalità hè chì u buffer pò cuntene solu dati Uint8, cusì avete bisognu di un FileReader per creà un tali buffer. Fighjate u codice è diventerà più chjaru:
Riproduce u flussu di video
<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>
U prototipu di u serviziu di streaming hè prontu. U principale svantaghju hè chì a riproduzione di video ritardarà da u latu di trasmissione di 100 ms; l'avemu stabilitu noi stessi quandu si divide u flussu di video prima di trasmette à u servitore. Inoltre, quandu aghju verificatu in u mo laptop, u lag trà i lati di trasmissione è di ricezione s'hè accumulatu gradualmente, questu era chjaramente visibile. Aghju cuminciatu à circà modi per superà stu svantaghju, è... ghjuntu
Source: www.habr.com