Русский
Русский
English
Статистика
Реклама

Хранение видео

Видеозапись в облако своими руками

13.06.2020 14:36:10 | Автор: admin

Идёте вы, уважаемый читатель, погожим летним вечером по улице, никого не трогаете, и тут на вас наезжают (тьфу-тьфу-тьфу, как говорится). Хулиганы, просто прохожие, специальные товарищи (как пелось в одной старой песенке) не столь важно. Вы достаете телефон и начинаете снимать происходящее на видео. Это не очень нравится наезжающим, и телефон у вас отбирают (или изымают на законных основаниях нужное подчеркнуть). Свидетелей нет, видеозаписи на телефоне больше нет, доказательств для полиции и суда тоже, соответственно, никаких.


Выход из этой ситуации очевиден: видеозапись должна вестись не в локальный файл на ваш телефон, а непосредственно на удаленный сервер. Правда, готовых программных решений для реализации этой идеи не так много (например, вот): в большинстве случаев предлагаемые приложения для мобильного телефона или платные, или работают из рук вон плохо. Экзотические рекомендации типа в случае нападения хулиганов начните трансляцию на YouTube я не рассматриваю, так как в реальной ситуации у вас элементарно не будет времени, чтобы запустить трансляцию. Кроме того, видео будет писаться в чьё-то чужое облако, а очень часто это не есть хорошо.


Можно, конечно, подучить Java или Kotlin (а заодно и Swift) или, на худой конец, освоить PhoneGap и написать своё приложение. Однако всё гораздо проще: под катом несложное решение этой задачи посредством HTML5 video/audio API.


Связываться ли с WebRTC


Безусловно, WebRTC очень крутая штука, позволяющая вести трансляцию в облако непосредственно. Однако реализация такой трансляции тот еще геморрой, поэтому я выбрал решение гораздо проще. Видео пишется в оперативную память телефона (заметьте, не на SD-карту, а только в оперативную память) и каждую минуту (например), а также по завершении записи отправляется на сервер. То есть даже если хулиганы начали отбирать у вас телефон вы успеваете нажать кнопку стоп и последний видеофайл уходит на сервер.


При настройках по умолчанию одна минута записи это файл размером около 20МБ. При этом никаких приложений, хоть готовых, хоть самописных только хардкор, только HTML и javascript.


Проблема кроссбраузерности


Справедливости ради надо сказать, что поддержка HTML5 video/audio API, хоть и развивается стремительно, все еще доставляет массу проблем разработчику. В предлагаемом ниже коде я сознательно не стал приводить кроссбраузерного варианта, чтобы не усложнять восприятие. Я даже, если честно, не тестировал этот код под различными ОС и различными браузерами: всё написанное замечательно работает в Mozilla Firefox 68 из-под Debian и в Chrome 83 из-под Android 7; в Chromium 80 из-под Debian и во многих браузерах для Android уже не работает в том, виде, в котором написано.


Так как вы будете использовать предложенное ниже исключительно в личных целях и на своем (скорее всего, на одном) мобильном телефоне, нужно просто найти реализацию video/audio API, поддерживаемую вашим устройством. Так, использованное мною navigator.mediaDevices.getUserMedia() придется, возможно, заменить на navigator.getUserMedia() или даже на navigator.webkitGetUserMedia, либо на navigator.mozGetUserMedia. Можно, конечно, написать и кроссбраузерный вариант. Кроме того, может потребоваться замена конструкции video.srcObject = stream на video.src = URL.createObjectURL(stream). Наконец, проблемы могут возникнуть из-за отсутствия поддержки MediaRecorder и fetch; последний, впрочем, легко заменяется AJAX'ом.


Итак, приступим Фронтенд


Как вы уже, наверно, поняли, мы собираемся написать html-страничку, которая берет видеопоток с камеры телефона (или ноутбука, или планшета, или стационарного компьютера) и раз в минуту отправляет соответствующий видеофайл на сервер fetch-запросом.


Html-файл очень прост, если не сказать элементарен:


<!DOCTYPE html><html lang="ru"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,      initial-scale=1.0, maximum-scale=1.0, user-scalable=no"><meta name="robots" content="noindex, nofollow"><link rel="stylesheet" type="text/css" href="style.css"><title>VideoCamera</title></head><body><video muted></video><button type="button" onclick="go()">&#9210;</button><script src="main.js"></script></body></html>

Здесь, собственно, только два элемента: окно, в котором пользователю будет показываться снимаемое им видео (без звука, чтобы не было эффекта эха; при этом на сервер звук будет отправляться, естественно) и кнопка Запись/Стоп. Для того, чтобы все это красиво выглядело и на телефоне, и на десктопе, пишем нехитрый style.css:


html {   height: 100%;}body {   height: 100%; margin: 0px; padding: 0px; background: black;   text-align: center;}video {   display: block; max-height: 100%; max-width: 100%; margin: auto;}button {   display: inline-block; width: 2em; margin-left: -1em;   position: absolute; bottom: 20px; left: 50%; background: none;   outline: none; border: none; font-size: 30px;  text-align: center;}

И, наконец, main.js, который выполняет всю работу на фронтенде:


"use strict";// Длительность одного блока записи в секундахconst recTime = 60;// Забираем пароль из queryStringlet pwd = location.search || 'a'; pwd = pwd.trim().replace('?', '');const video = document.querySelector("video"),      butt  = document.querySelector("button");let media, playFlag = false;// Начать запись видеоconst play = async () => {   try {      // Если клиент зашел со смартфона, включаем основную камеру      let c = /Android|iPhone/i.test(navigator.userAgent) ?         {video:{facingMode:{exact:"environment"}}, audio:true} :         {video:true, audio:true};      // Получаем видеопоток с камеры и показываем его юзеру      let stream = await navigator.mediaDevices.getUserMedia(c);      video.srcObject = stream;      video.play();      // Пишем видеопоток на сервер каждые recTime секунд      media = new MediaRecorder(stream);      media.ondataavailable = d => {         fetch("api.php", {            method: "POST",            headers: {"Content-Type": "video/webm", "X-PWD": pwd},            body: d.data         })      };      media.start(recTime * 1000);   }   catch(err) {alert(err);}};// Обработчик нажатия кнопки Запись/Стопconst go = () => {   if (!playFlag) {      butt.innerHTML = "&#9209;";      play();   }   else {      butt.innerHTML = "&#9210;";      video.pause();      video.srcObject = null;      media.stop();         }   playFlag = !playFlag;}

Здесь необходимы пояснения по поводу аутентификации. Конечно, можно обойтись и без нее, но тогда нет никакой гарантии, что какой-нибудь злоумышленник не воспользуется API вашего сервера (о нем речь впереди) и не зальет вам на сервер что-нибудь нехорошее. Поэтому, конечно, серверная сторона должна аутентифицировать клиента.


Это можно сделать различными способами (типа классического получения токена с сервера в ответ на отправленный пароль или анализа fingerprint клиента), но я решил не заморачиваться и поступил гораздо проще: пароль просто передается на сервер в заголовке X-PWD fetch-запроса; при этом пароль не вводится пользователем (вряд ли в глухом переулке у вас будет время для ввода пароля), а просто содержится в query string. Таким образом, для обращения к написанному сервису используется URL типа


https://my_domen/path/?abcde

где abcde и является паролем. На серверной же стороне пароль просто записан в коде: повторюсь, мы пишем это всё для себя, любимого, поэтому, на мой взгляд, можно обойтись таким примитивным способом аутентификации. Параноики могут, конечно, написать что-нибудь более продвинутое.


а теперь бэкенд


Начнем с проблемы хостинга и https. Реальность, увы, такова, что доступ к видеопотоку с вашей камеры вы не получите, если html-страничка получена по http. Наверно, это правильно. Выхода из этой ситуации, как обычно, два: либо использовать самоподписанный сертификат (вы же один, можно просто однократно принять этот сертификат и больше не заморачиваться), либо найти хостинг с поддержкой https.


Бесплатных хостингов, в том числе с поддержкой https, сейчас достаточно. Лучшим вариантом, конечно, будет хостить проект просто у себя, дома или на работе; не все, однако, хотят с этим связываться, поэтому бэкенд я написал на php, поддержка которого на бесплатных хостингах есть повсеместно. Вы будете смеяться, но файл api.php состоит всего из 6 строк:


<?php$pwdTrue = "abcde";$pwd     = $_SERVER["HTTP_X_PWD"];if ($pwdTrue !== $pwd) exit;@$data = file_get_contents("php://input") or $data = '';$flName = date("ymd-His").".webm";if ($data) file_put_contents("video/".$flName, $data);?>

Сервер просто принимает пришедший fetch-запросом видеофайл и кладет его в папку video с именем типа 200613-190123.webm (где 13.06.20 дата, а 19:01:23 время). При этом папка video будет доступна всем желающим (что довольно удобно, потому что можно скачать записанное видео просто браузером); если вы этого не хотите, можно закрыть эту папку с помощью .htaccess или другим способом, а отснятое видео забирать по ftp.


Здесь необходимо сделать важное замечание. Если ваша неприятная встреча в пустынном переулке длилась, например, 5 с небольшим минут, то на сервер будет отправлено 6 видеофайлов (пять минутных и шестой с оставшимся хвостиком). Корректно проигрываться при этом будет только первый; остальные (такова особенность реализации MediaRecorder) будут считаться продолжениями предыдущих и самостоятельно воспроизводиться не будут.


Это, однако, не недостаток, а скорее достоинство: чтобы получить цельную видеозапись, вам не нужно открывать видеоредактор и склеивать кусочки (что само по себе нехорошо, поскольку следы монтажа обнаружит любая судебная экспертиза). Достаточно просто сконкатенировать все файлы в один, и итоговое видео готово (ниже вариант для unix-подобных ОС):


$ cd путь_к_папке_с_файлами$ cat * > новое_имя.webm

Как пользоваться


Как я уже говорил выше, попытки испытать всё написанное в различных браузерах из-под Android увенчались успехом только для Chrome (может быть, вам повезет больше). Конечно, можно было подпилить код фронтенда и права доступа к камере для любого другого браузера, но Chrome меня вполне устраивал, поэтому я сосредоточился на другой проблеме.


Понятно, что в экстренной ситуации вы не будете долго открывать браузер и тем более вводить какой-то URL, да еще с паролем в query string. Кроме того, в Chrome для Android нельзя задать стартовую (не путать с домашней!) страницу. Открывать же браузер, а затем нажимать на значок домика (если вы установили написанное в качестве домашней страницы) довольно долго.


Выход очень прост: создаем в файловой системе телефона простенький файлик alarm.html:


<!DOCTYPE html><html lang="ru"><head><meta charset="utf-8"><meta http-equiv="refresh" content="0; url=https://domen/path?abcde"></head><body></body></html>

Создаем для этого файлика ярлык на рабочем столе телефона (прямо на главном экране). Теперь в экстренной ситуации вам необходимо выполнить всего три действия:


  • включить мобильный интернет (если он не включен у вас на телефоне постоянно);
  • кликнуть на ярлыке alarm.html;
  • нажать на кнопку Запись на загрузившейся страничке.

Последнее действие можно и исключить, если слегка подправить код фронтеда так, чтобы запись включалась сразу при загрузке страницы.


Вот, собственно и всё: простое решение, доступное каждому. Искренне желаю, чтобы лично вам это никогда не пригодилось...

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru