В далёком 2018г. я целых 24 часа работал на какой то фабрике в Подмосковье. Делающей салаты и другие полуфабрикаты. В каком то отделе, то ли контроля, то ли безопасности. В общем надо было смотреть в камеры видео наблюдения. Ну и вроде всё, сутки через трое. Хотел уйти с 5/2 на 1/3. Но я не согласился, слишком тяжело, слишком. Через 6 лет после тех суток я начну изучать Java и пробовать писать приложения на андроид. Третьим моим пет приложением будет Ch4IR.

Написанный на библиотеке LibVLC и в память о несложивщейся карьере в сесурити.

Он был настоящим RTSP рекордером: отображал 4 потока, писал 4 потока на флешку или usb диск, нарезал куски видео по 60 минут и автоматически удалял записи старше указанной даты. Он умел воспроизводить записи и перематывать их. Но всё изменилось, перед самой публикацией.

dota2, теннис, кино и парковка.
dota2, теннис, кино и парковка.

Мне просто нравилось делать приложение, я не думал и не знал ни о каких лицензиях сторонних библиотек. Как оказалось библиотека LibVLC имеет лицензию LGPL требующую либо открывать код, либо подгружать библиотеку динамически, давая пользователю возможность её заменять. Динамическая подгрузка это что то сложное и не безопасное, а открыть код, как? Как можно открыть такой крутой, сложный код который принесёт мне миллионы, зачем? Вы пошутили? Поэтому я перешёл на встроенный в андроид ExoPlayer с лицензией Apache 2.0. Который не умеет одновременно, и показывать, и писать видео с камеры видеонаблюдения.

Большая часть функционала была потеряна, пришлось придумывает новые функции. Почему то не сразу, но всё таки пришла мысль добавить к воспроизведению RTSP, поддержку m3u8 ссылок для просмотра IPTV. Ну скучно же смотреть весь день на парковку или склад. В самом приложении никакого контента нет, оно просто проигрыватель. Но ссылки на бесплатные каналы можно поискать в сети. И в приложении даже есть один линк на такой репозиторий GitHub. Можно загружать их пачками из файлов txt или m3u.

Добавление плейлистов из файла m3u
Добавление плейлистов из файла m3u

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

  • белый - stop

  • зеленый - play

  • жёлтый - play + mute

  • красный - record

И это всё ещё был просто онлайн медиапроигрыватель, хоть и в несколько окон. Надо было что то ещё. Очень хотелось добавить YouTube. Но там обязательная авторизация и плюс он скорее про ролики, а не про онлайн стримы. А вот Twitch, Twitch да, он про онлайн, и позволяет смотреть стримы без регистрации, хоть и с рекламными вставками. И делается это довольно просто. Нужна просто одна html страница на любом домене и всё.

Скрытый текст
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Ch4IR</title>
    <script src="https://player.twitch.tv/js/embed/v1.js"></script>
    <style>
        html, body {
          margin: 0;
          padding: 0;
          height: 100%;
          overflow: hidden;
          background-color: black;
        }
        #twitch-embed {
          width: 100vw;
          height: 100vh;
        }
    </style>
</head>
<body>
<div id="twitch-embed"></div>
<script>
    const urlParams = new URLSearchParams(window.location.search);
    const channelName = urlParams.get('channel');

    if (!channelName) {
      document.body.innerHTML = "<p style='color: white; text-align: center;'>Missing channel</p>";
    } else {
      const player = new Twitch.Player("twitch-embed", {
        width: "100%",
        height: "100%",
        channel: channelName,
        autoplay: true,
        parent: ["тут ваш домен"]
      });
    }
</script>
</body>
</html>

и стримы будут работать по ссылкам

https://ваш.домен/?channel=название_канала

Но ExoPlayer не умеет показывать Twitch, правильно, не умеет. Но умеет гугловский WebView. И если у вас нет сервисов Google на устройстве, то стримы твича приложение вам не покажет. Ошибки воспроизведения всех типов ссылок, тоже показывает WebView. Он вообще молодец. Поскольку мой СТУЛЬчик родом из рекордеров, он умеет делать Fullcreen, Zoom и Pan. И даже мышкой. Да мышкой тоже можно управлять. А вот пультом нет, казалось бы, а как же Android TV? Приложение показывающее в 4 экрана IPTV и не поддерживает телик, серьёзно? Ага, серьёзно. У меня нет Android TV, а если и появится, я 8 раз подумаю, стоит ли возиться с ним. Там всё управление завязано на пульт, который захватывает фокус элементов. А у меня одно меню на восемь экранов, семь из которых могут быть скрыты. Хотя это класный вызов, но... я восемь раз подумаю. Тем более что usb или air мышка стоят не дорого, а с ними работает и так.

И так, в приложении на самом деле 8 экранов (4 ExoPlayer + 4 WebView). Они просто появляются или скрываются в зависимости от типа выбранной для отображения ссылки. Меню вызывается при удержании пальца на любом экране. Но если присмотреться, то можно заметить что оно при просмотре IPTV вызывается быстрее, чем при просмотре Twitch. У твича есть свои элементы управления: запуск, остановка, изменение громкости, Mute. Чтоб управлять твичем через его собственные кнопки и не вызывать при этом моё меню, пришлось увеличить время удержания пальца для WebView.

И это было самое сложное, сложнее записи и удаления файлов. Это до сих пор самое сложное что приходит на память, из того что приходилось делать. Связать кнопку громкости твича с моей Mute. Твич отлично присылает события от своих кнопок в моё приложение через JavaScript Bridge. Но если ползунком убрать звук на твиче до 0, а потом нажать Mute. Твич отправит сообщение что звук включен, а громкость у этого звука будет равно 0. Потому что Mute на твиче восстанавливает предыдущее значение громкости, которое ползунком можно сдвинуть в 0. Хотя прошёл уже год, может что то и изменилось за это время у них. Проверил, исправили, а скорее подсмотрели у меня, и тоже выталкивают при анмуте громкость на 10%. Правильно я не хотел открывать код, ну всё таки подсмотрели.

Скрытый текст
private void setTwitchPlayerMute(boolean needMuted) {
        WebView webView = webViewRef != null ? webViewRef.get() : null;
        if (webView != null) {
            webView.evaluateJavascript(WINDOW_PLAYER_READY, value -> {
                //   Log.e(AppController.LOG_TAG, "Твичу пришла команда мут.анму, а твич реди статус = " + value);
                if (TRUE.equals(value)) {
                    if (needMuted) {
                        saveTwitchVolume();
                        webView.evaluateJavascript(TWITCH_MUTE_SCRIPT, null);
                        //   Log.e(AppController.LOG_TAG, "твичу пришла команда сделать мут" );
                    } else {
                        webView.evaluateJavascript(TWITCH_UNMUTE_SCRIPT, null);
                        //   Log.e(AppController.LOG_TAG, "твичу пришла команда сделать анмут" );
                        if (twitchVolume == 0f) {
                          // КАРАУЛ, УКРАЛИ :)
                            webView.evaluateJavascript(String.format(Locale.ENGLISH, TWITCH_SET_VOLUME_SCRIPT, 0.1f), null);
                            //  Log.e(AppController.LOG_TAG, "При анмуте громкость была 0: установлена 0.1" );
                        } else {
                            webView.evaluateJavascript(String.format(Locale.ENGLISH, TWITCH_SET_VOLUME_SCRIPT, twitchVolume), null);
                            // Log.e(AppController.LOG_TAG, "При анмуте установлена громкость" + twitchVolume);
                        }
                    }
                }
            });
        }
    }

Но это пустяки по сравнению с тем что получилось.

2026

Бывшие коллеги могут теперь смотреть спорт, ставки, мультики, танцующих и приседающих девочек на твиче, Джейсона Стэйтема. А ну и за складом тоже. А я нет. Я не смог смотреть за тем производством. Там в 2018 под предлогом важности работы, коллеги в столовую не ходили, а заказывали обед к столу, по звонку. Штрафовали на пятак мойщика за то что камера запотела. Подсовывали в паллет мусор чтоб выжить соперника упаковщика с любовного фронта. И всё это за одну смену стажёра. Я целые сутки просидел в этой копакабане, бесплатно. Тяжело, очень тяжело.

Скачать программу для работы.

Комментарии (0)