Selles artiklis tahan jagada oma katseid voogesitada videot veebipistikupesade kaudu ilma kolmanda osapoole brauseri pistikprogramme, nagu Adobe Flash Player, kasutamata. Loe edasi, et teada saada, mis sellest välja tuli.
Adobe Flash, varem Macromedia Flash, on platvorm veebibrauseris töötavate rakenduste loomiseks. Enne Media Stream API kasutuselevõttu oli see praktiliselt ainus platvorm video ja hääle voogesitamiseks veebikaamerast, samuti mitmesuguste konverentside ja vestluste loomiseks brauseris. Meediainfo edastamise protokoll RTMP (Real Time Messaging Protocol) oli tegelikult pikka aega suletud, mis tähendas: kui soovite oma voogedastusteenust turgutada, olge lahked ja kasutage Adobe enda tarkvara - Adobe Media Server (AMS).
Mõne aja pärast 2012. aastal andis Adobe alla ja sülitas selle avalikkuse ette.
Adobe Flashi platvorm on rohkem kui 20 aastat vana, selle aja jooksul on avastatud palju kriitilisi turvaauke, tugi
Oma projekti jaoks otsustasin kohe brauseris Flashi kasutamisest täielikult loobuda. Mainisin ülaltoodud peamise põhjuse; Flashi ei toetata ka mobiilsetel platvormidel ja ma ei tahtnud Adobe Flashi Windowsi (veini emulaator) arendamiseks juurutada. Nii otsustasin kirjutada JavaScriptis kliendi. Sellest saab alles prototüüp, sest hiljem sain teada, et voogesitust saab p2p baasil teha palju tõhusamalt, ainult minu jaoks on see peer - server - peer, aga sellest lähemalt mõni teine kord, sest see pole veel valmis.
Alustamiseks vajame tegelikku Websocketsi serverit. Kõige lihtsama tegin melody go paketi põhjal:
Serveri kood
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)
}
Kliendil (voogesituse poolel) peate esmalt pääsema juurde kaamerale. Seda tehakse läbi
Saame ligipääsu (loa) kaamerale/mikrofonile läbi
Järgmisena tagastab getUserMedia() lubaduse, millele edastab MediaStreami objekti – video-heliandmete voo. Määrame selle objekti videoelemendi atribuudile src. Kood:
Ringhäälingu pool
<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>
Videovoo edastamiseks pistikupesade kaudu peate selle kuhugi kodeerima, puhverdama ja osade kaupa edastama. Toores videovoogu ei saa veebipistikupesade kaudu edastada. Siin tuleb see meile appi
Kodeerime videovoo, jagame selle osadeks
<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>
Lisame nüüd edastuse veebipistikupesade kaudu. Üllataval kombel on selleks vaja ainult objekti
Edastame videovoo serverisse
<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>
Saate pool on valmis! Proovime nüüd videovoogu vastu võtta ja kliendil kuvada. Mida me selleks vajame? Esiteks muidugi pistikupesa ühendus. Lisame WebSocketi objektile "kuulaja" ja tellime sündmuse "sõnum". Pärast binaarandmete osa vastuvõtmist edastab meie server selle abonentidele, see tähendab klientidele. Sel juhul käivitatakse kliendil tagasihelistamise funktsioon, mis on seotud sõnumi sündmuse "kuulajaga"; objekt ise edastatakse funktsiooni argumendisse - vp8 poolt kodeeritud videovoo osa.
Aktsepteerime videovoogu
<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>
Pikka aega püüdsin aru saada, miks ei ole võimalik saabunud tükke kohe videoelemendile taasesitamiseks saata, kuid selgus, et seda ei saa teha, muidugi tuleb esmalt tükk spetsiaalsesse puhvrisse, mis on seotud videoelementi ja alles siis hakkab see videovoogu esitama. Selleks vajate
MediaSource toimib omamoodi vahendajana meediumi taasesitusobjekti ja selle meediumivoo allika vahel. MediaSource objekt sisaldab ühendatavat puhvrit video/helivoo allika jaoks. Üks omadus on see, et puhver mahutab ainult Uint8 andmeid, seega vajate sellise puhvri loomiseks FileReaderit. Vaadake koodi ja see muutub selgemaks:
Videovoo esitamine
<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>
Voogedastusteenuse prototüüp on valmis. Peamine puudus on see, et video taasesitus jääb edastavast poolest 100 ms võrra maha, selle määrame ise videovoo jagamisel enne serverisse edastamist. Veelgi enam, kui ma oma sülearvutit kontrollisin, kogunes järk-järgult vahe saatva ja vastuvõtva poole vahel, see oli selgelt näha. Hakkasin otsima viise, kuidas sellest puudusest üle saada ja... jõudsin kohale
Allikas: www.habr.com