Выходной переключатель

Переключатель вывода — это функция Cast SDK, которая обеспечивает плавную передачу между локальным и удаленным воспроизведением контента, начиная с Android 13. Цель — помочь приложениям-отправителям легко и быстро контролировать, где воспроизводится контент. Output Switcher использует библиотеку MediaRouter для переключения воспроизведения контента между динамиком телефона, сопряженными устройствами Bluetooth и удаленными устройствами с поддержкой Cast. Варианты использования можно разбить на следующие сценарии:

Загрузите и используйте пример приложения CastVideos-android, чтобы узнать, как реализовать переключатель вывода в вашем приложении.

Переключатель выходов должен быть включен для поддержки локального-удаленного, удаленного-локального и удаленного-удаленного, выполнив действия, описанные в этом руководстве. Для поддержки передачи между динамиками локального устройства и сопряженными устройствами Bluetooth не требуется никаких дополнительных действий.

Пользовательский интерфейс переключателя выходов

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

Известные вопросы

  • Медиа-сеансы, созданные для локального воспроизведения, будут закрыты и созданы заново при переключении на уведомление Cast SDK.

Точки входа

Уведомление СМИ

Если приложение публикует мультимедийное уведомление с помощью MediaSession для локального воспроизведения (локального воспроизведения), в правом верхнем углу мультимедийного уведомления отображается чип уведомления с именем устройства (например, динамика телефона), на котором в данный момент воспроизводится контент. Нажатие на чип уведомлений открывает пользовательский интерфейс диалоговой системы переключателя вывода.

Настройки громкости

Пользовательский интерфейс диалоговой системы переключателя вывода также можно вызвать, нажав кнопки физической громкости на устройстве, коснувшись значка настроек внизу и коснувшись текста «Воспроизвести <Имя приложения> на <Cast Device>».

Краткое описание шагов

Предварительные условия

  1. Перенесите существующее приложение Android на AndroidX.
  2. Обновите build.gradle вашего приложения, чтобы использовать минимально необходимую версию Android Sender SDK для переключателя вывода:
    dependencies {
      ...
      implementation 'com.google.android.gms:play-services-cast-framework:21.2.0'
      ...
    }
  3. Приложение поддерживает медиа-уведомления.
  4. Устройство под управлением Android 13.

Настройка медиа-уведомлений

Чтобы использовать переключатель вывода, аудио- и видеоприложениям необходимо создать медиа-уведомление для отображения состояния воспроизведения и элементов управления медиафайлами для локального воспроизведения. Для этого необходимо создать MediaSession , установить MediaStyle с помощью токена MediaSession и настроить элементы управления мультимедиа в уведомлении.

Если вы в настоящее время не используете MediaStyle и MediaSession , в приведенном ниже фрагменте показано, как их настроить, а также доступны руководства по настройке обратных вызовов мультимедийного сеанса для аудио- и видеоприложений :

Котлин
// Create a media session. NotificationCompat.MediaStyle
// PlayerService is your own Service or Activity responsible for media playback.
val mediaSession = MediaSessionCompat(this, "PlayerService")

// Create a MediaStyle object and supply your media session token to it.
val mediaStyle = Notification.MediaStyle().setMediaSession(mediaSession.sessionToken)

// Create a Notification which is styled by your MediaStyle object.
// This connects your media session to the media controls.
// Don't forget to include a small icon.
val notification = Notification.Builder(this@PlayerService, CHANNEL_ID)
    .setStyle(mediaStyle)
    .setSmallIcon(R.drawable.ic_app_logo)
    .build()

// Specify any actions which your users can perform, such as pausing and skipping to the next track.
val pauseAction: Notification.Action = Notification.Action.Builder(
        pauseIcon, "Pause", pauseIntent
    ).build()
notification.addAction(pauseAction)
Джава
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
    // Create a media session. NotificationCompat.MediaStyle
    // PlayerService is your own Service or Activity responsible for media playback.
    MediaSession mediaSession = new MediaSession(this, "PlayerService");

    // Create a MediaStyle object and supply your media session token to it.
    Notification.MediaStyle mediaStyle = new Notification.MediaStyle().setMediaSession(mediaSession.getSessionToken());

    // Specify any actions which your users can perform, such as pausing and skipping to the next track.
    Notification.Action pauseAction = Notification.Action.Builder(pauseIcon, "Pause", pauseIntent).build();

    // Create a Notification which is styled by your MediaStyle object.
    // This connects your media session to the media controls.
    // Don't forget to include a small icon.
    String CHANNEL_ID = "CHANNEL_ID";
    Notification notification = new Notification.Builder(this, CHANNEL_ID)
        .setStyle(mediaStyle)
        .setSmallIcon(R.drawable.ic_app_logo)
        .addAction(pauseAction)
        .build();
}

Кроме того, чтобы заполнить уведомление информацией о вашем медиафайле, вам необходимо добавить метаданные вашего медиафайла и состояние воспроизведения в MediaSession .

Чтобы добавить метаданные в MediaSession , используйте setMetaData() и укажите все соответствующие константы MediaMetadata для вашего мультимедиа в MediaMetadataCompat.Builder() .

Котлин
mediaSession.setMetadata(MediaMetadataCompat.Builder()
    // Title
    .putString(MediaMetadata.METADATA_KEY_TITLE, currentTrack.title)

    // Artist
    // Could also be the channel name or TV series.
    .putString(MediaMetadata.METADATA_KEY_ARTIST, currentTrack.artist)

    // Album art
    // Could also be a screenshot or hero image for video content
    // The URI scheme needs to be "content", "file", or "android.resource".
    .putString(
        MediaMetadata.METADATA_KEY_ALBUM_ART_URI, currentTrack.albumArtUri)
    )

    // Duration
    // If duration isn't set, such as for live broadcasts, then the progress
    // indicator won't be shown on the seekbar.
    .putLong(MediaMetadata.METADATA_KEY_DURATION, currentTrack.duration)

    .build()
)
Джава
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
    mediaSession.setMetadata(
        new MediaMetadataCompat.Builder()
        // Title
        .putString(MediaMetadata.METADATA_KEY_TITLE, currentTrack.title)

        // Artist
        // Could also be the channel name or TV series.
        .putString(MediaMetadata.METADATA_KEY_ARTIST, currentTrack.artist)

        // Album art
        // Could also be a screenshot or hero image for video content
        // The URI scheme needs to be "content", "file", or "android.resource".
        .putString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI, currentTrack.albumArtUri)

        // Duration
        // If duration isn't set, such as for live broadcasts, then the progress
        // indicator won't be shown on the seekbar.
        .putLong(MediaMetadata.METADATA_KEY_DURATION, currentTrack.duration)

        .build()
    );
}

Чтобы добавить состояние воспроизведения в MediaSession , используйте setPlaybackState() и укажите все соответствующие константы PlaybackStateCompat для вашего мультимедиа в PlaybackStateCompat.Builder() .

Котлин
mediaSession.setPlaybackState(
    PlaybackStateCompat.Builder()
        .setState(
            PlaybackStateCompat.STATE_PLAYING,

            // Playback position
            // Used to update the elapsed time and the progress bar.
            mediaPlayer.currentPosition.toLong(),

            // Playback speed
            // Determines the rate at which the elapsed time changes.
            playbackSpeed
        )

        // isSeekable
        // Adding the SEEK_TO action indicates that seeking is supported
        // and makes the seekbar position marker draggable. If this is not
        // supplied seek will be disabled but progress will still be shown.
        .setActions(PlaybackStateCompat.ACTION_SEEK_TO)
        .build()
)
Джава
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
    mediaSession.setPlaybackState(
        new PlaybackStateCompat.Builder()
            .setState(
                 PlaybackStateCompat.STATE_PLAYING,

                // Playback position
                // Used to update the elapsed time and the progress bar.
                mediaPlayer.currentPosition.toLong(),

                // Playback speed
                // Determines the rate at which the elapsed time changes.
                playbackSpeed
            )

        // isSeekable
        // Adding the SEEK_TO action indicates that seeking is supported
        // and makes the seekbar position marker draggable. If this is not
        // supplied seek will be disabled but progress will still be shown.
        .setActions(PlaybackStateCompat.ACTION_SEEK_TO)
        .build()
    );
}

Поведение уведомлений видеоприложений

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

  • Публикуйте медиа-уведомление при локальном воспроизведении мультимедиа, когда приложение находится на переднем плане.
  • Приостановите локальное воспроизведение и закройте уведомление, когда приложение работает в фоновом режиме.
  • Когда приложение вернется на передний план, локальное воспроизведение должно возобновиться, а уведомление должно быть повторно опубликовано.

Включить переключатель вывода в AndroidManifest.xml

Чтобы включить переключатель вывода, необходимо добавить MediaTransferReceiver в AndroidManifest.xml приложения. В противном случае функция не будет включена, а флаг функции удаленного доступа к локальному также будет недействителен.

<application>
    ...
    <receiver
         android:name="androidx.mediarouter.media.MediaTransferReceiver"
         android:exported="true">
    </receiver>
    ...
</application>

MediaTransferReceiver — это широковещательный приемник, который обеспечивает передачу мультимедиа между устройствами с системным пользовательским интерфейсом. Дополнительные сведения см. в справочнике по MediaTransferReceiver .

Локальное-удаленное

Когда пользователь переключает воспроизведение с локального на удаленное, Cast SDK автоматически запускает сеанс Cast. Однако приложениям необходимо обрабатывать переключение с локального режима на удаленный, например останавливать локальное воспроизведение и загружать медиафайлы на устройство Cast. Приложения должны прослушивать Cast SessionManagerListener , используя обратные вызовы onSessionStarted() и onSessionEnded() , и обрабатывать действие при получении обратных вызовов Cast SessionManager . Приложения должны гарантировать, что эти обратные вызовы все еще активны, когда диалоговое окно «Переключатель вывода» открыто, а приложение не находится на переднем плане.

Обновить SessionManagerListener для фонового кастинга.

Устаревшая версия Cast уже поддерживает передачу данных от локального к удаленному, когда приложение находится на переднем плане. Типичный процесс трансляции начинается, когда пользователи нажимают значок Cast в приложении и выбирают устройство для потоковой передачи мультимедиа. В этом случае приложению необходимо зарегистрироваться в SessionManagerListener в onCreate() или onStart() и отменить регистрацию прослушивателя в onStop() или onDestroy() активности приложения.

Благодаря новому интерфейсу трансляции с помощью переключателя вывода приложения могут начинать трансляцию, когда они находятся в фоновом режиме. Это особенно полезно для аудиоприложений, которые отправляют уведомления при воспроизведении в фоновом режиме. Приложения могут зарегистрировать прослушиватели SessionManager в onCreate() службы и отменить регистрацию в onDestroy() службы. Приложения всегда должны получать обратные вызовы между локальным и удаленным доступом (например, onSessionStarted ), когда приложение находится в фоновом режиме.

Если приложение использует MediaBrowserService , рекомендуется зарегистрировать там SessionManagerListener .

Котлин
class MyService : Service() {
    private var castContext: CastContext? = null
    protected fun onCreate() {
        castContext = CastContext.getSharedInstance(this)
        castContext
            .getSessionManager()
            .addSessionManagerListener(sessionManagerListener, CastSession::class.java)
    }

    protected fun onDestroy() {
        if (castContext != null) {
            castContext
                .getSessionManager()
                .removeSessionManagerListener(sessionManagerListener, CastSession::class.java)
        }
    }
}
Джава
public class MyService extends Service {
  private CastContext castContext;

  @Override
  protected void onCreate() {
     castContext = CastContext.getSharedInstance(this);
     castContext
        .getSessionManager()
        .addSessionManagerListener(sessionManagerListener, CastSession.class);
  }

  @Override
  protected void onDestroy() {
    if (castContext != null) {
       castContext
          .getSessionManager()
          .removeSessionManagerListener(sessionManagerListener, CastSession.class);
    }
  }
}

Благодаря этому обновлению передача с локального на удаленный действует так же, как и обычная трансляция, когда приложение находится в фоновом режиме, и для переключения с устройств Bluetooth на устройства Cast не требуется дополнительных усилий.

Удаленное-локальное

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

В случаях, когда текущее устройство-отправитель присоединяется к существующему сеансу с несколькими отправителями и приложению необходимо проверить, разрешена ли локальная передача текущего мультимедиа, приложения должны использовать обратный вызов onTransferred SessionTransferCallback для проверки SessionState .

Установите флаг setRemoteToLocalEnabled.

CastOptions.Builder предоставляет setRemoteToLocalEnabled для отображения или скрытия динамика телефона и локальных устройств Bluetooth в качестве целевых объектов передачи в диалоговом окне переключателя вывода при наличии активного сеанса трансляции.

Котлин
class CastOptionsProvider : OptionsProvider {
    fun getCastOptions(context: Context?): CastOptions {
        ...
        return Builder()
            ...
            .setRemoteToLocalEnabled(true)
            .build()
    }
}
Джава
public class CastOptionsProvider implements OptionsProvider {
    @Override
    public CastOptions getCastOptions(Context context) {
        ...
        return new CastOptions.Builder()
            ...
            .setRemoteToLocalEnabled(true)
            .build()
  }
}

Продолжить воспроизведение локально

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

CastContext#addSessionTransferCallback(SessionTransferCallback) позволяет приложению регистрировать свой SessionTransferCallback и прослушивать обратные вызовы onTransferred и onTransferFailed , когда отправитель переводится в режим локального воспроизведения.

После того как приложение отменит регистрацию SessionTransferCallback , оно больше не будет получать SessionTransferCallback .

SessionTransferCallback является расширением существующих обратных вызовов SessionManagerListener и запускается после запуска onSessionEnded . Порядок обратных вызовов удаленно-локально:

  1. onTransferring
  2. onSessionEnding
  3. onSessionEnded
  4. onTransferred

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

Приложения, поддерживающие фоновое воспроизведение

Для приложений, поддерживающих воспроизведение в фоновом режиме (обычно аудиоприложения), рекомендуется использовать Service (например, MediaBrowserService ). Службы должны прослушивать обратный вызов onTransferred и возобновлять воспроизведение локально, как когда приложение находится на переднем плане, так и в фоновом режиме.

Котлин
class MyService : Service() {
    private var castContext: CastContext? = null
    private var sessionTransferCallback: SessionTransferCallback? = null
    protected fun onCreate() {
        castContext = CastContext.getSharedInstance(this)
        castContext.getSessionManager()
                   .addSessionManagerListener(sessionManagerListener, CastSession::class.java)
        sessionTransferCallback = MySessionTransferCallback()
        castContext.addSessionTransferCallback(sessionTransferCallback)
    }

    protected fun onDestroy() {
        if (castContext != null) {
            castContext.getSessionManager()
                       .removeSessionManagerListener(sessionManagerListener, CastSession::class.java)
            if (sessionTransferCallback != null) {
                castContext.removeSessionTransferCallback(sessionTransferCallback)
            }
        }
    }

    class MySessionTransferCallback : SessionTransferCallback() {
        fun onTransferring(@SessionTransferCallback.TransferType transferType: Int) {
            // Perform necessary steps prior to onTransferred
        }

        fun onTransferred(@SessionTransferCallback.TransferType transferType: Int,
                          sessionState: SessionState?) {
            if (transferType == SessionTransferCallback.TRANSFER_TYPE_FROM_REMOTE_TO_LOCAL) {
                // Remote stream is transferred to the local device.
                // Retrieve information from the SessionState to continue playback on the local player.
            }
        }

        fun onTransferFailed(@SessionTransferCallback.TransferType transferType: Int,
                             @SessionTransferCallback.TransferFailedReason transferFailedReason: Int) {
            // Handle transfer failure.
        }
    }
}
Джава
public class MyService extends Service {
    private CastContext castContext;
    private SessionTransferCallback sessionTransferCallback;

    @Override
    protected void onCreate() {
        castContext = CastContext.getSharedInstance(this);
        castContext.getSessionManager()
                   .addSessionManagerListener(sessionManagerListener, CastSession.class);
        sessionTransferCallback = new MySessionTransferCallback();
        castContext.addSessionTransferCallback(sessionTransferCallback);
    }

    @Override
    protected void onDestroy() {
        if (castContext != null) {
            castContext.getSessionManager()
                       .removeSessionManagerListener(sessionManagerListener, CastSession.class);
            if (sessionTransferCallback != null) {
                castContext.removeSessionTransferCallback(sessionTransferCallback);
            }
        }
    }

    public static class MySessionTransferCallback extends SessionTransferCallback {
        public MySessionTransferCallback() {}

        @Override
        public void onTransferring(@SessionTransferCallback.TransferType int transferType) {
            // Perform necessary steps prior to onTransferred
        }

        @Override
        public void onTransferred(@SessionTransferCallback.TransferType int transferType,
                                  SessionState sessionState) {
            if (transferType==SessionTransferCallback.TRANSFER_TYPE_FROM_REMOTE_TO_LOCAL) {
                // Remote stream is transferred to the local device.
                // Retrieve information from the SessionState to continue playback on the local player.
            }
        }

        @Override
        public void onTransferFailed(@SessionTransferCallback.TransferType int transferType,
                                     @SessionTransferCallback.TransferFailedReason int transferFailedReason) {
            // Handle transfer failure.
        }
    }
}

Приложения, которые не поддерживают фоновое воспроизведение

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

Если приложение работает в фоновом режиме, оно должно приостановить воспроизведение и сохранить необходимую информацию из SessionState (например, метаданные мультимедиа и позицию воспроизведения). Когда приложение выходит на передний план из фона, локальное воспроизведение должно продолжаться с сохраненной информацией.

Котлин
class MyActivity : AppCompatActivity() {
    private var castContext: CastContext? = null
    private var sessionTransferCallback: SessionTransferCallback? = null
    protected fun onCreate() {
        castContext = CastContext.getSharedInstance(this)
        castContext.getSessionManager()
                   .addSessionManagerListener(sessionManagerListener, CastSession::class.java)
        sessionTransferCallback = MySessionTransferCallback()
        castContext.addSessionTransferCallback(sessionTransferCallback)
    }

    protected fun onDestroy() {
        if (castContext != null) {
            castContext.getSessionManager()
                       .removeSessionManagerListener(sessionManagerListener, CastSession::class.java)
            if (sessionTransferCallback != null) {
                castContext.removeSessionTransferCallback(sessionTransferCallback)
            }
        }
    }

    class MySessionTransferCallback : SessionTransferCallback() {
        fun onTransferring(@SessionTransferCallback.TransferType transferType: Int) {
            // Perform necessary steps prior to onTransferred
        }

        fun onTransferred(@SessionTransferCallback.TransferType transferType: Int,
                          sessionState: SessionState?) {
            if (transferType == SessionTransferCallback.TRANSFER_TYPE_FROM_REMOTE_TO_LOCAL) {
                // Remote stream is transferred to the local device.

                // Retrieve information from the SessionState to continue playback on the local player.
            }
        }

        fun onTransferFailed(@SessionTransferCallback.TransferType transferType: Int,
                             @SessionTransferCallback.TransferFailedReason transferFailedReason: Int) {
            // Handle transfer failure.
        }
    }
}
Джава
public class MyActivity extends AppCompatActivity {
  private CastContext castContext;
  private SessionTransferCallback sessionTransferCallback;

  @Override
  protected void onCreate() {
     castContext = CastContext.getSharedInstance(this);
     castContext
        .getSessionManager()
        .addSessionManagerListener(sessionManagerListener, CastSession.class);
     sessionTransferCallback = new MySessionTransferCallback();
     castContext.addSessionTransferCallback(sessionTransferCallback);
  }

  @Override
  protected void onDestroy() {
    if (castContext != null) {
       castContext
          .getSessionManager()
          .removeSessionManagerListener(sessionManagerListener, CastSession.class);
      if (sessionTransferCallback != null) {
         castContext.removeSessionTransferCallback(sessionTransferCallback);
      }
    }
  }

  public static class MySessionTransferCallback extends SessionTransferCallback {
    public MySessionTransferCallback() {}

    @Override
    public void onTransferring(@SessionTransferCallback.TransferType int transferType) {
        // Perform necessary steps prior to onTransferred
    }

    @Override
    public void onTransferred(@SessionTransferCallback.TransferType int transferType,
                               SessionState sessionState) {
      if (transferType==SessionTransferCallback.TRANSFER_TYPE_FROM_REMOTE_TO_LOCAL) {
        // Remote stream is transferred to the local device.

        // Retrieve information from the SessionState to continue playback on the local player.
      }
    }

    @Override
    public void onTransferFailed(@SessionTransferCallback.TransferType int transferType,
                                 @SessionTransferCallback.TransferFailedReason int transferFailedReason) {
      // Handle transfer failure.
    }
  }
}

Удаленный-удаленный

Выходной переключатель поддерживает возможность расширения до нескольких динамиков с поддержкой Cast для аудиоприложений с помощью Stream Expansion.

Аудиоприложения – это приложения, поддерживающие Google Cast для аудио в настройках приложения-приемника в консоли разработчика Google Cast SDK.

Расширение потока с помощью динамиков

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

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

Чтобы получить новое расширенное имя группы во время расширения потока, зарегистрируйте Cast.Listener с помощью CastSession#addCastListener . Затем вызовите CastSession#getCastDevice() во время обратного вызова onDeviceNameChanged .

Котлин
class MyActivity : Activity() {
    private var mCastSession: CastSession? = null
    private lateinit var mCastContext: CastContext
    private lateinit var mSessionManager: SessionManager
    private val mSessionManagerListener: SessionManagerListener<CastSession> =
        SessionManagerListenerImpl()
    private val mCastListener = CastListener()

    private inner class SessionManagerListenerImpl : SessionManagerListener<CastSession?> {
        override fun onSessionStarting(session: CastSession?) {}

        override fun onSessionStarted(session: CastSession?, sessionId: String) {
            addCastListener(session)
        }

        override fun onSessionStartFailed(session: CastSession?, error: Int) {}

        override fun onSessionSuspended(session: CastSession?, reason Int) {
            removeCastListener()
        }

        override fun onSessionResuming(session: CastSession?, sessionId: String) {}

        override fun onSessionResumed(session: CastSession?, wasSuspended: Boolean) {
            addCastListener(session)
        }

        override fun onSessionResumeFailed(session: CastSession?, error: Int) {}

        override fun onSessionEnding(session: CastSession?) {}

        override fun onSessionEnded(session: CastSession?, error: Int) {
            removeCastListener()
        }
    }

    private inner class CastListener : Cast.Listener() {
        override fun onDeviceNameChanged() {
            mCastSession?.let {
                val castDevice = it.castDevice
                val deviceName = castDevice.friendlyName
                // Update UIs with the new cast device name.
            }
        }
    }

    private fun addCastListener(castSession: CastSession) {
        mCastSession = castSession
        mCastSession?.addCastListener(mCastListener)
    }

    private fun removeCastListener() {
        mCastSession?.removeCastListener(mCastListener)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mCastContext = CastContext.getSharedInstance(this)
        mSessionManager = mCastContext.sessionManager
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java)
    }

    override fun onDestroy() {
        super.onDestroy()
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.java)
    }
}
Джава
public class MyActivity extends Activity {
    private CastContext mCastContext;
    private CastSession mCastSession;
    private SessionManager mSessionManager;
    private SessionManagerListener<CastSession> mSessionManagerListener =
            new SessionManagerListenerImpl();
    private Cast.Listener mCastListener = new CastListener();

    private class SessionManagerListenerImpl implements SessionManagerListener<CastSession> {
        @Override
        public void onSessionStarting(CastSession session) {}
        @Override
        public void onSessionStarted(CastSession session, String sessionId) {
            addCastListener(session);
        }
        @Override
        public void onSessionStartFailed(CastSession session, int error) {}
        @Override
        public void onSessionSuspended(CastSession session, int reason) {
            removeCastListener();
        }
        @Override
        public void onSessionResuming(CastSession session, String sessionId) {}
        @Override
        public void onSessionResumed(CastSession session, boolean wasSuspended) {
            addCastListener(session);
        }
        @Override
        public void onSessionResumeFailed(CastSession session, int error) {}
        @Override
        public void onSessionEnding(CastSession session) {}
        @Override
        public void onSessionEnded(CastSession session, int error) {
            removeCastListener();
        }
    }

    private class CastListener extends Cast.Listener {
         @Override
         public void onDeviceNameChanged() {
             if (mCastSession == null) {
                 return;
             }
             CastDevice castDevice = mCastSession.getCastDevice();
             String deviceName = castDevice.getFriendlyName();
             // Update UIs with the new cast device name.
         }
    }

    private void addCastListener(CastSession castSession) {
        mCastSession = castSession;
        mCastSession.addCastListener(mCastListener);
    }

    private void removeCastListener() {
        if (mCastSession != null) {
            mCastSession.removeCastListener(mCastListener);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCastContext = CastContext.getSharedInstance(this);
        mSessionManager = mCastContext.getSessionManager();
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class);
    }
}

Тестирование удаленного доступа

Чтобы протестировать эту функцию:

  1. Транслируйте контент на устройство с поддержкой Cast, используя обычную трансляцию или локально-удаленную трансляцию .
  2. Откройте переключатель выходов, используя одну из точек входа .
  3. Коснитесь другого устройства с поддержкой Cast, аудиоприложения распространят контент на дополнительное устройство, создавая динамическую группу.
  4. Нажмите на устройство с поддержкой Cast еще раз, оно будет удалено из динамической группы.