سوئیچر خروجی

Output Switcher یکی از ویژگی‌های Cast SDK است که از اندروید ۱۳ به بعد، انتقال یکپارچه بین پخش محلی و از راه دور محتوا را امکان‌پذیر می‌کند. هدف این است که به برنامه‌های فرستنده کمک کند تا به راحتی و به سرعت محل پخش محتوا را کنترل کنند. Output Switcher از کتابخانه MediaRouter برای تغییر پخش محتوا بین بلندگوی تلفن، دستگاه‌های بلوتوث جفت‌شده و دستگاه‌های از راه دور دارای قابلیت Cast استفاده می‌کند. موارد استفاده را می‌توان به سناریوهای زیر تقسیم کرد:

برای آشنایی با نحوه‌ی پیاده‌سازی Output Switcher در برنامه‌تان ، برنامه‌ی نمونه‌ی CastVideos-android را دانلود و استفاده کنید.

برای پشتیبانی از انتقال صدا از محلی به راه دور، از راه دور به محلی و از راه دور به راه دور، باید گزینه‌ی «سوئیچر خروجی» (Output Switcher) را فعال کنید. این کار با استفاده از مراحل ذکر شده در این راهنما انجام می‌شود. هیچ مرحله‌ی اضافی برای پشتیبانی از انتقال بین بلندگوهای دستگاه محلی و دستگاه‌های بلوتوث جفت‌شده لازم نیست.

رابط کاربری سوئیچ خروجی

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

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

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

نقاط ورود

اطلاع رسانی رسانه ای

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

تنظیمات صدا

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

خلاصه مراحل

پیش‌نیازها

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

اعلان‌های رسانه‌ای را تنظیم کنید

برای استفاده از Output Switcher، برنامه‌های صوتی و تصویری باید یک اعلان رسانه‌ای ایجاد کنند تا وضعیت پخش و کنترل‌های مربوط به رسانه‌های خود را برای پخش محلی نمایش دهند. این کار مستلزم ایجاد یک 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()
    );
}

رفتار اعلان برنامه ویدیویی

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

  • اعلان رسانه را هنگام پخش رسانه به صورت محلی ارسال کنید و برنامه در پیش‌زمینه باشد.
  • وقتی برنامه در پس‌زمینه است، پخش محلی را متوقف کنید و اعلان را رد کنید.
  • وقتی برنامه به پیش‌زمینه برمی‌گردد، پخش محلی باید از سر گرفته شود و اعلان باید دوباره ارسال شود.

فعال کردن Output Switcher در AndroidManifest.xml

برای فعال کردن Output Switcher، باید MediaTransferReceiver به AndroidManifest.xml برنامه اضافه شود. اگر اضافه نشود، این ویژگی فعال نخواهد شد و پرچم ویژگی remote-to-local نیز نامعتبر خواهد بود.

<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 اقدامات لازم را انجام دهند. برنامه‌ها باید اطمینان حاصل کنند که این فراخوانی‌ها هنگام باز شدن کادر محاوره‌ای Output Switcher و عدم نمایش برنامه در پیش‌زمینه، همچنان فعال هستند.

به‌روزرسانی SessionManagerListener برای ارسال در پس‌زمینه

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

با تجربه جدید ارسال با استفاده از Output Switcher، برنامه‌ها می‌توانند وقتی در پس‌زمینه هستند، شروع به ارسال کنند. این امر به ویژه برای برنامه‌های صوتی که هنگام پخش در پس‌زمینه اعلان ارسال می‌کنند، مفید است. برنامه‌ها می‌توانند شنونده‌های 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);
    }
  }
}

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

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

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

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

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

CastOptions.Builder یک setRemoteToLocalEnabled ارائه می‌دهد تا بلندگوی تلفن و دستگاه‌های بلوتوث محلی را به عنوان اهداف انتقال به (transfer-to) در پنجره‌ی Output Switcher، زمانی که یک جلسه‌ی فعال Cast وجود دارد، نمایش یا پنهان کند.

کاتلین
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 فعال می‌شود. ترتیب فراخوانی‌های remote به local به شرح زیر است:

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

گسترش جریان با بلندگوها

برنامه‌های صوتی که از Output Switcher استفاده می‌کنند، این قابلیت را دارند که در طول یک جلسه Cast، با استفاده از Stream Expansion، صدا را به چندین دستگاه بلندگوی دارای قابلیت Cast گسترش دهند.

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

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

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