В предыдущей статье я рассказывал о простом сервере для работы с камерами видеонаблюдения, но для оперативного просмотра RTSP потоков использовал мобильное приложение VLC, которое меня не вполне устраивало по нескольким причинам. Под катом вы найдете описание и листинги простого мобильного приложения под андроид, написанного специально для охранных камер. Исходники приложения можно взять на github. Для тех, кто не хочет собирать apk самостоятельно, вот ссылка на готовые файлы. На самом деле доставить контент пользователю можно было бы разными способами, например, через веб приложение. Но, к сожалению, почти все современные браузеры не поддерживают кодек H.265, который мне был очень нужен, поэтому этот путь пришлось отбросить сразу. UPD: В следующей части будет рассказано о варианте доставки видеопотоков в современные браузеры на основе Chromium 106+ (такая возможность появилась после публикации этой статьи). Кроме того, в моей схеме подключения участвуют два сервера – локальный, с «серым» IP адесом, и удаленный, с «белым» IP, который предоставляет доступ к камерам через интернет по протоколу TCP. Поэтому одно из главных требований к приложению – возможность явного переключения TCP/UDP. Такой роскоши в VLC нет. Немного порывшись в плей маркете и перепробовав некоторое количество существующих приложений разного толка, активно впихивающих рекламу, выклянчивающих деньги за платный контент, требующих доступ к всем мыслимым и немыслимым активам телефона и бодро сливающие мои данные в недра интернета, я понял, что для решения такой казалось бы простой задачи все-таки придется заняться мобильной разработкой. Фреймворки для разработки кроссплатформенных приложений также пришлось исключить, потому что мне нужно обрабатывать жесты для масштабирования и позиционирования изображения, и сделать это максимально плавно. Кстати, JetBrains предлагает вроде бы интересное мультиплатформенное решение – Kotlin Multiplatform Mobile. Надо попробовать! Устанавливаю плагин KMM в Android Studio, создаю проект по единственному предложенному шаблону. Структура проекта не нравится. Ладно, может быть можно вынести в shared хотя бы строковые ресурсы? Нет, без танцев с бубном нельзя. А как собрать приложение под iOS? Да никак, для этого нужна iOS. А если учесть, что в стране, где я живу, будущее продукции Apple несколько туманно, смысл теряется окончательно. Решено: буду честно писать под андроид на его официальном языке — котлине. Приложение должно быть максимально простым, я (пока) не буду использовать фрагменты и граф навигации. У меня будет всего три экрана: список камер, редактор настроек камеры и экран видео:  Для работы с потоками я буду использовать библиотеку libvlc, настройки сохранять в приватном каталоге во внутреннем хранилище устройства в формате json с помощью библиотеки gson. Для взаимодействия с элементами представления мне нравится view binding, который включается опцией viewBinding true в файле build.gradle уровня приложения: В манифесте, помимо трех activity, нужно не забыть включить разрешение на доступ к сети android.permission.INTERNET: Главный экран приложения (MainActivity) содержит список камер recyclerView и ссылки на редактирование/добавление камер: Для работы recyclerView требуется адаптер: и элемент списка: За хранение данных отвечает синглтон StreamData, формат данных описывает data class StreamDataModel: Камеры (streams) хранятся в списке mutableList, доступ к данным камеры можно получить по индексу (position). Экран редактирования настроек камер (EditActivity) отвечает за добавление, редактирование и удаление записей в списке streams: Экран видео (VideoActivity) инициализирует медиаплеер (MediaPlayer(libVlc)) и добавляет необходимые параметры --rtsp-tcp и network-caching. К сожалению, не существует рекомендуемого набора опций, при которых плеер будет работать «хорошо». Значение параметра network-caching подобрано опытным путем. Слишком низкое значение может привести к невозможности отображения видеопотока, слишком высокое увеличивает задержку перед воспроизведением. Экран видео дополнительно реализует (implements) интерфейс MediaPlayer.EventListener, который нужен для отключения индикатора загрузки (pbLoading) после окончания буферизации потока. Внутренний класс ScaleListener обрабатывает жест масштабирования «pinch zoom». Заголовок экранов я вынес в отдельный файл, включаемый в разметку экранов директивой include: В результате приложение получилось если не максимально простым, то, по крайне мере, максимально близким к этому:) Хотя нативные приложения имеют минимальный размер (и максимальную производительность), использование библиотеки libvlc-all увеличивает результирующий размер сборки:  Как видите, поддержка каждой платформы съедает около 19 МБ дискового пространства. Такова цена «всеядности» VLC, который работает почти всегда и везде и воспроизводит все, что вообще может воспроизводиться. Поскольку мне нужно было сделать максимально просто, в эту статью не вошла реализация перемещения увеличенного изображение – это требует некоторого количества арифметических вычислений, не добавляющих понятности коду. Кроме того, чуть позже я планирую добавить поддержку воспроизведения сохраненного архива через SFTP. В результате моих исследований получилось простое, но вполне рабочее приложение, поэтому я оставлю его здесь, на Хабре. Надеюсь, кому-нибудь поможет. P.S. Времени на написание комментариев в коде не было, прошу не судить строго. Зато комментарии открыты на Хабре – добро пожаловать! Разработчик