Dans cet article, je souhaite partager mes tentatives de streaming vidéo via des sockets Web sans utiliser de plug-ins de navigateur tiers tels que Adobe Flash Player. Qu'est-il advenu de cette lecture.
Adobe Flash - anciennement Macromedia Flash, est une plate-forme pour créer des applications qui s'exécutent dans un navigateur Web. Avant la mise en œuvre de l'API Media Stream, c'était presque la seule plate-forme pour diffuser des vidéos et de la voix à partir d'une webcam, ainsi que pour créer diverses conférences et discussions dans un navigateur. Le protocole de transmission d'informations sur les médias RTMP (Real Time Messaging Protocol) a en fait été fermé pendant longtemps, ce qui signifie: si vous souhaitez augmenter votre service de streaming, veuillez utiliser le logiciel d'Adobe lui-même - Adobe Media Server (AMS).
Après un certain temps en 2012, Adobe a «abandonné et craché» la
spécification du protocole RTMP, qui contenait des erreurs et, en fait, n'était pas complète. À ce moment-là, les développeurs ont commencé à implémenter ce protocole, de sorte que le serveur Wowza est apparu. En 2011, Adobe a déposé une plainte contre Wowza pour l'utilisation illégale de brevets liés au RTMP, après 4 ans, le conflit a été résolu par le monde.
La plate-forme Adobe Flash existe depuis plus de 20 ans, pendant ce temps, de nombreuses vulnérabilités critiques ont été découvertes, elles ont
promis de cesser de prendre en charge d'ici 2020, il n'y a donc pas tant d'alternatives pour le service de streaming.
Pour mon projet, j'ai immédiatement décidé d'abandonner complètement l'utilisation de Flash dans le navigateur. La raison principale que j'ai indiquée ci-dessus, Flash n'est pas du tout pris en charge sur les plates-formes mobiles et je ne voulais vraiment pas déployer Adobe Flash pour le développement sur Windows (émulateur Wine). J'ai donc commencé à écrire un client en JavaScript. Ce ne sera qu'un prototype, car j'ai appris plus tard que le streaming peut être fait beaucoup plus efficacement sur la base de p2p, seulement j'aurai des pairs - serveur - pairs, mais plus à ce sujet une autre fois, car il n'est pas encore prêt.
Pour commencer, nous avons besoin du serveur websockets lui-même. J'ai fait le plus simple basé sur le package go mélodie:
Code serveurpackage 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) })
Côté client (côté diffusion), vous devez d'abord accéder à la caméra. Cela se fait via l'
API MediaStream .
Nous obtenons l'accès (résolution) à la caméra / au microphone via l'
API Media Devices . Cette API fournit la méthode
MediaDevices.getUserMedia () , qui affiche une fenêtre contextuelle. une fenêtre demandant à l'utilisateur l'autorisation d'accéder à la caméra et / ou au microphone. Je voudrais noter que j'ai mené toutes les expériences dans Google Chrome, mais je pense que dans Firefox, tout fonctionnera à peu près de la même manière.
Ensuite, getUserMedia () renvoie une promesse, dans laquelle un objet MediaStream est transmis - un flux de données vidéo et audio. Nous affectons cet objet à la propriété de l'élément vidéo dans src. Code:
Côté diffusion <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'); </script>
Pour diffuser un flux vidéo via des sockets, vous devez en quelque sorte le coder quelque part, le mettre en mémoire tampon et le transmettre en plusieurs parties. Le flux vidéo brut ne peut pas être transmis via des sockets Web. C'est
là que l'API MediaRecorder vient à la
rescousse . Cette API vous permet d'encoder et de diviser un flux en morceaux. Je fais du codage pour compresser le flux vidéo, afin de pouvoir conduire moins d'octets sur le réseau. Après avoir éclaté en morceaux, il est possible d'envoyer chaque pièce sur websocket. Code:
Nous encodons le flux vidéo, le battons en morceaux <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'); </script>
Ajoutez maintenant le transfert sur les websockets. Étonnamment, cela ne nécessite qu'un objet
WebSocket . Il n'a que deux méthodes d'envoi et de fermeture. Les noms parlent d'eux-mêmes. Code étendu:
Nous transmettons le flux vidéo au serveur <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'); </script>
Le côté diffusion est prêt! Essayons maintenant de prendre un flux vidéo et de le montrer sur le client. De quoi avons-nous besoin pour cela? Tout d'abord, bien sûr, une connexion socket. Nous raccrochons un "écouteur" sur l'objet WebSocket, souscrivons à l'événement "message". Ayant reçu une donnée binaire, notre serveur la diffuse aux abonnés, c'est-à-dire aux clients. Dans le même temps, la fonction de rappel liée à l '«écouteur» de l'événement «message» est déclenchée sur le client, l'objet lui-même est passé dans l'argument de la fonction - un morceau du flux vidéo codé par vp8.
Nous acceptons un flux vidéo <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) { </script>
Pendant longtemps, j'ai essayé de comprendre pourquoi il était impossible d'envoyer immédiatement les éléments reçus à l'élément vidéo pour la lecture, mais cela s'est avéré impossible, bien sûr, vous devez d'abord placer le morceau dans un tampon spécial attaché à l'élément vidéo, et seulement alors, il commencera à lire le flux vidéo. Pour ce faire, vous avez besoin de l'
API MediaSource et de l'
API FileReader .
MediaSource agit comme une sorte d'intermédiaire entre l'objet de lecture multimédia et la source de ce flux multimédia. L'objet MediaSource contient un tampon enfichable pour la source de flux vidéo / audio. Une caractéristique est que le tampon ne peut contenir que des données Uint8, FileReader est donc requis pour créer un tel tampon. Jetez un œil au code et il deviendra plus clair:
Lire le flux vidéo <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(), </script>
Le prototype du service de streaming est prêt. Le principal inconvénient est que la lecture vidéo sera à 100 ms derrière le côté de transmission, nous le définissons nous-mêmes lors du fractionnement du flux vidéo avant de l'envoyer au serveur. De plus, lorsque j'ai vérifié sur mon ordinateur portable, j'ai progressivement accumulé un décalage entre les côtés émetteur et récepteur, ce qui était clairement visible. J'ai commencé à chercher des moyens de surmonter cette lacune, et ... je suis tombé sur l'
API RTCPeerConnection , qui vous permet de transférer un flux vidéo sans astuces comme diviser un flux en morceaux. Le décalage accumulé, je pense, est dû au fait que dans le navigateur, avant le transfert, chaque morceau est transcodé au format webm. Je n'ai pas creusé plus loin, mais j'ai commencé à étudier WebRTC, je pense aux résultats de mes recherches, j'écrirai un article séparé si je trouve cette communauté intéressante.