Еще совсем недавно желание посмотреть фильм на большом экране в гостиной неизбежно разжигало диванные споры о выборе подходящего медиаплеера. По большому счету число вариантов было не столь велико (все-таки одинаковая элементная база), и победители, равно как и аутсайдеры, определялись довольно быстро. С появлением Android’а в моду вошли так называемые TV-Stick’и (или «донглы»), то есть устройства размером с флешку, имеющие на борту HDMI-разъем и мобильную ОС в придачу — этакие смартфоны без экрана.

Китайские интернет-магазины предлагают стики на любой вкус, цвет и конфигурацию за небольшие деньги (правда, в корзину также стоит добавить беспроводную клавиатуру/мышь или универсальный пульт). Омрачает всеобщую радость лишь то, что Android на экране телевизора смотрится отлично, а вот управлять им совершенно неудобно (чего стоит перемещение курсора крестовиной на пульте). Одна небезызвестная корпорация добра (Google, если что ;)) решила пойти своим путем и выпустила гаджет со схожей функциональностью, но более дружелюбный для пользователя — Chromecast. Сегодня мы рассмотрим это устройство с точки зрения программиста и даже напишем код под него.

 

Chromecast

По словам Википедии, Chromecast — цифровой медиаплеер, предназначенный для воспроизведения потокового видео- или аудиоконтента с помощью Wi-Fi из интернета либо из локальной сети. При этом никаких органов управления он не имеет, то есть, в отличие от TV-Stick’ов, требует внешнего управляющего устройства, такого как компьютер или смартфон.

Chromecast «светит» HDMI-разъемом
Chromecast «светит» HDMI-разъемом

По определению, основным источником видеоконтента для Chromecast из интернета служит YouTube. Если браузер или официальное мобильное приложение обнаружат в сети работающий Chromecast, в интерфейс будет добавлена соответствующая кнопка, запускающая трансляцию на большой экран. Помимо YouTube, донгл от Google поддерживают такие сервисы, как Twitch, Amediateka, Deezer, и многие другие.

Трансляция с YouTube
Трансляция с YouTube

Что касается локальных файлов, то самый простой способ воспроизвести что-либо медийное через Chromecast — это перетащить видео- или аудиофайл в окошко браузера Chrome с установленным плагином Google Cast и нажать все ту же заветную кнопочку. И никаких плясок с бубном вокруг DLNA-устройств!

Запуск трансляции из браузера Chrome
Запуск трансляции из браузера Chrome

У этой простоты, правда, есть и неприятная сторона — Chromecast поддерживает далеко не все кодеки сжатия. Например, видео в контейнерах MKV и MP4 воспроизводится уверенно, тогда как AVI устройство не переваривает. В этом случае потребуется установить транскодер (например, BubbleUPnP Server), конвертирующий видео на лету, или использовать специализированный сервис (например, Videostream). Вообще, «Хакер» уже касался этой темы, разбирая все мыслимые и немыслимые лайфхаки Chromecast, — повторяться не будем, а перейдем непосредственно к нашей теме.

INFO

Если ты не в курсе, Chromecast дешев по сравнению с аналогами: цены на версию 2013 года начинаются с 2000 ₽, а версию 2015 года можно найти за 3000 ₽. На «Авито» цены еще ниже — посмотри, если не боишься связываться с б/у.

 

Постановка задачи

Итак, допустим, у нас есть внушительная коллекция фильмов (разумеется, не торренты и не мелодрамы с отважными немецкими сантехниками ;), а, скажем, лицензионные... мультики), которую мы хотим через Chromecast посмотреть на большом экране.

Так как Chromecast работает с потоковым видео, нам потребуется поднять локальный сервер на компьютере (подойдет даже такой легковесный, как HTTP File Server). В моем случае ничего настраивать вообще не пришлось: весь видеоконтент хранится на домашнем сетевом диске (NAS) с уже работающим сервером на базе Apache. Будем считать, что этот этап мы успешно прошли и новоиспеченный сервер отдает видео по ссылкам вида:

http://192.168.1.2/01 - What’s the Score, Pooh.mkv
http://192.168.1.2/02 - Boo to You Too! Winnie the Pooh.mkv
http://192.168.1.2/03 - Pooh Day Afternoon.mkv

и так далее (кстати, вместо IP можно использовать сетевое имя).

 

Cast SDK vs. Cast Companion Library

Для работы с Chromecast существует официальный инструмент — Cast SDK, анонсированный Google сразу же после выхода донгла на рынок. SDK включает объемную документацию и изобилует множеством примеров на все случаи тележизни. С другой стороны, инструментарий страдает некоторой «низкоуровневостью» и предполагает написание (копи-пасты, чего уж там) объемного кода взаимодействия с устройством — обнаружение в сети, регистрация, настройка, колбэки и прочее, что несколько выходит за рамки простого воспроизведения медиаконтента на экране телевизора.

К счастью, Google не была бы Google, если бы и здесь не решила облегчить жизнь Android-разработчикам, выпустив вполне удобную обертку в виде библиотеки Cast Companion Library. Говоря избитой фразой, теперь разработчики могут сосредоточиться на написании функциональности приложения, а не обвязки.

Нам Cast Companion Library подходит как нельзя лучше, так что не забудь скачать pdf-мануал и скорее запускай Android Studio.

 

Sender и Receiver Applications

Приложения для Chromecast бывают двух видов: Sender (передатчики) и Receiver (приемники).

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

Передатчики (Sender Application) настраивают контент для воспроизведения на ТВ посредством ссылок или потокового видео (аудио), формируют очередь, контролируют работу Chromecast. Официально поддерживаются мобильными ОС Android и iOS (Windows Mobile опять «где-то рядом») и платформой Chrome.

В статье рассматривается создание Sender Application.

 

Шаг 1. Создаем проект

Для нашей задачи создадим новый проект, состоящий из одной активности и плавающей кнопки (FAB). Вся суть интерфейса представлена на скрине ниже (традиционно — исходный код доступен по ссылке).

В файле build.gradle подключаем Companion Library:

compile 'com.google.android.libraries.cast.companionlibrary:ccl:2.9.1'

Кроме того, понадобятся библиотеки совместимости:

compile 'com.android.support:appcompat-v7:24.2.1'
compile 'com.android.support:support-v4:24.2.1'
Пока ничто не указывает на Chromecast
Пока ничто не указывает на Chromecast

Традиционно для работы в сети спрашиваем у пользователя разрешение:

<uses-permission android:name="android.permission.INTERNET" />
 

Шаг 2. Настраиваем VideoCastManager

Всей логикой взаимодействия с Chromecast занимается специальный класс VideoCastManager. Он же отслеживает состояние устройства в сети, дергает колбэки — в общем, сильно упрощает жизнь программисту своим API. Как следует из документации, является почетным синглтоном. Чтобы не зависеть от жизненного цикла активности или фрагмента, имеет смысл инициализировать VideoCastManager в объекте Application, также одиночке:

public class MyApplication extends Application {
    private static final String applicationId = CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID;

    @Override
    public final void onCreate() {
        super.onCreate();

        CastConfiguration options = new CastConfiguration.Builder(applicationId)
            .enableAutoReconnect()
            .enableDebug()
            .enableLockScreen()
            .enableWifiReconnection()
            .enableNotification()
            .addNotificationAction(CastConfiguration.NOTIFICATION_ACTION_PLAY_PAUSE, true)
            .addNotificationAction(CastConfiguration.NOTIFICATION_ACTION_DISCONNECT, true)
            .build();

        VideoCastManager.initialize(this, options);
    }
    ...
}

Класс CastConfiguration определяет конфигурацию работы приложения с Chromecast. В частности:

enableAutoReconnect восстанавливает соединение после разрыва, а enableWifiReconnection() — после смены Wi-Fi-сети;
enableDebug включает отладочный режим (требует регистрации устройства — см. врезку);
enableLockScreen разрешает управление воспроизведением на экране блокировки (без ввода пароля);
enableNotification — то же самое, но в области оповещений.

В последнем случае addNotificationAction определяет те органы управления (кнопки), которые пользователь увидит в шторке смартфона. В нашем случае это пауза/воспроизведение (NOTIFICATION_ACTION_PLAY_PAUSE) и отключение от девайса (NOTIFICATION_ACTION_DISCONNECT). Всего же определены шесть стандартных действий: Play/Pause, Next, Previous, Fast Forward, Rewind и Disconnect.

Описание остальных свойств и констант можно подсмотреть в исходнике библиотеки на GitHub’е.

Отдельно стоит упомянуть параметр applicationId, передаваемый в конструктор CastConfiguration. Это, как следует из названия, уникальный идентификатор приложения. Получить его можно, отправив СМС на короткий номер Google ;), или посетив небезызвестную консоль разработчика (для справки — пошаговая инструкция).

Но делать это совсем не обязательно, если приложение использует стандартный проигрыватель (так называемый Default Media Receiver) со стандартным интерфейсом пользователя (примерно как на YouTube). А вот если ты захочешь при воспроизведении лицезреть свой логотип или, например, нарисовать свои скевоморфные кнопочки управления — регистрация обязательна. В нашем случае возможностей стандартного проигрывателя хватает с лихвой, поэтому смело передаем CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID и двигаемся дальше.

 

Регистрация устройства

По умолчанию Chromecast не предназначен для полного цикла разработки и отладки приложений, поэтому не делится никакой отладочной информацией с программистом. Для включения режима «бога» устройство нужно зарегистрировать, выполнив ряд нехитрых манипуляций:

  1. Настроить устройство на работу в сети по Wi-Fi, а на настольном компьютере установить последнюю версию браузера Chrome с расширением Google Cast.
  2. Зайти в консоль разработчика (или зарегистрироваться, если еще не сделал этого).
  3. Нажать Add New Device и указать серийный номер устройства (этот номер можно видеть при запуске трансляции через плагин — Cast: example.com to ХХХХ или на экране телевизора).
  4. Ввести имя устройства (можно неприличное ;)).
  5. Подождать примерно пятнадцать минут и убедиться, что статус устройства изменился на Ready for Testing.
  6. Перезапустить Chromecast, отсоединив его от розетки на пару секунд.

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

 

Шаг 3. Рисуем активность

Наша единственная активность будет унаследована от класса AppCompatActivity, дабы немного порадовать владельцев не совсем новых (или совсем не новых) девайсов. Основной код приложения представлен ниже:

public class Main extends AppCompatActivity {
    private VideoCastManager videoCastManager;
    private VideoCastConsumer videoCastConsumer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        videoCastManager = VideoCastManager.getInstance();

        setupQueue();
    }

    @Override
    protected void onResume() {
        if (videoCastManager != null) {
            videoCastManager.addVideoCastConsumer(videoCastConsumer);
            videoCastManager.incrementUiCounter();
        }
        super.onResume();
    }

    @Override
    protected void onPause() {
        videoCastManager.decrementUiCounter();
        videoCastManager.removeVideoCastConsumer(videoCastConsumer);
        super.onPause();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        videoCastManager.addMediaRouterButton(menu, R.id.media_route_menu_item);
        return true;
    }
}

В методе onCreate нестандартными будут только две последние строки: первая получает ссылку на синглтон VideoCastManager, созданный на первом шаге, а setupQueue готовит очередь из файлов для воспроизведения. Формированием этой очереди мы займемся немного позднее, а пока рассмотрим второй по важности объект библиотеки — VideoCastConsumer.

VideoCastConsumer представляет собой интерфейс для работы с классом VideoCastManager, ориентированный на различные медиапотоки — видео или аудио. В оппозиции к нему находится класс DataCastConsumer, облегчающий работу с данными (да, для Chromecast существует десяток различных игр и программ). Казалось бы, бери VideoCastConsumer и используй на здоровье, но стоит только взглянуть на количество требуемых к реализации колбэков, чтобы испортить себе настроение на весь день (как говорится, если есть желание поработать — сядь отдохни, и все пройдет). К счастью, Cast Companion Library по доброте душевной дарит нам более удобные классы-обертки — VideoCastConsumerImpl и DataCastConsumerImpl. Используя их, мы переопределим только те методы, которые нам действительно понадобятся для передачи видеофайла на Chromecast.

Но вернемся к нашему коду. В методе onResume мы, во-первых, передадим объект VideoCastConsumer, содержащий очередь из медиафайлов (см. следующий шаг), менеджеру VideoCastManager; во-вторых, увеличим на единицу счетчик ссылок — incrementUiCounter. Последнее необходимо для корректной работы библиотеки в условиях жизненного цикла нашей активности. Например, если активность уходит на задний план, для экономии заряда батареи нет смысла искать в сети устройства Chromecast, и наоборот — если активность выходит вперед, показывать нотификации в шторке уже не нужно. Очевидно, в onPause следует выполнить противоположные действия, то есть отключить VideoCastConsumer и уменьшить счетчик ссылок.

Магическая кнопка Cast обычно располагается на панели Toolbar в верхней части экрана. Библиотека предоставляет стандартный внешний вид и поведение этой кнопочки совершенно даром: в onCreateOptionsMenu вызываем addMediaRouterButton, указав в качестве идентификатора кнопки R.id.media_route_menu_item. При этом разметка меню должна выглядеть примерно так:

<menu
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context="com.example.ccx.Main" >

    <item
        android:id="@+id/media_route_menu_item"
        android:title="@string/action_cast"
        app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider"
        app:showAsAction="always"/>
    ...
</menu>

Если сейчас запустить проект и включить Chromecast, то уже можно наблюдать появление кнопки Cast, последующий диалог выбора устройства для воспроизведения, а также симпатичную анимацию. Вся эта функциональность скрыта под капотом библиотеки Cast Companion Library.

Устройство Chromecast обнаружено
Устройство Chromecast обнаружено

Есть контакт!
Есть контакт!

 

Шаг 4. Встаем в очередь

Мы подошли к самой увлекательной части повествования — к формированию очереди воспроизведения медиафайлов. У нас этим будет заниматься метод setupQueue, листинг которого представлен ниже:

private void setupQueue() {
    videoCastConsumer = new VideoCastConsumerImpl(){

        @Override
        public void onApplicationConnected(ApplicationMetadata appMetadata, String sessionId, boolean wasLaunched) {
            invalidateOptionsMenu();

            MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
            movieMetadata.putString(MediaMetadata.KEY_TITLE, "title");
            movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, "subtitle");
            movieMetadata.addImage(new WebImage(Uri.parse("http://192.168.1.2/folder.jpg")));

            MediaInfo info = new MediaInfo.Builder("http://192.168.1.2/03 - Pooh Day Afternoon.mkv")
                    .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
                    .setContentType("video/mkv")
                    .setMetadata(movieMetadata)
                    .build();

            MediaQueueItem item = new MediaQueueItem.Builder(info).build();

            try {
                videoCastManager.queueLoad(new MediaQueueItem[]{item}, 0, MediaStatus.REPEAT_MODE_REPEAT_OFF, null);
            } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
                e.printStackTrace();
            }
        }
        ...
    };
}

Как и договаривались, будем использовать класс VideoCastConsumerImpl, в котором переопределим метод onApplicationConnected, вызываемый при подключении к Chromecast, — в простейшем случае этого достаточно.

При воспроизведении видео на экране телевизора в начальный момент появляется титр с названием медиафайла и небольшим изображением (например, с обложкой или постером фильма). Эта информация (если она нужна, разумеется) содержится в метаданных каждого элемента очереди. Класс MediaMetadata включает в себя такие пары «ключ — значение», как KEY_TITLE — название (или заголовок) элемента и KEY_SUBTITLE — опциональный подзаголовок. Кроме того, метод addImage добавляет картинку элементу, а WebImage загружает ее по сети (как вариант, можно передать картинку из ресурсов приложения).

Помимо метаданных, элемент очереди должен определять тип файла и ссылку для воспроизведения. Эту роль выполняет специальный билдер класса MediaInfo, единственным параметром которого является адрес медиапотока (напомню, у нас он локальный вида http://192.168.1.2/some_file.mkv). Раз уж мы используем файлы формата Matroska (.mkv), указываем это в setContentType("video/mkv").

Метод setStreamType задает тип медиапотока: STREAM_TYPE_BUFFERED или STREAM_TYPE_LIVE. Первый предполагает трансляцию готового потока (то есть фактически файла), а второй — «живую» трансляцию, то есть формируемую на лету. Очевидно, нам подходит MediaInfo.STREAM_TYPE_BUFFERED.

Когда вся информация собрана и подготовлена, создаем элемент очереди, вызывая MediaQueueItem.Builder(info).build(). Для примера я сгенерировал только один элемент, содержащий видеофайл и метаданные, остальные формируются аналогично.

Наконец, с помощью videoCastManager.queueLoad() загружаем очередь, задав первым параметром массив из готовых элементов (у нас он только один), а предпоследним — тип воспроизведения: REPEAT_MODE_REPEAT_OFF — без повтора, REPEAT_MODE_REPEAT_ALL — повтор всей очереди, REPEAT_MODE_REPEAT_SINGLE — бесконечный повтор текущего элемента, REPEAT_MODE_REPEAT_ALL_AND_SHUFFLE — повтор очереди с перемешиванием элементов после воспроизведения последнего.

Для остановки видео и отключения от Chromecast в интерфейсе приложения мы предусмотрели плавающую кнопку (FAB), единственная функция которой — вызов метода disconnectDevice:

private void disconnectDevice() {
   videoCastManager.disconnect();
}

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

 

Шаг 5. Stack Overflow

Как говорится, какой проект без Stack Overflow? В нашем случае неудобства начинаются при попытке приостановить воспроизведение мультика. Использование кнопки «Пауза» в шторке, равно как и на экране блокировки, не приводит к сколько-нибудь значимому результату. Официальная документация Companion Library также молчит. К счастью, на Stack Overflow быстро нашелся ответ. Чтобы все заработало, необходимо подключить к проекту специальный широковещательный приемник (что, в общем-то, напрашивалось, если ты читаешь рубрику «Кодинг» ;)), пару сервисов и активность управления. Весь перечисленный багаж уже есть в библиотеке, просто добавь в манифест AndroidManifest.xml строки:

<receiver android:name="com.google.android.libraries.cast.companionlibrary.remotecontrol.VideoIntentReceiver" >
    <intent-filter>
        <action android:name="android.media.AUDIO_BECOMING_NOISY" />
        <action android:name="android.intent.action.MEDIA_BUTTON" />
        <action android:name="com.google.android.libraries.cast.companionlibrary.action.toggleplayback" />
        <action android:name="com.google.android.libraries.cast.companionlibrary.action.stop" />
    </intent-filter>
</receiver>

<service android:name="com.google.android.libraries.cast.companionlibrary.notification.VideoCastNotificationService"
    android:exported="false" >
    <intent-filter>
        <action android:name="com.google.android.libraries.cast.companionlibrary.action.toggleplayback" />
        <action android:name="com.google.android.libraries.cast.companionlibrary.action.stop" />
        <action android:name="com.google.android.libraries.cast.companionlibrary.action.notificationvisibility" />
    </intent-filter>
</service>

<service android:name="com.google.android.libraries.cast.companionlibrary.cast.reconnection.ReconnectionService"/>
<activity android:name="com.google.android.libraries.cast.companionlibrary.cast.player.VideoCastControllerActivity"/>

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

Смотрим Title (в ролях: Subtitle)...
Смотрим Title (в ролях: Subtitle)...

...в шторке
...в шторке

...на экране блокировки
...на экране блокировки

 

Выводы

Я приобретал Chromecast в качестве игрушки (для гика, если угодно), но как-то внезапно этот девайс стал просто незаменимым в быту: запустить мультик ребенку, или трансляцию концерта на большом экране, или видеоролик из памяти смартфона — все это легко и доступно для всех, и к этому привыкаешь очень быстро. Вдвойне приятно, что с устройством можно работать: есть SDK и Cast Companion Library, есть комьюнити и даже отдельный раздел на Stack Overflow. Сегодня мы рассмотрели простейший вариант использования Chromecast, можно сказать — скелет приложения, но ничто не мешает тебе дополнить его нужной функциональностью.

WWW

Исходный код проекта доступен по ссылке

INFO

Статья о хитростях работы с Chromecast

Оставить мнение