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

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


    Выход из этой ситуации очевиден: видеозапись должна вестись не в локальный файл на ваш телефон, а непосредственно на удаленный сервер. Правда, готовых программных решений для реализации этой идеи не так много (например, вот): в большинстве случаев предлагаемые приложения для мобильного телефона или платные, или работают из рук вон плохо. Экзотические рекомендации типа «в случае нападения хулиганов начните трансляцию на 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;
    
    // Забираем пароль из queryString
    let 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;
    • нажать на кнопку «Запись» на загрузившейся страничке.

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


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

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 24

      0

      А если повредится один фрагмент, то все видео накроется медным тазом?

        0

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

        0

        Я просто запускаю IP Webcam и забираю из него видеопоток ffmpeg'ом через SSH-туннель (впрочем, он умеет и сам файлы в облако отправлять, но файлы делит криво и я это не юзаю)


        А ещё далеко не везде интернет достаточно хороший для видео, у меня скорость 4G сейчас всего 0.5 Мбит/с, видео особо не попередаёшь

          0

          Так решений полно, конечно. Но мне было интересно реализовать нативными, так сказать, средствами, без чужих приложений типа IP webcam.


          По поводу скорости интернета — можно задать низкую частоту кадров, хоть 5 кадров в секунду, например. Тогда не видео получится фактически, а просто скоростная фотосъемка.

          0

          Тоже думал об этом, но мобильную разработку недолюбливаю.
          Из дополнительных идей:
          Отправка текста(если скорость не позволяет)/видео по заданным емейл, если не отзовешь в течение N времени.
          Добавление GPS меток, номера sim-карты и imei.
          Автоматическая переотправка при обнаружении сети (для варианта отобрали в лесу, приехали в город), а в идеале и при включении телефоне (для варианта отобрали в лесу, выключили, попал в другие руки в городе).
          Отправка сообщения при движении.

            0

            Это, конечно, все отлично, вот только можно ли реализовать средствами javascript и АPI html5 в браузере? Ну геопозиционирование получить можно, а вот всё остальное… можно подумать ))

              0
              А не следует делать в браузере. Причины:
              1. Менее эффективный расход энергии;
              2. При блокировке экрана приложение уйдёт в фон;
              3. При переходе в фон и активном использовании сети Андроид может убить браузер или вытеснить его из оперативной памяти. Если этого не произойдёт, то будет уведомление, что браузер использует батарею, что может заинтересовать ушлых ребят.

              Ваш текущий вариант не имеет обработки ошибок в случае возникновения таковых на сервере.

              включить мобильный интернет (если он не включен у вас на телефоне постоянно);
              Может всё же пусть приложение само пытается найти доступную WiFi сеть и/или мобильный интернет и автоматически включает их?

              Достаточно просто сконкатенировать все файлы в один
              Может нужно было инициализировать сессию и при съёмке в рамках одной сессии автоматически конкатенировать с помощью:
              file_put_contents("video/".$flName, $data, FILE_APPEND | LOCK_EX);
              ?

              $flName = date("ymd-His").".webm";
              Нельзя использовать один и тот же сервер с этим скриптом одновременно с двух телефонов, иначе один видеофайл может перезаписаться другим. Используйте уникальный идентификатор (сессий, uuid) создавая директорию и размещая файл(ы) уже в ней.

              То есть даже если хулиганы начали отбирать у вас телефон — вы успеваете нажать кнопку «стоп» и последний видеофайл уходит на сервер.
              Мне кажется, что как раз во время попытки отобрать телефон и после этого начинается «всё самое интересное». Телефон должен и дальше снимать. Не останавливаясь.
                0
                Нельзя использовать один и тот же сервер с этим скриптом одновременно с двух телефонов

                Я об этом и пишу постоянно в статье: предложенное решение — исключительно для личного использования, только со своего телефона. Это многое упрощает.
                Может нужно было инициализировать сессию и при съёмке в рамках одной сессии автоматически конкатенировать

                Я об этом думал, но идея не очень хорошая, так как в результате мы получим огромный файл на сервере, который замучаемся качать потом, особенно при нестабильном интернете.
            0

            Так идея состояла именно в том, чтобы написать браузерное нативное решение, а не мобильное приложение. Но я согласен, на предложенный скелет можно еще наращивать мясо.

              +2
              А какой смысл писать браузерное «нативное» решение, если оно в любой момент может перестать работать из-за перехода в фон и при этом разные фичи нужно делать через костыли? Не понимаю. Мне кажется, что нужно выбирать тот инструмент, который наиболее подходит под задачу.
                0
                Задача состояла в том, чтобы написать простенький инструмент для личного пользования (с одного своего телефона) и исключительно для видеосъемки своими руками (никакого ухода браузера в фон). Этот инструмент и не претендует ни на что большее. Если потребуются дополнительные фичи, то, конечно, придется писать мобильное приложение.
              0
              Тема модная. В США правозащитники выбрали вариант сделать это в виде приложения: ACLU Mobile Justice Moon Prism Power.
                0
                ИМХО — это всё ДОЛГО.
                Телефон еще надо разблокировать.
                Пусть даже инет включен постоянно — может вылезти какая-нибудь фигня (обновление или еще что) и в критической ситуации это всё помешает незаметному включению записи.
                У меня на телефоне есть бесполезная кнопка — её можно настроить на запуск приложения которое должно сразу писать в облако. Ну и куски по минуте — это очень долго. Надо по секунде. За секунду может много чего произойти.
                Может такое уже есть? Пусть и платное, но хорошее.
                  0

                  Я искал, но ничего путного не нашел. IP-камер и систем видеонаблюдения много, но это немного не то, что нам нужно. Единственное — это разработка ребят из Иваново, на которую есть ссылка в начале статьи. Надо писать своё.


                  А про минуту и секунду не понял, если честно. Пишется всё, просто видео делится на минутные отрезки. Можно и секундные сделать, конечно, не проблема, но зачем?


                  Сразу писать в облако — это WebRTC, но это гораздо сложнее в реализации. Ну или, действительно, отправлять секундные отрезки и на сервере их конкатенировать.

                    0

                    Вышеупомянутый IP Webcam умеет выводить кнопку быстрого запуска на лаунчер, а также работать в фоне, лично мне он норм (хотя и не без недостатков)


                    Можно и секундные сделать, конечно, не проблема, но зачем?

                    Даже секундные куски это слишком долго. Случись что — телефон летит из рук на асфальт, аккумулятор улетает на два метра от девайса, и весь записанный кусок безвозвратно теряется. Так что — только захват RTSP-потока ffmpeg'ом через SSH-туннель :)

                      0
                      WebRTC здесь не нужен от слова совсем. Достаточно использовать вебсокеты.
                        0
                        Просто WebRTC как бы поддерживается браузерами из коробки, а для работы с сокетами на клиентской стороне придется использовать сторонние библиотеки. Понятно, что можно разные транспорты использовать, в том числе и сокеты, конечно, но не уверен, что это лучшее решение.
                          0
                          Для удобной работы с WebRTC тоже необходимы библиотеки. При этом без библиотек и с WebSocket вполне возможно работать, если нужно. И почему бы не использовать библиотеки? Тоже вопрос.

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

                          Не понял? Он же прост как дубина, var ws = new WebSocket(), onconnect onmessage и вперёд. Никогда не использовал библиотеки для вебсокетов, ибо не вижу смысла (даже небезызвестный socket.io всегда игнорировал)

                      0
                      Да, с сокетами можно поиграться (именно для передачи потокового видео), надо попробовать на досуге.
                        0
                        телефон у вас отбирают (или изымают на законных основаниях — нужное подчеркнуть). Свидетелей нет, видеозаписи на телефоне больше нет, доказательств для полиции и суда тоже, соответственно, никаких
                        С практической точки зрения, эта запись не является доказательством в суде РФ — вспомните, было несколько видео про человека, который просто стоял у метро, а ему было предъявлено обвинение в нападении на ОМОН с заведением уголовного дела, и видео происшествия с телефонов не принималось судом как доказательство. Причем все по закону, поскольку средства аудио- и видеофиксации должны иметь сертификацию и поверку, в смысле, бумажку, что видеокамера прошла сертификацию и поверку, практического смысла в этом не больше, чем в бумажке Ростеста для телефонов.
                        Так что выложить в Ютуб для привлечения внимания к своей проблеме — можно, в остальном — в РФ годится только для административных (типа автоаварии), а не уголовных дел. Лучше понимать это сразу и приоритетно озаботиться поиском свидетелей с места происшествия, а не сохранением видеозаписи. Хотя при прочих равных — видеозапись лишней не будет.
                          0
                          средства аудио- и видеофиксации должны иметь сертификацию и поверку, в смысле, бумажку, что видеокамера прошла сертификацию и поверку

                          Насколько я понимаю, в законе такой нормы нет (поправьте меня, если я ошибаюсь). Зато есть такое понятие как «оценка доказательств» судом. То есть все отдано на откуп судье: захочет — примет это как доказательство, не захочет — не примет. На том же ютубе достаточно много видео, в которых суд просматривает записи, снятые мобильными телефонами, и принимает их в качестве доказательства.
                            0
                            Открывать же браузер, а затем нажимать на значок домика (если вы установили написанное в качестве домашней страницы) довольно долго.
                            Выход очень прост: создаем в файловой системе телефона простенький файлик alarm.html:

                            Можно ещё оформить как pwa приложение.

                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                            Самое читаемое