Меню

Как работает хромкаст – Все, что Вы хотели знать о Chromecast или как сделать обычный телевизор чуть умнее за 35$

Chromecast — Википедия

Материал из Википедии — свободной энциклопедии

Текущая версия страницы пока не проверялась опытными участниками и может значительно отличаться от версии, проверенной 17 августа 2015; проверки требуют 12 правок. Текущая версия страницы пока не проверялась опытными участниками и может значительно отличаться от версии, проверенной 17 августа 2015; проверки требуют 12 правок.

Chromecast — цифровой медиаплеер (сетевой медиаплеер) компании Google, предназначенный для воспроизведения потокового видео- или аудиоконтента с помощью Wi-Fi из Интернета либо из локальной сети. Устройство было представлено 24 июля 2013 года в США по цене 35 $.

Chromecast построен на чипе Marvell 88DE3005 (Armada 1500-mini), в котором реализовано аппаратное декодирование форматов сжатия видео VP8 и H.264. Беспроводная связь реализована на чипе AzureWave NH-387 Wi-Fi, который поддерживает стандарты Wi-Fi 802.11 b/g/n (на частоте 2,4 ГГц). Устройство имеет 512 МБ оперативной памяти Micron DDR3L и 2 ГБ флэш-памяти. Длина устройства — 72 мм.

Chromecast работает на упрощенной версии Chrome OS. Обновления операционной системы загружаются автоматически, без предварительного уведомления. Для передачи медиа-контента Chromecast использует протокол DIAL, разработанный совместными усилиями компаний Netflix и YouTube.

Chromecast подключается к HDMI-порту телевизора, питание подается путём подключения через порт micro-USB к внешнему адаптеру питания либо к USB-порту телевизора. Устройство подключается к домашней сети пользователя и Интернет через Wi-Fi. Для использования устройства пользователю нужно выбрать контент из веб-браузера Google Chrome на персональном компьютере или в поддерживаемом приложении на мобильном устройстве. Медиа-контент воспроизводится в потоковом режиме через Chromecast таким образом, что позволяет использовать мобильное устройство для других задач, например для ответа на входящие звонки.

Поддерживается работа через приложения для Android и iOS, а также веб-приложения, установленные в браузере Google Chrome для Windows, OS X и Chrome OS. Качество изображения зависит от вычислительной мощности компьютера. Контент, который использует плагины Silverlight и QuickTime, полностью не поддерживается.

Google выпустила средства разработки программного обеспечения «Google Cast SDK», позволяющие разработчикам сделать свои приложения совместимыми с Chromecast.

Рецензент Нилай Патель с The Verge поставил Chromecast оценку 8,5 баллов из 10 возможных, написав:

Импульсом к покупке Chromecast является возможность просто и дешево увидеть окно браузера, или весь рабочий стол на экране телевизора… Кажется, что Chromecast может на самом деле выполнить все, он имеет большой потенциал, но Google все еще предстоит проделать много работы.

Порталы TechCrunch, Gizmodo и The New York Times также дали положительные оценки устройству.

Как работает Chromecast? / Habr

Введение

Загорелся я недавно выбором беспроводного адаптера для передачи видео. Смотрел китайские адаптеры с Miracast/DLNA и, естественно, не обошел меня стороной и Chromecast. Если с такими технологиями, как WiDi, Miracast и донглами вроде AIRTAME все понятно (не требуется поддержка со стороны софта, видится системой как еще один монитор), как устроен Chromecast внутри я не был до конца уверен, как и не был уверен, подойдет ли он мне. Однако, я нашел open-source проект эмулятора Chromecast LeapCast, и решил его испробовать в работе, а также изучить протокол.
Установка и использование

Установка на ArchLinux довольно простая — достаточно установить пакет leapcast-git из AUR. Необходимо еще установить Google Chrome или Chromium, т.к. его нет в зависимостях.

Запускаем программу и видим следующую картину:

Скрытый текст
INFO:root:Starting SSDP server
INFO:root:Starting LEAP server
INFO:root:Loading Config-JSON from Google-Server
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): clients3.google.com
INFO:root:Parsing Config-JSON
INFO:root:Added edaded98-5119-4c8a-afc1-de722da03562 app
INFO:root:Added PlayMovies app
INFO:root:Added 00000000-0000-0000-0000-000000000000 app
INFO:root:Added 1812335e-441c-4e1e-a61a-312ca1ead90e app
INFO:root:Added 06ee44ee-e7e3-4249-83b6-f5d0b6f07f34 app
INFO:root:Added 2be788b9-b7e0-4743-9069-ea876d97ac20 app
INFO:root:Added GoogleSantaTracker app
INFO:root:Added 06ee44ee-e7e3-4249-83b6-f5d0b6f07f34_1 app
INFO:root:Added Pandora_App app
INFO:root:Added aa35235e-a960-4402-a87e-807ae8b2ac79 app
INFO:root:Added YouTube app
INFO:root:Added HBO_App app
INFO:root:Added TicTacToe app
INFO:root:Added Revision3_App app
INFO:root:Added Songza_App app
INFO:root:Added a7f3283b-8034-4506-83e8-4e79ab1ad794_2 app
WARNING:root:Didn't add Netflix because it has no URL!
INFO:root:Added GoogleMusic app
INFO:root:Added 18a8aeaa-8e3d-4c24-b05d-da68394a3476_1 app
INFO:root:Added Post_TV_App app
INFO:root:Added ChromeCast app
INFO:root:Added Hulu_Plus app
INFO:root:Added GoogleCastSampleApp app
INFO:root:Added GoogleCastPlayer app
INFO:root:Added Fling app

Открываем любое приложение на андроид-смартфоне, поддерживающее Chromecast (например, YouTube), и можем подключиться к Leapcast:

В это же время в логе:
Скрытый текст
INFO:tornado.access:200 GET /ssdp/device-desc.xml (192.168.0.105) 2.22ms
INFO:tornado.access:200 GET /apps/YouTube (192.168.0.105) 3.18ms
INFO:tornado.access:200 GET /apps/YouTube (192.168.0.105) 3.67ms
INFO:tornado.access:201 POST /apps/YouTube (192.168.0.105) 5.29ms
INFO:root:Channel for app set to <leapcast.services.websocket.ServiceChannel object at 0x195f690>

Ого, работает! Как же оно работает?
Протокол

Для поиска и первоначального управления (запуск нужного приложения, получение параметров) используется протокол DIAL — некий симбиоз SSDP для поиска и HTTP REST + XML для управления. SSDP, возможно, знаком вам, если вы использовали UPnP и DLNA, а REST-часть достаточно простая.
  1. При запуске LeapCast, происходит подключение к серверу Google с целью получения списка официально поддерживаемых приложений в формате json. В этом файле описываются всякие параметры для каждого приложения: по какой ссылке отправлять запрос, с какими параметрами запускать и так далее.
  2. LeapCast анонсирует себя по SSDP.
  3. Android-клиент (в моем случае YouTube) находит LeapCast по SSDP, получает о нем информацию (обращаясь к /ssdp/device-desc.xml), далее убеждается, что устройство работает с YouTube (запрашивая /apps/YouTube), подключается к серверу YouTube, забирает у него pairing token, отправляет его LeapCast в POST-запросе (на все тот же /apps/YouTube).
  4. LeapCast запускает Chrome/Chromium-приложение по ссылке www.youtube.com/tv?${POST_DATA}
  5. Далее, для связи Android и LeapCast используется протокол RAMP (Google удалил спецификацию со своего сайта). Он описывает взаимодействие между устройствами посредством пересылки json-сообщений внутри WebSocket.
  6. Для завершения взаимодействия, Android посылает DELETE-запрос на Leapcast на тот же URL

В случае с YouTube, как я понимаю, WebSocket поднимается на стороне серверов YouTube, однако, другие приложения (например, Google Music) требуют LeapCast создать WebSocket, и управление происходит уже внутри вашей беспроводной сети.

Следует заметить, если приложение, которые поддерживает Chromecast, не было одобрено в Google (и, соответственно, записи о нем в json нет), то пользователи смогут им воспользоваться только войдя в режим разработчика и загрузив информацию о нем. Достаточно печально.

Заключение

В общем-то, с LeapCast вы можете превратить любое устройство, способное запускать Python + Chrome в почти полноценный аналог Chromecast, если, по какой-то причине вы не можете купить и так дешевое устройство. А я так и не надумал покупать его, т.к. не работает без интернета, требуется одобрение приложений Google, да и вообще.

Смотрю и слушаю где хочу. Интегрируем Chromecast в Android-приложение / MobileUp corporate blog / Habr

На улице я часто слушаю аудиокниги и подкасты со смартфона. Когда прихожу домой, мне хочется продолжить слушать их на Android TV или Google Home. Но далеко не все приложения поддерживают Chromecast. А было бы удобно.

По статистике Google за последние 3 года, количество девайсов на Android TV увеличилось в 4 раза, а число партнеров-производителей уже превысило сотню: «умные» телевизоры, колонки, TV-приставки. Все они поддерживают Chromecast. Но в маркете ещё много приложений, которым явно не хватает интеграции с ним.

В этой статье я хочу поделиться своим опытом интеграции Chromecast в Android-приложение для воспроизведения медиа-контента.


Если вы впервые слышите слово «Chromecast», то постараюсь вкратце рассказать. С точки зрения пользования, это выглядит примерно так:


  1. Пользователь слушает музыку или смотрит видео через приложение или веб-сайт.
  2. В локальной сети появляется Chromecast-девайс.
  3. В интерфейсе плеера должна появиться соответствующая кнопка.
  4. Нажав её, пользователь выбирает нужный девайс из списка. Это может быть Nexus Player, Android TV или «умная» колонка.
  5. Дальше воспроизведение продолжается именно с этого девайса.

Технически происходит примерно следующее:


  1. Google Services отслеживают наличие Chromecast девайсов в локальной сети посредством бродкастинга.
  2. Если к вашему приложению подключен MediaRouter, то вам придёт событие об этом.
  3. Когда пользователь выбирает Cast-девайс, и подключается к нему, открывается новая медиа-сессия (CastSession).
  4. Уже в созданную сессию мы будем передавать контент для воспроизведения.
    Звучит очень просто.

У Google есть свой SDK для работы с Chromecast, но он плохо покрыт документацией, а его код обфусцирован. Поэтому многие вещи пришлось проверять методом тыка. Давайте обо всём по порядку.


Инициализация

Для начала нам надо подключить Cast Application Framework и MediaRouter:

implementation "com.google.android.gms:play-services-cast-framework:16.1.0"
implementation "androidx.mediarouter:mediarouter:1.0.0"

Затем Cast Framework должен получить идентификатор приложения (об этом позже), и типы поддерживаемого медиаконтента. То есть если у нас приложение воспроизводит только видео, то кастинг на колонку Google Home будет невозможен, и в списке девайсов её не будет. Для этого нужно создать реализацию OptionsProvider:

class CastOptionsProvider: OptionsProvider {

   override fun getCastOptions(context: Context): CastOptions {
       return CastOptions.Builder()
           .setReceiverApplicationId(BuildConfig.CHROMECAST_APP_ID)
           .build()
   }

   override fun getAdditionalSessionProviders(context: Context): MutableList<SessionProvider>? {
       return null
   }
}

И объявить его в Manifest:

<meta-data
   android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
   android:value="your.app.package.CastOptionsProvider" />

Регистрируем приложение

Чтобы Chromecast мог работать с нашим приложением, его необходимо зарегистрировать в Google Cast SDK Developers Console. Для этого потребуется аккаунт Chromecast разработчика (не путать с аккаунтом разработчика Google Play). При регистрации придётся внести разовый взнос в 5$. После публикации ChromeCast Application нужно немного подождать.
В консоли можно изменить внешний вид Cast-плеера для девайсов с экраном и посмотреть аналитику кастинга в рамках приложения.


MediaRouteFramework – это механизм, который позволяет находить все удалённые устройства воспроизведения вблизи пользователя. Это может быть не только Chromecast, но и удалённые дисплеи и колонки с использованием сторонних протоколов. Но нас интересует именно Chromecast.

В MediaRouteFramework есть View, которая отражает состояние медиароутера. Есть два способа её подключить:

1) Через меню:

<?xml version="1.0" encoding="utf-8"?>
<menu
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">
...
   <item
       android:id="@+id/menu_media_route"
       android:title="@string/cast"
       app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
       app:showAsAction="always"/>
...
</menu>

2) Через layout:

<androidx.mediarouter.app.MediaRouteButton
   android:id="@+id/mediaRouteButton"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:mediaRouteTypes="user"/>

А из кода требуется всего лишь зарегистрировать кнопку в CastButtonFactory. тогда в нее будет прокидываться текущее состояние медиароутера:

CastButtonFactory.setUpMediaRouteButton(applicationContext, view.mediaRouteButton)

Теперь, когда приложение зарегистрировано, и MediaRouter настроен, можно подключаться к ChromeCast-девайсам и открывать сессии к ним.


ChromeCast поддерживает три основных вида контента:


В зависимости от настроек плеера, типа медиаконтента и cast-девайса, интерфейс плеера может отличаться.


CastSession

Итак, пользователь выбрал нужный девайс, CastFramework открыл новую сессию. Теперь наша задача заключается в том, чтобы отреагировать на это и передать девайсу информацию для воспроизведения.
Чтобы узнать текущее состояние сессии и подписаться на обновление этого состояния, воспользуемся объектом SessionManager:

private val mediaSessionListener = object : SessionManagerListener<CastSession> {

   override fun onSessionStarted(session: CastSession, sessionId: String) {
       currentSession = session

       // Тут проверим, что мы готовы начать кастинг
       checkAndStartCasting()
   }

   override fun onSessionEnding(session: CastSession) {
       stopCasting()
   }

   override fun onSessionResumed(session: CastSession, wasSuspended: Boolean) {
       currentSession = session
       checkAndStartCasting()
   }

   override fun onSessionStartFailed(session: CastSession, p1: Int) {
       stopCasting()
   }

   override fun onSessionEnded(session: CastSession, p1: Int) {
       // do nothing
   }

   override fun onSessionResumeFailed(session: CastSession, p1: Int) {
       // do nothing
   }

   override fun onSessionSuspended(session: CastSession, p1: Int) {
       // do nothing
   }

   override fun onSessionStarting(session: CastSession) {
       // do nothing
   }

   override fun onSessionResuming(session: CastSession, sessionId: String) {
       // do nothing
   }
}

val sessionManager = CastContext.getSharedInstance(context).sessionManager
sessionManager.addSessionManagerListener(mediaSessionListener, CastSession::class.java)

А ещё можем узнать, нет ли открытой сессии в данный момент:

val currentSession: CastSession? = sessionManager.currentCastSession

У нас есть два основных условия, при которых мы можем начинать кастинг:


  1. Сессия уже открыта.
  2. Есть контент для кастинга.

При каждом из этих двух событий можем проверять состояние, и если всё в порядке, то начинать кастить.


Кастинг

Теперь, когда у нас есть что кастить и куда кастить, можем перейти к самому главному. Помимо всего прочего, у CastSession есть объект RemoteMediaClient, который отвечает за состояние воспроизведения медиаконтента. С ним и будем работать.

Создадим MediaMetadata, где будет храниться информация об авторе, альбоме и т. д. Очень похоже на то, что мы передаём в MediaSession, когда начинаем локальное воспроизведение.

val mediaMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MUSIC_TRACK
).apply {
   putString(MediaMetadata.KEY_TITLE, “In C”)
   putString(MediaMetadata.KEY_ARTIST, “Terry Riley”)
   mediaContent?.metadata?.posterUrl?.let { poster ->
       addImage(WebImage(Uri.parse(“https://habrastorage.org/webt/wk/oi/pf/wkoipfkdyy2ctoa5evnd8vhxtem.png”)))
   }
}

Параметров у MediaMetadata много, и их лучше посмотреть в документации. Приятно удивило, что можно добавить изображение не через bitmap, а просто ссылкой внутри WebImage.

Объект MediaInfo несёт информацию о метаданных контента и будет говорить о том, откуда медиаконтент брать, какого он типа, как его проигрывать:

val mediaInfo = MediaInfo.Builder(“https://you-address.com/in_c.mp3”)
   .setContentType(“audio/mp3”)
   .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
   .setMetadata(mediaMetadata)
   .build()

Напомню, что contentType – это тип контента по спецификации MIME.
Также в MediaInfo можно передать рекламные вставки:


  • setAdBreakClips – принимает список рекламных роликов AdBreakClipInfo с указанием ссылок на контент, заголовка, тайминга и временем, через которое реклама становится пропускаемой.
  • setAdBreaks – информация о разметке и тайминге рекламных вставок.

В MediaLoadOptions мы описываем то, как будем обрабатывать медиапоток (скорость, начальная позиция). Также документация говорит, что через setCredentials можно передать заголовок запроса для авторизации, но у меня запросы от Chromecast не включали в себя заявленные поля для авторизации.

val mediaLoadOptions = MediaLoadOptions.Builder()
   .setPlayPosition(position!!)
   .setAutoplay(true)
   .setPlaybackRate(playbackSpeed)
   .setCredentials(context.getString(R.string.bearer_token, authGateway.authState.accessToken!!))
   .setCredentialsType(context.getString(R.string.authorization_header_key))
   .build()

После того как всё готово, мы можем отдать все данные в RemoteMediaClient, и Chromecast начнёт воспроизведение. Важно поставить локальное воспроизведение на паузу.

val remoteMediaClient = currentSession!!.remoteMediaClient
remoteMediaClient.load(mediaInfo, mediaLoadOptions)

Обработка событий

Видео заиграло, а что дальше? Что если пользователь нажмёт паузу на телевизоре? Чтобы узнавать о событиях, происходящих со стороны Chromecast, у RemoteMediaClient есть обратные вызовы:

private val castStatusCallback = object : RemoteMediaClient.Callback() {
   override fun onStatusUpdated() {
      // check and update current state
   }
}

remoteMediaClient.registerCallback(castStatusCallback)

Узнать текущий прогресс тоже просто:

val periodMills = 1000L
remoteMediaClient.addProgressListener(
   RemoteMediaClient.ProgressListener { progressMills, durationMills ->
       // show progress in your UI
   },
   periodMills
)

В приложении, над которым я работал, уже был готовый медиаплеер. Стояла задача интегрировать в него поддержку Chromecast. В основе медиаплеера лежал State Machine, и первой мыслью было добавить новое состояние: «CastingState». Но эта идея сразу была отвергнута, потому что каждое состояние плеера отражает состояние воспроизведения, и не важно, что служит реализацией ExoPlayer или ChromeCast.
Тогда пришла идея сделать некую систему делегатов с расстановкой приоритетов и обработкой «жизненного цикла» плеера. Все делегаты могут получать события о состоянии плеера: Play, Pause и т.д. — но только ведущий делегат будет воспроизводить медиаконтент.

У нас есть примерно такой интерфейс плеера:

interface Player {

   val isPlaying: Boolean

   val isReleased: Boolean

   val duration: Long

   var positionInMillis: Long

   var speed: Float

   var volume: Float

   var loop: Boolean

   fun addListener(listener: PlayerCallback)

   fun removeListener(listener: PlayerCallback): Boolean

   fun getListeners(): MutableSet<PlayerCallback>

   fun prepare(mediaContent: MediaContent)

   fun play()

   fun pause()

   fun release()

   interface PlayerCallback {
       fun onPlaying(currentPosition: Long)
       fun onPaused(currentPosition: Long)
       fun onPreparing()
       fun onPrepared()
       fun onLoadingChanged(isLoading: Boolean)
       fun onDurationChanged(duration: Long)
       fun onSetSpeed(speed: Float)
       fun onSeekTo(fromTimeInMillis: Long, toTimeInMillis: Long)
       fun onWaitingForNetwork()
       fun onError(error: String?)
       fun onReleased()
       fun onPlayerProgress(currentPosition: Long)
   }
}

Внутри у него будет лежать State Machine с таким множеством состояний:


  • Empty — начальное состояние до инициализации.
  • Preparing — плеер инициализирует воспроизведение медиаконтента.
  • Prepared — медиаданные загружены и готовы к воспроизведению.
  • Playing
  • Paused
  • WaitingForNetwork
  • Error

Раньше каждое состояние при инициализации отдавало команду в ExoPlayer. Теперь оно будет отдавать команду в список Playing-делегатов, и «Ведущий» делегат сможет его обработать. Поскольку делегат реализует все функции плеера, то его тоже можно наследовать от интерфейса плеера и при необходимости использовать отдельно. Тогда абстрактный делегат будет выглядеть так:

abstract class PlayingDelegate(
   protected val playerCallback: Player.PlayerCallback,
   var isLeading: Boolean = false
) : Player {

   fun setIsLeading(isLeading: Boolean, positionMills: Long, isPlaying: Boolean) {
       this.isLeading = isLeading

       if (isLeading) {
           onLeading(positionMills, isPlaying)
       } else {
           onDormant()
       }
   }

   final override fun addListener(listener: Player.PlayerCallback) {
       // do nothing
   }

   final override fun removeListener(listener: Player.PlayerCallback): Boolean {
       return false
   }

   final override fun getListeners(): MutableSet<Player.PlayerCallback> {
       return mutableSetOf()
   }

   /**
    * Если сеть вернулась
    */
   open fun netwarkIsRestored() {
       // do nothing
   }

   /**
    * Делегат переведен в ведущее состояние
    */
   abstract fun onLeading(positionMills: Long, isPlaying: Boolean)

   /**
    * Делегат переведен в состояние бездействия
    */
   abstract fun onIdle()

   /**
    * Вызывается на этапе инициализации плеера.
    * Если делегат готов к ведению воспроизведения,
    * то плеер может передать эту ответственность ему.
    */
   abstract fun readyForLeading(): Boolean
}

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


ChromeCastDelegate.kt
class ChromeCastDelegate(
    private val context: Context,
    private val castCallback: ChromeCastListener,
    playerCallback: Player.PlayerCallback
) : PlayingDelegate(playerCallback) {

    companion object {
        private const val CONTENT_TYPE_VIDEO = "videos/mp4"
        private const val CONTENT_TYPE_AUDIO = "audio/mp3"
        private const val PROGRESS_DELAY_MILLS = 500L
    }

    interface ChromeCastListener {

        fun onCastStarted()

        fun onCastStopped()
    }

    private var sessionManager: SessionManager? = null
    private var currentSession: CastSession? = null
    private var mediaContent: MediaContent? = null

    private var currentPosition: Long = 0

    private val mediaSessionListener = object : SessionManagerListener<CastSession> {
        override fun onSessionStarted(session: CastSession, sessionId: String) {
            currentSession = session
            castCallback.onCastStarted()
        }

        override fun onSessionEnding(session: CastSession) {
            currentPosition = session.remoteMediaClient?.approximateStreamPosition
                ?: currentPosition
            stopCasting()
        }

        override fun onSessionResumed(session: CastSession, wasSuspended: Boolean) {
            currentSession = session
            castCallback.onCastStarted()
        }

        override fun onSessionStartFailed(session: CastSession, p1: Int) {
            stopCasting()
        }

        override fun onSessionEnded(session: CastSession, p1: Int) {
            // do nothing
        }

        override fun onSessionResumeFailed(session: CastSession, p1: Int) {
            // do nothing
        }

        override fun onSessionSuspended(session: CastSession, p1: Int) {
            // do nothing
        }

        override fun onSessionStarting(session: CastSession) {
            // do nothing
        }

        override fun onSessionResuming(session: CastSession, sessionId: String) {
            // do nothing
        }
    }

    private val castStatusCallback = object : RemoteMediaClient.Callback() {
        override fun onStatusUpdated() {
            if (currentSession == null) return
            val playerState = currentSession!!.remoteMediaClient.playerState

            when (playerState) {
                MediaStatus.PLAYER_STATE_PLAYING -> playerCallback.onPlaying(positionInMillis)
                MediaStatus.PLAYER_STATE_PAUSED -> playerCallback.onPaused(positionInMillis)
            }
        }
    }

    private val progressListener = RemoteMediaClient.ProgressListener { progressMs, durationMs ->
        playerCallback.onPlayerProgress(progressMs)
    }

    // Playing delegate

    override val isReleased: Boolean = false
    override var loop: Boolean = false

    override val isPlaying: Boolean
        get() = currentSession?.remoteMediaClient?.isPlaying ?: false

    override val duration: Long
        get() = currentSession?.remoteMediaClient?.streamDuration ?: 0

    override var positionInMillis: Long
        get() {
            currentPosition = currentSession?.remoteMediaClient?.approximateStreamPosition
                ?: currentPosition
            return currentPosition
        }
        set(value) {
            currentPosition = value
            checkAndStartCasting()
        }

    override var speed: Float = SpeedProvider.default()
        set(value) {
            field = value
            checkAndStartCasting()
        }

    override var volume: Float
        get() = currentSession?.volume?.toFloat() ?: 0F
        set(value) {
            currentSession?.volume = value.toDouble()
        }

    override fun prepare(mediaContent: MediaContent) {

        sessionManager = CastContext.getSharedInstance(context).sessionManager
        sessionManager?.addSessionManagerListener(mediaSessionListener, CastSession::class.java)
        currentSession = sessionManager?.currentCastSession

        this.mediaContent = mediaContent

        playerCallback.onPrepared()
    }

    override fun play() {
        if (isLeading) {
            currentSession?.remoteMediaClient?.play()
        }
    }

    override fun pause() {
        if (isLeading) {
            currentSession?.remoteMediaClient?.pause()
        }
    }

    override fun release() {
        stopCasting(true)
    }

    override fun onLeading(positionMills: Long, isPlaying: Boolean) {
        currentPosition = positionMills
        checkAndStartCasting()
    }

    override fun onIdle() {
        // TODO
    }

    override fun readyForLeading(): Boolean {
        return currentSession != null
    }

    // internal
    private fun checkAndStartCasting() {
        if (currentSession != null && mediaContent?.metadata != null && isLeading) {

            val mediaMetadata = MediaMetadata(getMetadataType(mediaContent!!.type)).apply {
                putString(MediaMetadata.KEY_TITLE, mediaContent?.metadata?.title.orEmpty())
                putString(MediaMetadata.KEY_ARTIST, mediaContent?.metadata?.author.orEmpty())
                mediaContent?.metadata?.posterUrl?.let { poster ->
                    addImage(WebImage(Uri.parse(poster)))
                }
            }

            val mediaInfo = MediaInfo.Builder(mediaContent!!.contentUri.toString())
                .setContentType(getContentType(mediaContent!!.type))
                .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
                .setMetadata(mediaMetadata)
                .build()

            val mediaLoadOptions = MediaLoadOptions.Builder()
                .setPlayPosition(currentPosition)
                .setAutoplay(true)
                .setPlaybackRate(speed.toDouble())
                .build()

            val remoteMediaClient = currentSession!!.remoteMediaClient
            remoteMediaClient.unregisterCallback(castStatusCallback)
            remoteMediaClient.load(mediaInfo, mediaLoadOptions)
            remoteMediaClient.registerCallback(castStatusCallback)
            remoteMediaClient.addProgressListener(progressListener, PROGRESS_DELAY_MILLS)
        }
    }

    private fun stopCasting(removeListener: Boolean = false) {
        if (removeListener) {
            sessionManager?.removeSessionManagerListener(mediaSessionListener, CastSession::class.java)
        }
        currentSession?.remoteMediaClient?.unregisterCallback(castStatusCallback)
        currentSession?.remoteMediaClient?.removeProgressListener(progressListener)
        currentSession?.remoteMediaClient?.stop()
        currentSession = null

        if (isLeading) {
            castCallback.onCastStopped()
        }
    }

    private fun getContentType(mediaType: MediaContent.Type) = when (mediaType) {
        MediaContent.Type.AUDIO -> CONTENT_TYPE_AUDIO
        MediaContent.Type.VIDEO -> CONTENT_TYPE_VIDEO
    }

    private fun getMetadataType(mediaType: MediaContent.Type) = when (mediaType) {
        MediaContent.Type.AUDIO -> MediaMetadata.MEDIA_TYPE_MUSIC_TRACK
        MediaContent.Type.VIDEO -> MediaMetadata.MEDIA_TYPE_MOVIE
    }
}

Прежде чем отдать команду о воспроизведении, нам надо определиться с ведущим делегатом. Для этого они добавляются в порядке приоритета в плеер, и каждый из них может отдавать состояние своей готовности в методе readyForLeading(). Полный код примера можно увидеть на GitHub.


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

Что такое Chromecast: беспроводное потоковое воспроизведение на ТВ

Автор Исхаков Максим На чтение 4 мин. Просмотров 47 Опубликовано

Что такое Chromecast: беспроводное потоковое воспроизведение на ТВЧто такое Chromecast: беспроводное потоковое воспроизведение на ТВ

Устройство выглядит несколько загадочно: в первой версии оно было похоже на флэшку большого размера, но с HDMI вместо USB, а в последней версии — на округлый диск, свисающий с тыльной стороны телевизора. Внутри расположено оборудование, которое обеспечивает телевизору изображение через Wi-Fi. Спецификация может сбить с толку, но ее достаточно, потому что вы найдете здесь все необходимое для беспроводной передачи данных, подключения контроллера по Bluetooth и плавного воспроизведения FullHD контента со скоростью 60 к/с (4K Ultra версия).

Что такое Chromecast и откуда он взялся?

Chromecast был представлен компанией Google. Можно сказать, что Google Chromecast является духовным наследником другого проекта американской компании — медиаплеера Nexus Q, который оказался не очень удачным устройством. Завоевать рынок он не смог из-за цены. Chromecast 3 стоит значительно дешевле, имеет гораздо больше возможностей, чем может показаться на первый взгляд.

Для чего используется Chromecast?

Для чего используется Chromecast?Для чего используется Chromecast?

Chromecast — это устройство, которое при правильной настройке подключается к сети Wi-Fi и позволяет осуществлять беспроводную потоковую передачу видео с компьютера через браузер Google Chrome, телефон Android или iOS и не только. На большом экране вы сможете просматривать YouTube, Netflix, слушать музыку (включая Google Play), все это не составит никакой проблемы. Со смартфона можно переключиться на выбранные приложения на ТВ экране в вашей гостиной.

Большим преимуществом гаджета от Google является также взаимодействие с беспроводными динамиками, которые можно подключить даже в группы, чтобы одновременно воспроизводить музыку во всей квартире или доме. Chromecast также совместим с Google Assistant (через динамики Google Home), который позволяет включать и выключать телевизор, используя голосовые команды или воспроизводить любимые композиции и сериалы в таких сервисах, как YouTube и Netflix.

Как работает Chromecast?

Chromecast подключается к телевизору через HDMI. Кроме того, ему необходим отдельный источник питания, который предоставляется Micro-USB-портом и подключается к USB-порту телевизора или к источнику питания с розеткой. При правильной настройке, Chromecast может работать двумя способами. С одной стороны, устройство является посредником между, например, компьютером или смартфоном и телевизором, где видео передается по домашней сети Wi-Fi с помощью облака. С другой стороны, компьютер может запрограммировать Chromecast, какие именно материалы он будет загружать из интернета для просмотра позже. Так, например, это работает с YouTube, благодаря чему вы можете заблокировать экран смартфона и фильм будет воспроизводиться на экране телевизора.

На видео: Обзор Google Chromecast 2018. Что он умеет?

Аппаратные и программные требования

Если вы хотите транслировать потоковое содержимое с компьютера, он должен быть оснащен как минимум процессором Intel Core i5 второго поколения или эквивалентным ему. Для более старых версий качество передаваемого видео и аудио может значительно ухудшиться.
Chromecast также работает с устройствами Chromebook 2, такими как Pixel и Samsung Chromebook 2, а также со всеми процессорами Intel Haswell. Для работы с устройством достаточно браузера Chrome или браузера с движком Chromium (Opera, Vivaldi).

Для мобильных устройств необходимо установить приложение Chromecast. Смартфон должен работать под управлением ОС Android версии 5.0 (и выше) или iOS версии 10.1 или выше.

Что предлагает Chromecast?

Chromecast — это в первую очередь предложение, адресованное пользователям, у которых нет Smart TV с установленными приложениями, такими как YouTube или VOD. Благодаря простому устройству от Google, любой телевизор с разъемом HDMI может стать интеллектуальным, воспроизводя видео с самых популярных в мире видеосервисов, браузера Chrome или магазина Google Play. Chromecast также будет интересен людям, у которых компьютер расположен далеко от телевизора и они не хотят протягивать слишком длинные кабели в доме, которые могут проходить вдоль стен или даже посередине комнаты. Вместо этого достаточно инвестировать в устройство, которое может управлять беспроводным воспроизведением мультимедиа.

Некролог на смерть Chromecast Audio или почему Google убил черные диски / Pult.ru corporate blog / Habr

Несколько дней назад www.reddit.com сообщил, что Googlе прекращает выпуск Chromecast Audio, устройства, которое в свое время стало одним из самых удобных альтернатив большим мультирум системам. Для тех, кто успел забыть, что это за штуковина, напомню, что это такой диск, который превращал любые колонки в беспроводные, принимал звуковой сигнал по WI-FI, предлагал 3 варианта коммутации и стоил $ 35. Лично для меня Chromecast Audio и тв-приставка Chromecast стали если не эталонами, то своеобразным символами того, что инновации могут существенно расширить возможности пользователя без астрономических затрат.



О чем речь


Немного о том, что это было. Chromecast Audio был достаточно современным, для своего времени гаджетом. Так он создавался на основе медиапроцессора Marvell Armada 88DE3006 1500 Mini Plus с двумя ядрами ARM Cortex-A7. Также в архитектуре был задействован чип Marvell Avastar 88W8887 VHT, который обеспечивал поддержку беспроводных соединений (WLAN).
Кроме того, гаджет использовал модуль флеш-памяти TC58NVG1S3HBAI6 от Toshiba объемом 2 Gb, а для ОЗУ устройства было выбрано 2 Gb DDR3L SDRAM чип Nanya NT5CC128M16IP-DI/EK. Таким образом, за производительность гаджета на момент появления можно было не беспокоиться.

Устройство могло коммутироваться с акустическими системами через 3,5 мм jack, а также через RCA и оптику. Звук передавался через WLAN, что, по отзывам пользователей, позволяло сделать соединение более стабильным и улучшить верность воспроизведения по сравнению с Bluetooth (вполне вероятно для 2014-го года).

Неоправданные надежды на взлёт


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

Очевидной причиной сворачивания производства стало повсеместное распространение беспроводной передачи и интеграции функций Chromecast в сами устройства.

Также я подозреваю, что несколько подкачал маркетинг. И тут был тот случай, когда он действительно был уместен. Устройство появилось в тот момент (2014 год), когда далеко не все флагманские акустические системы были оснащены беспроводной передачей и напичканы функционалом, а источники уже могли осуществлять передачу.

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

Подтверждение и комментарии от Google


ИТ-гигант подтвердил, что прекращает выпуск в официальном сообщении через пару дней после сообщения реддит. В заявлении компании для Android Police в частности сказано следующее:
«Наш портфель продуктов продолжает развиваться, и теперь у нас есть множество продуктов, которые позволяют пользователям наслаждаться аудио… Мы продолжим предлагать поддержку для Chromecast Audio, чтобы пользователи могли продолжать наслаждаться музыкой, подкастами и многим другим».
Сообщение свидетельствует о том, что в Google потеряли интерес и не видят перспектив в развитии Chromecast Audio. Возможно, это также связано с тем, что часть этой ниши на рынке заняли более дешевые аналоги из поднебесной, с более дешевыми устройствами, предлагающими близкий функционал.

Итог


Несмотря на множество достоинств, Chromecast Audio не стал сверх популярным, как того, вероятно, хотели его создатели. Как показало время, рынок оказался более восприимчивыми к устройствам, которые объединяют в себе множество функций, но достаточно холодно отнесся к гаджету, который наделяет привычные нам вещи новыми функциями.
Считаете ли вы решение о свертывании производства этого гаджета правильным, напишите в комментариях — я буду признателен за ваше мнение.

Джинса
Мы продаем мультирум системы и беспроводную акустику, оценить ассортимент можно в нашем каталоге.

Также мы приглашаем всех заинтересованных на мастер-класс “Как выбрать виниловый проигрыватель”. Мероприятие состоится 19 января 2019 в салоне PULT.ru в Москве по адресу ул. Орджоникидзе, 11 подробно о мероприятии здесь.

Мы подарим глинтвейн и плед всем посетителям мастер-класса.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *