سوئیچر خروجی

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

نمونه زیر را برای مرجع در مورد نحوه پیاده سازی Output Switcher در برنامه صوتی خود دانلود و استفاده کنید. برای دستورالعمل‌های مربوط به نحوه اجرای نمونه، به README.md همراه مراجعه کنید.

دانلود نمونه

Output Switcher باید برای پشتیبانی از محلی به راه دور و از راه دور به محلی با استفاده از مراحل ارائه شده در این راهنما فعال باشد. هیچ مرحله اضافی برای پشتیبانی از انتقال بین بلندگوهای دستگاه محلی و دستگاه های بلوتوث جفت شده مورد نیاز نیست.

برنامه‌های صوتی برنامه‌هایی هستند که از Google Cast for Audio در تنظیمات برنامه گیرنده در کنسول برنامه‌نویس Google Cast SDK پشتیبانی می‌کنند.

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

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

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

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

نقاط ورود

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

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

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

همچنین می‌توانید با کلیک کردن روی دکمه‌های حجم فیزیکی دستگاه، ضربه زدن روی نماد تنظیمات در پایین، و ضربه زدن روی متن «Play <App Name> در <Cast Device>» فعال شود.

خلاصه مراحل

پیش نیازها

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

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

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

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

گزینه Output Switcher را در AndroidManifest.xml فعال کنید

برای فعال کردن Output Switcher، 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() فعالیت برنامه لغو ثبت کند.

با تجربه جدید ارسال محتوا با استفاده از 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);
    }
  }
}

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

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

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

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

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

CastOptions یک setRemoteToLocalEnabled برای نشان دادن یا پنهان کردن بلندگوی تلفن و دستگاه‌های بلوتوث محلی به عنوان اهداف انتقال به اهداف در گفتگوی 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 s را دریافت نخواهد کرد.

SessionTransferCallback یک فرمت از تماس های SessionManagerListener موجود است و پس از راه اندازی onSessionEnded فعال می شود. بنابراین، ترتیب تماس‌های تماس از راه دور به محلی به صورت زیر است:

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

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