"أداة التبديل بين أجهزة التشغيل" هي ميزة في حزمة تطوير البرامج (SDK) لمنصة Cast تتيح نقل المحتوى بسلاسة بين التشغيل على الجهاز المحلي والتشغيل عن بُعد، بدءًا من الإصدار Android 13. الهدف
هو مساعدة تطبيقات الإرسال في التحكّم بسهولة وسرعة في مكان تشغيل المحتوى.
تستخدم "أداة التبديل بين أجهزة التشغيل" مكتبة MediaRouter لتبديل تشغيل المحتوى بين مكبّر صوت الهاتف والأجهزة المقترنة التي تتضمّن بلوتوث والأجهزة البعيدة التي تتيح استخدام Google Cast. يمكن تقسيم حالات الاستخدام إلى السيناريوهات التالية:
نزِّل نموذج تطبيق CastVideos-android واستخدِمه لمعرفة كيفية تنفيذ ميزة "تبديل المُخرجات" في تطبيقك.
يجب تفعيل ميزة "تبديل المخرجات" لإتاحة نقل المحتوى من جهاز محلي إلى جهاز عن بُعد، ومن جهاز عن بُعد إلى جهاز محلي، ومن جهاز عن بُعد إلى جهاز آخر عن بُعد، وذلك باتّباع الخطوات الموضّحة في هذا الدليل. ما مِن خطوات إضافية مطلوبة لإتاحة نقل الصوت بين مكبّرات الصوت في الجهاز المحلي والأجهزة المقترنة التي تتضمّن بلوتوث.
واجهة مستخدم "أداة التبديل بين أجهزة التشغيل"
يعرض "مبدّل المخرجات" الأجهزة المحلية والأجهزة البعيدة المتاحة بالإضافة إلى حالات الأجهزة الحالية، بما في ذلك ما إذا تم اختيار الجهاز أو إذا كان يتصل، ومستوى الصوت الحالي. إذا كانت هناك أجهزة أخرى بالإضافة إلى الجهاز الحالي، يتيح لك النقر على جهاز آخر نقل تشغيل الوسائط إلى الجهاز المحدّد.

المشاكل المعروفة
- سيتم تجاهل جلسات الوسائط التي تم إنشاؤها للتشغيل على الجهاز وإعادة إنشائها عند التبديل إلى إشعار Cast SDK.
نقاط الإدخال
إشعار الوسائط
إذا نشر أحد التطبيقات إشعارًا بشأن الوسائط يتضمّن
MediaSession
للتشغيل على الجهاز (التشغيل على الجهاز)، سيعرض أعلى يسار إشعار الوسائط
شريحة إشعار تتضمّن اسم الجهاز (مثل مكبّر صوت الهاتف) الذي
يتم تشغيل المحتوى عليه حاليًا. يؤدي النقر على شريحة الإشعار إلى فتح واجهة مستخدم النظام الخاصة بمربّع حوار "أداة تبديل الإخراج".

إعدادات مستوى الصوت
يمكن أيضًا تفعيل واجهة مستخدم نظام مربّع حوار "مبدّل المخرجات" من خلال النقر على أزرار التحكّم بمستوى الصوت على الجهاز، ثم النقر على رمز الإعدادات في أسفل الشاشة، ثم النقر على النص "تشغيل <اسم التطبيق> على <جهاز البث>".

ملخّص الخطوات
- التأكّد من استيفاء المتطلبات الأساسية
- تفعيل "أداة التبديل بين أجهزة التشغيل" في AndroidManifest.xml
- تعديل SessionManagerListener للبث في الخلفية
- إضافة إمكانية الوصول من جهاز عن بُعد إلى جهاز آخر عن بُعد
- ضبط علامة setRemoteToLocalEnabled
- مواصلة التشغيل على الجهاز
المتطلبات الأساسية
- نقل تطبيق Android الحالي إلى AndroidX
- يُرجى تحديث
build.gradleفي تطبيقك لاستخدام الحدّ الأدنى المطلوب من إصدار حزمة تطوير البرامج (SDK) لمرسِل Android من أجل "مبدّل المخرجات":dependencies { ... implementation 'com.google.android.gms:play-services-cast-framework:21.2.0' ... }
- يتيح التطبيق إشعارات الوسائط.
- جهاز يعمل بنظام التشغيل Android 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() ); }

طريقة عمل إشعارات تطبيق الفيديو
يجب أن تتّبع تطبيقات الفيديو أو الصوت التي لا تتيح التشغيل المحلي في الخلفية سلوكًا محدّدًا لإشعارات الوسائط لتجنُّب المشاكل المتعلّقة بإرسال أوامر الوسائط في الحالات التي لا يتوفّر فيها التشغيل:
- يتم نشر إشعار الوسائط عند تشغيل الوسائط محليًا ويكون التطبيق في المقدمة.
- إيقاف التشغيل المحلي مؤقتًا وإغلاق الإشعار عندما يكون التطبيق في الخلفية
- عندما يعود التطبيق إلى المقدّمة، يجب استئناف التشغيل المحلي وإعادة نشر الإشعار.
تفعيل "أداة التبديل بين أجهزة التشغيل" في AndroidManifest.xml
لتفعيل "أداة التبديل بين أجهزة التشغيل"، يجب إضافة
MediaTransferReceiver
إلى AndroidManifest.xml في التطبيق. وإذا لم يكن كذلك، لن يتم تفعيل الميزة وستكون علامة ميزة "التحويل من بُعد إلى محلي" غير صالحة أيضًا.
<application>
...
<receiver
android:name="androidx.mediarouter.media.MediaTransferReceiver"
android:exported="true">
</receiver>
...
</application>
MediaTransferReceiver هو جهاز استقبال بث يتيح نقل الوسائط بين الأجهزة باستخدام واجهة مستخدم النظام. لمزيد من المعلومات، يمكنك الاطّلاع على مرجع MediaTransferReceiver.
من الجهاز المحلي إلى الجهاز البعيد
عندما يبدّل المستخدم التشغيل من الجهاز إلى جهاز آخر عن بُعد، ستبدأ حزمة تطوير البرامج (SDK) لتطبيق Cast
جلسة Cast تلقائيًا. ومع ذلك، يجب أن تتعامل التطبيقات مع التبديل من
المحلي إلى البعيد، على سبيل المثال إيقاف التشغيل المحلي
وتحميل الوسائط على جهاز Cast. يجب أن تستمع التطبيقات إلى طلبات Cast
SessionManagerListener،
باستخدام عمليات الاستدعاء
onSessionStarted()
و
onSessionEnded()،
والتعامل مع الإجراء عند تلقّي عمليات الاستدعاء
SessionManager
من Cast. يجب أن تضمن التطبيقات أنّ عمليات الاستدعاء هذه لا تزال نشطة عند فتح مربّع حوار "محوِّل الإخراج" وعدم تشغيل التطبيق في المقدّمة.
تعديل SessionManagerListener للبث في الخلفية
تتيح تجربة Cast القديمة إمكانية البث من جهاز محلي إلى جهاز عن بُعد عندما يكون التطبيق
في المقدّمة. تبدأ تجربة البث المعتادة عندما ينقر المستخدمون على رمز البث
في التطبيق ويختارون جهازًا لبث الوسائط. في هذه الحالة، يحتاج التطبيق إلى
التسجيل في
SessionManagerListener،
في onCreate() أو
onStart()
وإلغاء تسجيل أداة معالجة الحدث في
onStop()
أو
onDestroy()
لنشاط التطبيق.
من خلال التجربة الجديدة للبث باستخدام "أداة تبديل المخرجات"، يمكن للتطبيقات بدء
البث عندما تكون في الخلفية. ويُعدّ ذلك مفيدًا بشكل خاص لتطبيقات الصوت التي تنشر إشعارات عند تشغيلها في الخلفية. يمكن للتطبيقات تسجيل مستمعي 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); } } }
من خلال هذا التحديث، تعمل ميزة "من الجهاز المحلي إلى الجهاز البعيد" بالطريقة نفسها التي تعمل بها ميزة البث التقليدية عندما يكون التطبيق في الخلفية، ولا يتطلّب التبديل من الأجهزة التي تتضمّن بلوتوث إلى أجهزة Google Cast أي عمل إضافي.
من جهاز التحكّم عن بُعد إلى الجهاز المحلي
تتيح "أداة التبديل بين أجهزة التشغيل" إمكانية نقل المحتوى من التشغيل عن بُعد إلى مكبّر صوت الهاتف أو جهاز يتضمّن بلوتوث محلي. ويمكن تفعيل هذه الميزة من خلال ضبط علامة
setRemoteToLocalEnabled
على true في CastOptions.
في الحالات التي ينضم فيها جهاز المُرسِل الحالي إلى جلسة حالية مع
عدة مُرسِلين ويحتاج التطبيق إلى التحقّق مما إذا كان مسموحًا بنقل الوسائط الحالية
محليًا، يجب أن تستخدم التطبيقات دالة الاستدعاء onTransferred
الخاصة بـ SessionTransferCallback
للتحقّق من SessionState.
ضبط العلامة setRemoteToLocalEnabled
توفّر واجهة برمجة التطبيقات CastOptions.Builder
setRemoteToLocalEnabled لإظهار أو إخفاء مكبّر صوت الهاتف وأجهزة البلوتوث المحلية كأهداف
نقل في مربّع حوار "أداة تبديل المخرجات" عندما تكون هناك جلسة بث محتوى نشطة.
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. ترتيب عمليات الاستدعاء
من بُعد إلى محلي هو:
onTransferringonSessionEndingonSessionEndedonTransferred
بما أنّه يمكن فتح "أداة تبديل المخرجات" من خلال شريحة إشعار الوسائط عندما يكون التطبيق قيد التشغيل في الخلفية ويتم بث المحتوى، يجب أن تتعامل التطبيقات مع عملية النقل إلى الجهاز المحلي بشكل مختلف حسب ما إذا كانت تتيح التشغيل في الخلفية أم لا. في حال تعذُّر النقل، سيتم تنشيط 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. } } }
من جهاز تحكّم عن بُعد إلى جهاز تحكّم عن بُعد
تتيح "أداة التبديل بين أجهزة التشغيل" إمكانية توسيع نطاق البث ليشمل عدة أجهزة مكبّرات صوت تتيح استخدام Google Cast لتطبيقات الصوت باستخدام ميزة "توسعة البث".
التطبيقات الصوتية هي تطبيقات متوافقة مع ميزة "Google Cast للصوت" في إعدادات تطبيق الاستقبال في وحدة تحكّم مطوّري حزمة تطوير البرامج (SDK) لـ Google Cast

ميزة "توسعة البث" مع مكبّرات الصوت
تتيح ميزة "توسعة البث" لتطبيقات الصوت التي تستخدم "أداة تبديل المخرجات" إمكانية توسيع نطاق الصوت ليشمل عدة مكبّرات صوت تتيح استخدام Google Cast أثناء جلسة بث.
تتوفّر هذه الميزة على منصة Cast ولا تتطلّب أي تغييرات إضافية إذا كان التطبيق يستخدم واجهة المستخدم التلقائية. إذا تم استخدام واجهة مستخدم مخصّصة، يجب أن يعدّل التطبيق واجهة المستخدم ليعكس أنّ التطبيق يبث المحتوى إلى مجموعة.

للحصول على اسم المجموعة الموسّعة الجديد أثناء توسعة البث،
يجب تسجيل
Cast.Listener
باستخدام
CastSession#addCastListener.
بعد ذلك، يمكنك الاتصال بالرقم
CastSession#getCastDevice()
خلال معاودة الاتصال من خلال onDeviceNameChanged.
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); } }
اختبار الاتصال بين جهازين عن بُعد
لاختبار الميزة، اتّبِع الخطوات التالية:
- يمكنك بث المحتوى إلى جهاز يتيح استخدام Google Cast باستخدام البث التقليدي أو من خلال البث من جهاز محلي إلى جهاز عن بُعد.
- افتح "مبدّل المخرجات" باستخدام إحدى نقاط الدخول.
- انقر على جهاز آخر يتيح استخدام Google Cast، وستوسّع تطبيقات الصوت المحتوى ليشمل الجهاز الإضافي، ما يؤدي إلى إنشاء مجموعة ديناميكية.
- انقر على الجهاز الذي يتيح استخدام Google Cast مرة أخرى، وستتم إزالته من المجموعة الديناميكية.