تعویض‌کننده خروجی

«تعویض‌کننده برونداد» یکی از ویژگی‌های «کیت توسعه نرم‌افزار پخش محتوا» است که انتقال یکپارچه بین بازپخش محلی و از دور محتوا را از Android 13 امکان‌پذیر می‌کند. هدف این است که به برنامه‌های فرستنده کمک کند به‌راحتی و به‌سرعت کنترل کنند محتوا کجا پخش می‌شود. «تعویض‌کننده خروجی» از کتابخانه MediaRouter برای جابه‌جایی بازپخش محتوا بین بلندگوی تلفن، دستگاه‌های بلوتوث جفت‌شده، و دستگاه‌های دور سازگار با Cast استفاده می‌کند. موارد استفاده را می‌توان به سناریوهای زیر تقسیم کرد:

برای اطلاع از نحوه پیاده‌سازی «تعویض‌کننده برونداد» در برنامه‌تان، برنامه نمونه CastVideos-android را بارگیری و استفاده کنید.

«تعویض‌کننده برونداد» باید فعال شود تا از محلی به راه دور، از راه دور به محلی، و از راه دور به راه دور بااستفاده از مراحل پوشش‌داده‌شده در این راهنما پشتیبانی کند. برای پشتیبانی از انتقال بین بلندگوهای دستگاه محلی و دستگاه‌های بلوتوث جفت‌شده، به مراحل اضافی نیاز نیست.

رابط کاربری «تعویض‌کننده خروجی»

«تعویض‌کننده خروجی» دستگاه‌های محلی و راه دور دردسترس را و همچنین وضعیت فعلی دستگاه‌ها را نمایش می‌دهد، ازجمله اینکه دستگاه انتخاب شده است یا نه، درحال اتصال است یا نه، و سطح صدای فعلی. اگر علاوه‌بر دستگاه فعلی دستگاه‌های دیگری وجود داشته باشد، با کلیک کردن روی دستگاه دیگر می‌توانید بازپخش رسانه را به دستگاه انتخاب‌شده منتقل کنید.

مشکلات شناخته‌شده

  • «جلسه‌های رسانه» ایجادشده برای بازپخش محلی هنگام جابه‌جایی به اعلان Cast SDK بسته و دوباره ایجاد خواهد شد.

نقطه‌های ورودی

اعلان رسانه

اگر برنامه‌ای اعلان رسانه‌ای با MediaSession برای بازپخش محلی (پخش محلی) پست کند، گوشه بالای اعلان رسانه تراشه اعلانی با نام دستگاهی (مثل بلندگوی تلفن) که محتوا درحال‌حاضر در آن پخش می‌شود نمایش می‌دهد. با تک‌ضرب زدن روی تراشه اعلان، واسط کاربر سیستم کادر گفتگوی «تعویض‌کننده برونداد» باز می‌شود.

تنظیمات میزان صدا

واسط کاربر سیستم گفتگوی «تعویض‌کننده برونداد» را می‌توان با کلیک کردن روی دکمه‌های فیزیکی میزان صدا در دستگاه، تک‌ضرب زدن روی نماد تنظیمات در پایین، و تک‌ضرب زدن روی نوشتار «پخش <نام برنامه> در <دستگاه پخش محتوا>» نیز راه‌اندازی کرد.

خلاصه مراحل

پیش‌نیازها

  1. برنامه Android موجودتان را به AndroidX منتقل کنید.
  2. build.gradle برنامه‌تان را به‌روز کنید تا از حداقل نسخه لازم «کیت توسعه نرم‌افزار فرستنده Android» برای «تعویض‌کننده خروجی» استفاده کند:
    dependencies {
      ...
      implementation 'com.google.android.gms:play-services-cast-framework:21.2.0'
      ...
    }
  3. برنامه از اعلان‌های رسانه پشتیبانی می‌کند.
  4. دستگاه دارای Android 13.

راه‌اندازی اعلان‌های رسانه

برای استفاده از «تعویض‌کننده خروجی»، برنامه‌های صوتی و ویدیویی باید اعلان رسانه‌ای ایجاد کنند تا وضعیت بازپخش و کنترل‌های رسانه برای بازپخش محلی نمایش داده شود. برای این کار باید MediaSession ایجاد کنید، MediaStyle را با کد MediaSession تنظیم کنید، و کنترل‌های رسانه را در اعلان تنظیم کنید.

اگر درحال‌حاضر از MediaStyle و MediaSession استفاده نمی‌کنید، گزیده زیر نحوه راه‌اندازی آن‌ها را نشان می‌دهد و راهنماهایی برای راه‌اندازی بازخوان‌های جلسه رسانه برای صدا و ویدیو برنامه‌ها دردسترس است:

Kotlin
// 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() ارائه دهید.

Kotlin
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() ارائه دهید.

Kotlin
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()
)
Java
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» جلسه Cast را به‌طور خودکار شروع می‌کند. بااین‌حال، برنامه‌ها باید تغییر از محلی به از دور را مدیریت کنند، برای مثال، پخش محلی را متوقف کنند و رسانه را در دستگاه «پخش محتوا» بار کنند. برنامه‌ها باید بااستفاده از onSessionStarted() و onSessionEnded() بازخوان‌ها به Cast SessionManagerListener گوش دهند و هنگام دریافت بازخوان‌های Cast SessionManager ، کنش را مدیریت کنند. برنامه‌ها باید مطمئن شوند که این برگشتی‌ها وقتی کادر گفتگوی «تعویض‌کننده خروجی» باز می‌شود و برنامه در پیش‌زمینه نیست همچنان فعال هستند.

به‌روزرسانی SessionManagerListener برای پخش محتوای پس‌زمینه‌ای

تجربه قدیمی Cast درحال‌حاضر از محلی به دوردست وقتی برنامه در پیش‌زمینه است پشتیبانی می‌کند. تجربه معمول «پخش محتوا» زمانی شروع می‌شود که کاربران روی نماد «پخش محتوا» در برنامه کلیک می‌کنند و دستگاهی را برای جاری‌سازی رسانه انتخاب می‌کنند. در این مورد، برنامه باید در SessionManagerListener در onCreate() یا onStart() ثبت شود و شنونده در onStop() یا onDestroy() از فعالیت برنامه لغو ثبت شود.

با تجربه جدید پخش محتوا بااستفاده از «جابه‌جاگر برونداد»، برنامه‌ها می‌توانند وقتی در پس‌زمینه هستند پخش محتوا را شروع کنند. این ویژگی به‌ویژه برای برنامه‌های صوتی که هنگام پخش در پس‌زمینه اعلان پست می‌کنند مفید است. برنامه‌ها می‌توانند شنوندگان SessionManager را در onCreate() سرویس ثبت کنند و در onDestroy() سرویس ثبت آن‌ها را لغو کنند. وقتی برنامه در پس‌زمینه است، برنامه‌ها باید همیشه پاسخ‌های تماس محلی به راه دور را دریافت کنند (مثل onSessionStarted).

اگر برنامه از MediaBrowserService استفاده می‌کند، توصیه می‌شود SessionManagerListener را در آنجا ثبت کنید.

Kotlin
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)
        }
    }
}
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);
    }
  }
}

با این به‌روزرسانی، وقتی برنامه در پس‌زمینه است، عملکرد محلی به دور یکسان با پخش محتوای معمولی است و برای جابه‌جایی از دستگاه‌های بلوتوث به دستگاه‌های «پخش محتوا» به کار اضافی نیاز نیست.

از دور به محلی

«تعویض‌کننده خروجی» امکان انتقال از پخش از دور به بلندگوی تلفن یا دستگاه بلوتوث محلی را فراهم می‌کند. با تنظیم پرچم setRemoteToLocalEnabled روی true در CastOptions می‌توان این ویژگی را فعال کرد.

در مواردی که دستگاه فرستنده فعلی به جلسه موجود با چند فرستنده می‌پیوندد و برنامه باید بررسی کند که آیا رسانه فعلی مجاز به انتقال محلی است یا نه، برنامه‌ها باید از onTransferred بازخوان SessionTransferCallback برای بررسی SessionState استفاده کنند.

پرچم setRemoteToLocalEnabled را تنظیم کنید

CastOptions.Builder setRemoteToLocalEnabled را ارائه می‌دهد تا بلندگوی تلفن و دستگاه‌های بلوتوث محلی را به‌عنوان هدف‌های انتقال به در کادر گفتگوی «تعویض‌کننده خروجی» درصورت وجود جلسه «پخش» فعال نشان دهد یا پنهان کند.

Kotlin
class CastOptionsProvider : OptionsProvider {
    fun getCastOptions(context: Context?): CastOptions {
        ...
        return Builder()
            ...
            .setRemoteToLocalEnabled(true)
            .build()
    }
}
Java
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 بازخوان گوش دهند و بازپخش را به‌صورت محلی هم زمانی که برنامه در پیش‌زمینه است و هم زمانی که در پس‌زمینه است ازسر بگیرند.

Kotlin
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.
        }
    }
}
Java
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 ذخیره کند (برای مثال، فراداده رسانه و موقعیت بازپخش). وقتی برنامه از پس‌زمینه به پیش‌زمینه می‌آید، پخش محلی باید با اطلاعات ذخیره‌شده ادامه یابد.

Kotlin
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 برای برنامه‌های صوتی بااستفاده از «گسترش جاری‌سازی» پشتیبانی می‌کند.

برنامه‌های صوتی برنامه‌هایی هستند که از Google Cast برای صدا در تنظیمات برنامه گیرنده در «کنسول توسعه‌دهندگان کیت توسعه نرم‌افزار Google Cast» پشتیبانی می‌کنند

«توسعه جاری‌سازی» با بلندگو

برنامه‌های صوتی که از «تعویض‌کننده خروجی» استفاده می‌کنند می‌توانند درطول جلسه Cast بااستفاده از «گسترش جاری‌سازی»، صدا را به چندین دستگاه بلندگوی سازگار با Cast گسترش دهند.

این ویژگی توسط پلاتفرم «پخش محتوا» پشتیبانی می‌شود و اگر برنامه از میانای کاربری پیش‌فرض استفاده کند، نیازی به تغییرات بیشتر ندارد. اگر از میانای کاربر سفارشی استفاده می‌شود، برنامه باید میانای کاربر را به‌روز کند تا نشان دهد برنامه درحال ارسال محتوا به گروه است.

برای دریافت نام گروه ازهم بازشده جدید درطول ازهم بازشدن جاری‌سازی، بااستفاده از CastSession#addCastListener، Cast.Listener را ثبت کنید. سپس درطول تماس برگشتی onDeviceNameChanged تماس بگیرید CastSession#getCastDevice()

Kotlin
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. محتوای خود را بااستفاده از پخش محتوای معمولی یا با محلی به دور در دستگاه سازگار با پخش محتوا پخش کنید.
  2. «گزینشگر برونداد» را بااستفاده از یکی از نقاط ورودی باز کنید.
  3. روی دستگاه سازگار با Cast دیگری تک‌ضرب بزنید، برنامه‌های صوتی محتوا را به دستگاه دیگر گسترش می‌دهند و گروهی پویا ایجاد می‌کنند.
  4. دوباره روی دستگاه سازگار با Cast تک‌ضرب بزنید، دستگاه از گروه پویا برداشته خواهد شد.