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

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

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

تجربه قدیمی Cast درحال‌حاضر از محلی به راه دور وقتی برنامه در پیش‌زمینه است پشتیبانی می‌کند. تجربه معمول Cast زمانی شروع می‌شود که کاربران روی نماد 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)
        }
    }
}
جاوا
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);
    }
  }
}

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

ازراه‌دور به محلی

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

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

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

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

Kotlin
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 فراخوان گوش دهند و بازپخش را به‌صورت محلی هم وقتی برنامه در پیش‌زمینه است و هم وقتی در پس‌زمینه است ازسر بگیرند.

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.
        }
    }
}
جاوا
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 SDK پشتیبانی می‌کنند.

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

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

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

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