سوئیچر خروجی

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

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

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

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

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

مسائل شناخته شده

  • وقتی به اعلان 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.Builder یک 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.
    }
  }
}

از راه دور به ریموت

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

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

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

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

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

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