Создание видео трансляции на JS

в 15:37, , рубрики: JS, nodejs, php, Веб-разработка, Песочница, метки: ,

Доброго времени суток, дорогой хабрачитатель.
Сегодня я расскажу, как можно организовать прямую трансляцию изображения с веб-камеры с помощью HTML5/JS и NodeJS, а так же PHP.

В статье наблюдается довольно большое количество кода. Так же эта статья ориентирована скорей на начинающих веб разработчиков, чем на профессионалов.

Клиентская часть

Для начала нужно создать элемент типа «video», в который будет копироваться поток из navigator.getUserMedia, а так же canvas, из которого будет браться изображение для отправки:

<video autoplay id="vid" style="display:none;"></video>
<canvas id="canvas" width="640" height="480" style="border:1px solid #d3d3d3;"></canvas><br>

Далее нужно направить поток из getUserMedia в video:

var video = document.querySelector("#vid"),
       canvas = document.querySelector('#canvas'),
       ctx = canvas.getContext('2d'),
       localMediaStream = null,
       onCameraFail = function (e) {
            console.log('Camera did not work.', e); // Исключение на случай, если камера не работает
        };
       navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
        window.URL = window.URL || window.webkitURL;
        navigator.getUserMedia({video: true}, function(stream) {
            video.src = window.URL.createObjectURL(stream);
            localMediaStream = stream;
        }, onCameraFail);

Ну что же, на данном этапе в тег «video», который кстати не видно, клонируется видеопоток. Теперь надо сделать так, чтобы изображение постоянно копировалось в canvas. Для этого поставим таймер, вызывающий процедуру копирования, и, соответственно, саму процедуру копирования.

cameraInterval = setInterval(function(){ snapshot();}, 1);
function snapshot(){
       if(localMediaStream){
              ctx.drawImage(video, 0, 0);
        }
}

Хорошо, теперь можно посмотреть на себя, но данные ещё никуда не посылаются. Для отправки данных надо убедиться в синхронности происходящего и в том, что пользователь согласен вести трансляцию, поэтому добавим пару кнопок для включения и отключения потока.

<button onclick="startBroadcasting()">Start Broadcasting</button>
<button onclick="stopBroadcasting()">Stop Broadcasting</button>

И напишем процедуру отправки данных на сервер.
Но каких данных? Для решения этой задачи можно использовать base64 сжатие всего, что находится на canvas'е, и, соответственно, отправлять эту строку на сервер.

var isBroadcasting = false,
	broadcastingTimer;
function sendSnapshot(){
	if(localMediaStream && !isBroadcasting){
		isBroadcasting = true;
                $.post("/",
			{
				p: "new",
				text: ctx.canvas.toDataURL("image/webp", quality); // quality - качество изображения(float)
			},
			function(result){
				console.log(result); // На случай, если что-то пойдёт не так
				isBroadcasting = false;
			}
		);
	}
}
// И добавим обработчики кнопок начала и завершения вещания
function startBroadcasting(){
	broadcastingTimer = setInterval(sendSnapshot, 1);
}
function stopBroadcasting(){
	clearInterval(broadcastingTimer);
}

С клиентской частью транслирования покончено.

Серверная часть(Node JS)

Для такого приложения не требуется каких-либо изощрений или серверных фреймворков. Сервер создаётся при помощи функции http.createServer.
Главное здесь дописать обработчик на случай, если пришёл post запрос.

var qs = require('querystring');
var imageData = "";
var myId = 1;
/* Запуск сервера */
if(req.method == "POST"){
	var fullBody = "";
	req.on('data', function(chunk){
		fullBody += chunk.toString();
	});
	req.on('end', function(){
		res.writeHead(200, {'Content-Type': 'text/html'});
		var POST = qs.parse(fullBody);
		if(POST["p"] == "new"){ // Смена изображения
			imageData = POST["text"];
			myId += 1;
			res.write(imageData);
		}else if(POST["p"] == "ajax"){
			if(myId > parseInt(POST["last"])){
				if(typeof(imageData) != "undefined"){
					res.write(document.body.innerHTML = ('<img src=" + '"' + imageData + '"' + "/>');" + "n");
					res.write("last_message_id = " + myId + ";");
				}
			}
		}
		res.end();
	});
}else{ /* Здесь идёт отдача всего, что пользователь просит. */}

Аналогично с php:

Код на php

if($_GET['p'] == "ajax"){
	Header("Cache-Control: no-cache, must-revalidate");
	Header("Pragma: no-cache");
	Header("Content-Type: text/javascript; charset=windows-1251");
	$file = file("monitor_id.txt");
	$id = $file[0];
	if($id > $_GET['last']){
		$text_file = file("monitor_command.txt");
		$count = count($text_file);
		$last = $id;
		echo "var main = $('#main'); n";
		for($i = 0; $i < 1; $i++){
			$s = $text_file[$i];
			while(strpos($s, chr(92)) !== false){
				$s = str_replace(chr(92), "", $s);
			}
			echo $s;
		}
		echo "n";
    	echo "last_message_id = $id;";
	}
}elseif((isset($_GET['p']) && $_GET['p'] == "new") || (isset($_POST['p']))){
	$file = file("monitor_id.txt");
	$id = $file[0];
	$fh = fopen("monitor_command.txt", "w+");
	$get_text = $_POST['text'];
	$gt = $get_text;
	while(strpos($get_text, "rn") !== false){
		$get_text = str_replace("rn", "</br>", $get_text);
	}
	fwrite($fh, "document.body.innerHTML = ('<img src=".'"'.".$get_text.'"'."/>);n");
	fclose($fh);
	$fhn = fopen("monitor_id.txt", "w+");
	fwrite($fhn, $id + 1);
	fclose($fhn);
	echo $get_text;
}

Клиентская часть(просмотр трансляции)

Как не странно, клиентская часть очень проста, от неё лишь требуется исполнение кода, приходящего от сервера.

var last_message_id = 0, 
	load_in_process = false; 
function Load() {
    if(!load_in_process)
    {
	    load_in_process = true;
    	$.post("/", 
    	{
      	    p: "ajax", 
      	    last: last_message_id,
			version: version
    	},
   	    function (result) {
		    eval(result);
		    load_in_process = false; 
    	});
    }
}
var loadInterval = setInterval(Load, 1);
Заключение

Сегодня мы написали простой сервис для организации односторонних видео трансляций посредством html5/js. Стоит лишь отметить то, что пока данное решение работает не очень прытко вследствие отсутствия нормального сжатия, а так-же из-за того, что все операции по обработке изображения могут производиться только на клиенте, а от этого скорость передачи кадров уменьшается и начинают наблюдаться «подвисания».

Но этот довольно простой пример — лишь доказательство того, что html5 уже не так далёк от flash, и что реализация многих вещей становится возможной.

Спасибо за внимание!

Автор: andruekonst

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js