Bu makalede, Adobe Flash Player gibi üçüncü taraf tarayıcı eklentilerini kullanmadan websockets aracılığıyla video akışı yapma girişimlerimi paylaşmak istiyorum. Bunun ne olduğunu öğrenmek için okumaya devam edin.
Adobe Flash, eski adıyla Macromedia Flash, bir web tarayıcısında çalışan uygulamalar oluşturmaya yönelik bir platformdur. Media Stream API'nin piyasaya sürülmesinden önce, bir web kamerasından video ve ses akışının yanı sıra tarayıcıda çeşitli türde konferanslar ve sohbetler oluşturmak için pratik olarak tek platformdu. Medya bilgilerini aktarma protokolü RTMP (Gerçek Zamanlı Mesajlaşma Protokolü) aslında uzun bir süredir kapalıydı; bu şu anlama geliyordu: akış hizmetinizi güçlendirmek istiyorsanız, Adobe'nin kendi yazılımını - Adobe Media Server'ı (AMS) kullanma nezaketini gösterin.
2012'de bir süre sonra Adobe "vazgeçti ve bunu kamuoyuna tükürdü".
Adobe Flash platformunun yaşı 20'den fazladır ve bu süre zarfında birçok kritik güvenlik açığı keşfedilmiştir.
Projem için tarayıcıda Flash kullanımını hemen tamamen bırakmaya karar verdim. Ana nedeni yukarıda belirttim; Flash mobil platformlarda da hiç desteklenmiyor ve Adobe Flash'ı Windows (wine emulator) üzerinde geliştirme için dağıtmak istemedim. Bu yüzden JavaScript'te bir istemci yazmaya başladım. Bu sadece bir prototip olacak, çünkü daha sonra akışın p2p'ye dayalı olarak çok daha verimli bir şekilde yapılabileceğini öğrendim, sadece benim için eş - sunucu - eşler olacak, ancak daha fazlası başka zaman olacak çünkü henüz hazır değil.
Başlamak için gerçek websockets sunucusuna ihtiyacımız var. Melody go paketine dayanarak en basitini yaptım:
Sunucu kodu
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)
}
İstemcide (akış tarafında), öncelikle kameraya erişmeniz gerekir. Bu aracılığıyla yapılır
aracılığıyla kameraya/mikrofona erişim (izin) alıyoruz.
Daha sonra, getUserMedia() bir Promise döndürür ve bu nesneye bir video-ses verileri akışı olan MediaStream nesnesini iletir. Bu nesneyi video öğesinin src özelliğine atadık. Kod:
Yayın tarafı
<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>
Bir video akışını soketler üzerinden yayınlamak için, onu bir yere kodlamanız, arabelleğe almanız ve parçalar halinde iletmeniz gerekir. Ham video akışı web soketleri aracılığıyla iletilemez. Burası bizim yardımımıza geliyor
Video akışını kodluyor, parçalara ayırıyoruz
<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>
Şimdi websocket'ler aracılığıyla iletim ekleyelim. Şaşırtıcı bir şekilde, bunun için ihtiyacınız olan tek şey bir nesne
Video akışını sunucuya aktarıyoruz
<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>
Yayın tarafı hazır! Şimdi bir video akışı almayı ve bunu istemcide görüntülemeyi deneyelim. Bunun için neye ihtiyacımız var? Öncelikle elbette soket bağlantısı. WebSocket nesnesine bir “listener” ekleyip 'message' olayına abone oluyoruz. Bir parça ikili veri aldıktan sonra sunucumuz bunu abonelere, yani istemcilere yayınlar. Bu durumda, 'mesaj' olayının "dinleyicisi" ile ilişkili geri çağırma işlevi istemcide tetiklenir; nesnenin kendisi, vp8 tarafından kodlanan video akışının bir parçası olan işlev argümanına iletilir.
Video akışını kabul ediyoruz
<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>
Uzun bir süre, alınan parçaları oynatmak için video öğesine hemen göndermenin neden imkansız olduğunu anlamaya çalıştım, ancak bunun yapılamayacağı ortaya çıktı, elbette, önce parçayı özel bir ara belleğe koymanız gerekiyor. video öğesini seçin ve ancak o zaman video akışını oynatmaya başlayacaktır. Bunun için ihtiyacınız olacak
MediaSource, medya oynatma nesnesi ile bu medya akışının kaynağı arasında bir tür aracı görevi görür. MediaSource nesnesi, video/ses akışının kaynağı için takılabilir bir arabellek içerir. Bir özelliği, arabelleğin yalnızca Uint8 verilerini tutabilmesidir, dolayısıyla böyle bir arabellek oluşturmak için bir FileReader'a ihtiyacınız olacaktır. Koda bakın ve daha net hale gelecektir:
Video akışını oynatma
<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>
Akış hizmetinin prototipi hazır. Ana dezavantaj, video oynatımının iletim tarafının 100 ms gerisinde kalmasıdır; bunu, video akışını sunucuya aktarmadan önce bölerken kendimiz ayarlıyoruz. Üstelik dizüstü bilgisayarımda kontrol ettiğimde verici ve alıcı taraflar arasındaki gecikmenin giderek biriktiği açıkça görülüyordu. Bu dezavantajı aşmanın yollarını aramaya başladım ve...
Kaynak: habr.com