نظرة عامة على MediaRouteProvider

يسمح إطار عمل جهاز توجيه الوسائط لنظام التشغيل Android للشركات المصنّعة بتفعيل تشغيل المحتوى على أجهزتهم من خلال واجهة موحّدة تُسمى MediaRouteProvider. يحدد موفِّر المسار واجهة شائعة لتشغيل الوسائط على جهاز الاستقبال، ما يجعل من الممكن تشغيل الوسائط على جهازك من أي تطبيق Android يتوافق مع مسارات الوسائط.

يناقش هذا الدليل كيفية إنشاء موفِّر مسار الوسائط لجهاز الاستقبال وإتاحتها لتطبيقات تشغيل الوسائط الأخرى التي تعمل على نظام التشغيل Android. لاستخدام واجهة برمجة التطبيقات هذه، يجب أن تكون على دراية بالفئات الرئيسية MediaRouteProvider وMediaRouteProviderDescriptor وRouteController.

نظرة عامة

يتيح إطار عمل جهاز توجيه الوسائط Android لمطوّري تطبيقات الوسائط والشركات المصنّعة لأجهزة تشغيل الوسائط إمكانية الاتصال من خلال واجهة برمجة تطبيقات مشتركة وواجهة مستخدم مشتركة. يمكن لمطوّري التطبيقات الذين ينفّذون واجهة MediaRouter الربط بإطار العمل وتشغيل المحتوى على الأجهزة التي تشارك في إطار عمل جهاز توجيه الوسائط. ويمكن للشركات المصنّعة لأجهزة تشغيل الوسائط المشاركة في إطار العمل من خلال نشر MediaRouteProvider تتيح للتطبيقات الأخرى الاتصال بالوسائط وتشغيلها على أجهزة الاستقبال. يوضّح الشكل 1 كيفية اتصال أحد التطبيقات بجهاز الاستقبال من خلال إطار عمل جهاز توجيه الوسائط.

الشكل 1. نظرة عامة على كيفية توفير فئات مزوّدي مسارات الوسائط للتواصل من تطبيق وسائط إلى جهاز استقبال

عند إنشاء موفِّر مسار الوسائط لجهاز الاستقبال، يخدم الموفر الأغراض التالية:

  • يُرجى وصف إمكانات جهاز الاستقبال ونشرها حتى تتمكّن التطبيقات الأخرى من اكتشافه واستخدام ميزات التشغيل الخاصة به.
  • لفّ واجهة البرمجة لجهاز الاستقبال وآليات نقل الاتصالات التابعة له لجعل الجهاز متوافقًا مع إطار عمل جهاز توجيه الوسائط.

توزيع مزوّدي المسارات

يتم توزيع موفِّر مسار الوسائط كجزء من تطبيق Android. ويمكن إتاحة موفِّر التوجيه للتطبيقات الأخرى من خلال توسيع نطاق MediaRouteProviderService أو إضافة تضمين MediaRouteProvider مع خدمتك الخاصة وإعلان فلتر الأهداف لموفّر مسار الوسائط. تسمح هذه الخطوات للتطبيقات الأخرى باكتشاف مسار الوسائط والاستفادة منه.

ملاحظة: يمكن أن يتضمّن التطبيق الذي يحتوي على موفِّر مسار الوسائط أيضًا واجهة MediaRouter لمقدِّم التوجيه، إلا أنّ هذا الإجراء غير مطلوب.

مكتبة دعم MediaRouter

يتم تحديد واجهات برمجة تطبيقات جهاز توجيه الوسائط في مكتبة AndroidX MediaRouter يجب إضافة هذه المكتبة إلى مشروع تطوير التطبيق. ولمزيد من المعلومات حول إضافة مكتبات الدعم إلى مشروعك، يُرجى الاطّلاع على إعداد مكتبة الدعم.

تنبيه: احرص على استخدام تطبيق AndroidX لإطار عمل جهاز توجيه الوسائط. لا تستخدم حزمة android.media القديمة.

إنشاء خدمة الموفر

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

يعرض الرمز في المثال التالي بيانًا عن خدمة مقدّم خدمة مسار الوسائط وفلتر الأهداف في أحد البيان، ما يتيح إمكانية العثور على هذه الخدمة واستخدامها من خلال إطار عمل جهاز توجيه الوسائط:

<service android:name=".provider.SampleMediaRouteProviderService"
    android:label="@string/sample_media_route_provider_service"
    android:process=":mrp">
    <intent-filter>
        <action android:name="android.media.MediaRouteProviderService" />
    </intent-filter>
</service>

يعلن هذا المثال على البيان عن خدمة تضم فئات موفِّري مسار الوسائط الفعلي. يوفّر إطار عمل جهاز توجيه الوسائط Android الفئة MediaRouteProviderService لاستخدامها كبرنامج تضمين خدمة لموفّري مسارات الوسائط. يوضّح الرمز البرمجي التالي كيفية استخدام فئة برنامج التضمين هذه:

Kotlin

class SampleMediaRouteProviderService : MediaRouteProviderService() {

    override fun onCreateMediaRouteProvider(): MediaRouteProvider {
        return SampleMediaRouteProvider(this)
    }
}

Java

public class SampleMediaRouteProviderService extends MediaRouteProviderService {

    @Override
    public MediaRouteProvider onCreateMediaRouteProvider() {
        return new SampleMediaRouteProvider(this);
    }
}

تحديد إمكانيات المسارات

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

يتيح لك إطار عمل موجِّه الوسائط تحديد إمكانات مسار الوسائط ونشرها من خلال عناصر IntentFilter وكائنات MediaRouteDescriptor وMediaRouteProviderDescriptor. يوضّح هذا القسم كيفية استخدام هذه الصفوف لنشر تفاصيل مسار الوسائط للتطبيقات الأخرى.

فئات المسارات

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

  • CATEGORY_LIVE_AUDIO — إخراج صوت إلى جهاز إخراج ثانوي، مثل نظام موسيقى يعمل لاسلكيًا
  • CATEGORY_LIVE_VIDEO — إخراج الفيديو إلى جهاز إخراج ثانوي، مثل أجهزة العرض اللاسلكية
  • CATEGORY_REMOTE_PLAYBACK: يمكنك تشغيل الفيديو أو الصوت على جهاز منفصل مسؤول عن استرداد الوسائط وفك ترميزها وتشغيلها، مثل أجهزة Chromecast.

لتضمين هذه الإعدادات في وصف مسار الوسائط، يمكنك إدراجها في عنصر IntentFilter، والذي تضيفه لاحقًا إلى كائن MediaRouteDescriptor:

Kotlin

class SampleMediaRouteProvider(context: Context) : MediaRouteProvider(context) {

    companion object {
        private val CONTROL_FILTERS_BASIC: ArrayList<IntentFilter> = IntentFilter().run {
            addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
            arrayListOf(this)
        }
    }
}

Java

public final class SampleMediaRouteProvider extends MediaRouteProvider {
    private static final ArrayList<IntentFilter> CONTROL_FILTERS_BASIC;
    static {
        IntentFilter videoPlayback = new IntentFilter();
        videoPlayback.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
        CONTROL_FILTERS_BASIC = new ArrayList<IntentFilter>();
        CONTROL_FILTERS_BASIC.add(videoPlayback);
    }
}

إذا حددت هدف CATEGORY_REMOTE_PLAYBACK، عليك أيضًا تحديد أنواع الوسائط وعناصر التحكّم في التشغيل المتوافقة مع مزوّد مسار الوسائط. يوضّح القسم التالي طريقة تحديد هذه الإعدادات لجهازك.

أنواع الوسائط والبروتوكولات الخاصة بها

يجب أن يحدّد موفِّر مسار الوسائط لجهاز تشغيل عن بُعد أنواع الوسائط وبروتوكولات النقل المتوافقة معه. يمكنك تحديد هذه الإعدادات باستخدام الفئة IntentFilter والطريقتَين addDataScheme() وaddDataType() لذلك الكائن. يوضّح مقتطف الرمز التالي كيفية تحديد فلتر أهداف لإتاحة تشغيل الفيديوهات عن بُعد باستخدام بروتوكول http وhttps وبروتوكول البث في الوقت الفعلي (RTSP):

Kotlin

class SampleMediaRouteProvider(context: Context) : MediaRouteProvider(context) {

    companion object {

        private fun IntentFilter.addDataTypeUnchecked(type: String) {
            try {
                addDataType(type)
            } catch (ex: IntentFilter.MalformedMimeTypeException) {
                throw RuntimeException(ex)
            }
        }

        private val CONTROL_FILTERS_BASIC: ArrayList<IntentFilter> = IntentFilter().run {
            addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
            addAction(MediaControlIntent.ACTION_PLAY)
            addDataScheme("http")
            addDataScheme("https")
            addDataScheme("rtsp")
            addDataTypeUnchecked("video/*")
            arrayListOf(this)
        }
    }
    ...
}

Java

public final class SampleMediaRouteProvider extends MediaRouteProvider {

    private static final ArrayList<IntentFilter> CONTROL_FILTERS_BASIC;

    static {
        IntentFilter videoPlayback = new IntentFilter();
        videoPlayback.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
        videoPlayback.addAction(MediaControlIntent.ACTION_PLAY);
        videoPlayback.addDataScheme("http");
        videoPlayback.addDataScheme("https");
        videoPlayback.addDataScheme("rtsp");
        addDataTypeUnchecked(videoPlayback, "video/*");
        CONTROL_FILTERS_BASIC = new ArrayList<IntentFilter>();
        CONTROL_FILTERS_BASIC.add(videoPlayback);
    }
    ...

    private static void addDataTypeUnchecked(IntentFilter filter, String type) {
        try {
            filter.addDataType(type);
        } catch (MalformedMimeTypeException ex) {
            throw new RuntimeException(ex);
        }
    }
}

عناصر التحكّم في التشغيل

بالنسبة إلى موفِّر مسار الوسائط، الذي يوفر إمكانية التشغيل عن بُعد، يجب أن يحدّد أنواع عناصر التحكّم في الوسائط المتوافقة. في ما يلي الأنواع العامة للتحكّم التي يمكن أن توفّرها مسارات الوسائط:

  • عناصر التحكم في التشغيل، مثل التشغيل والإيقاف المؤقت والترجيع والتقديم السريع
  • ميزات قائمة المحتوى التالي التي تسمح لتطبيق الإرسال بإضافة عناصر وإزالتها من قائمة تشغيل يديرها جهاز المستلِم.
  • ميزات الجلسة: تمنع هذه الميزات إرسال التطبيقات من التدخّل في بعضها البعض من خلال ضبط جهاز المُستلِم على توفير معرّف جلسة للتطبيق الذي يطلبه، ثم التحقّق من ذلك المعرّف مع كل طلب لاحق للتحكّم في التشغيل.

يوضّح مثال الرمز التالي كيفية إنشاء فلتر أهداف لإتاحة عناصر التحكّم الأساسية في تشغيل مسار الوسائط:

Kotlin

class SampleMediaRouteProvider(context: Context) : MediaRouteProvider(context) {

    companion object {
        ...
        private val CONTROL_FILTERS_BASIC: ArrayList<IntentFilter> = run {
            val videoPlayback: IntentFilter = ...
            ...
            val playControls = IntentFilter().apply {
                addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
                addAction(MediaControlIntent.ACTION_SEEK)
                addAction(MediaControlIntent.ACTION_GET_STATUS)
                addAction(MediaControlIntent.ACTION_PAUSE)
                addAction(MediaControlIntent.ACTION_RESUME)
                addAction(MediaControlIntent.ACTION_STOP)
            }
            arrayListOf(videoPlayback, playControls)
        }
    }
    ...
}

Java

public final class SampleMediaRouteProvider extends MediaRouteProvider {
    private static final ArrayList<IntentFilter> CONTROL_FILTERS_BASIC;
    static {
        ...
        IntentFilter playControls = new IntentFilter();
        playControls.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
        playControls.addAction(MediaControlIntent.ACTION_SEEK);
        playControls.addAction(MediaControlIntent.ACTION_GET_STATUS);
        playControls.addAction(MediaControlIntent.ACTION_PAUSE);
        playControls.addAction(MediaControlIntent.ACTION_RESUME);
        playControls.addAction(MediaControlIntent.ACTION_STOP);
        CONTROL_FILTERS_BASIC = new ArrayList<IntentFilter>();
        CONTROL_FILTERS_BASIC.add(videoPlayback);
        CONTROL_FILTERS_BASIC.add(playControls);
    }
    ...
}

لمزيد من المعلومات عن الأغراض المتاحة للتحكّم في التشغيل، يُرجى الاطّلاع على الفئة MediaControlIntent.

أداة وصف MediaRouteProvider

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

يوضّح الرمز النموذجي التالي كيفية إضافة فلاتر الأهداف التي تم إنشاؤها سابقًا إلى MediaRouteProviderDescriptor وضبط الوصف لاستخدامه من خلال إطار عمل جهاز توجيه الوسائط:

Kotlin

class SampleMediaRouteProvider(context: Context) : MediaRouteProvider(context) {

    init {
        publishRoutes()
    }

    private fun publishRoutes() {
        val resources = context.resources
        val routeName: String = resources.getString(R.string.variable_volume_basic_route_name)
        val routeDescription: String = resources.getString(R.string.sample_route_description)
        // Create a route descriptor using previously created IntentFilters
        val routeDescriptor: MediaRouteDescriptor =
                MediaRouteDescriptor.Builder(VARIABLE_VOLUME_BASIC_ROUTE_ID, routeName)
                        .setDescription(routeDescription)
                        .addControlFilters(CONTROL_FILTERS_BASIC)
                        .setPlaybackStream(AudioManager.STREAM_MUSIC)
                        .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
                        .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
                        .setVolumeMax(VOLUME_MAX)
                        .setVolume(mVolume)
                        .build()
        // Add the route descriptor to the provider descriptor
        val providerDescriptor: MediaRouteProviderDescriptor =
                MediaRouteProviderDescriptor.Builder()
                        .addRoute(routeDescriptor)
                        .build()

        // Publish the descriptor to the framework
        descriptor = providerDescriptor
    }
    ...
}

Java

public SampleMediaRouteProvider(Context context) {
    super(context);
    publishRoutes();
}

private void publishRoutes() {
    Resources r = getContext().getResources();
    // Create a route descriptor using previously created IntentFilters
    MediaRouteDescriptor routeDescriptor = new MediaRouteDescriptor.Builder(
            VARIABLE_VOLUME_BASIC_ROUTE_ID,
            r.getString(R.string.variable_volume_basic_route_name))
            .setDescription(r.getString(R.string.sample_route_description))
            .addControlFilters(CONTROL_FILTERS_BASIC)
            .setPlaybackStream(AudioManager.STREAM_MUSIC)
            .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
            .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
            .setVolumeMax(VOLUME_MAX)
            .setVolume(mVolume)
            .build();
    // Add the route descriptor to the provider descriptor
    MediaRouteProviderDescriptor providerDescriptor =
            new MediaRouteProviderDescriptor.Builder()
            .addRoute(routeDescriptor)
            .build();

    // Publish the descriptor to the framework
    setDescriptor(providerDescriptor);
}

للمزيد من المعلومات عن الإعدادات المتاحة الخاصة بالواصفات، اطّلِع على المستندات المرجعية للسمتَين MediaRouteDescriptor وMediaRouteProviderDescriptor.

التحكم في المسارات

عندما يتصل تطبيق بمزوّد مسار الوسائط، يتلقى موفّر الخدمة أوامر التشغيل من خلال إطار عمل جهاز توجيه الوسائط الذي ترسله تطبيقات أخرى إلى مسارك. لمعالجة هذه الطلبات، يجب توفير فئة MediaRouteProvider.RouteController تعالج الأوامر وتعالج الاتصال الفعلي بجهاز الاستقبال.

يستدعي إطار عمل جهاز توجيه الوسائط طريقة onCreateRouteController() لموفّر المسار للحصول على مثيل من هذه الفئة ثم يوجِّه الطلبات إليه. في ما يلي الطرق الرئيسية للفئة MediaRouteProvider.RouteController، التي يجب تنفيذها مع موفِّر مسار الوسائط:

  • onSelect() — يتم استدعاء هذا الإجراء عندما يختار تطبيق مسارك للتشغيل. ويمكنك استخدام هذه الطريقة لتنفيذ أي أعمال تحضيرية قد تكون مطلوبة قبل بدء تشغيل الوسائط.
  • onControlRequest(): لإرسال أوامر تشغيل محدّدة إلى الجهاز المستلِم.
  • onSetVolume() — لإرسال طلب إلى الجهاز المستلِم لضبط مستوى صوت التشغيل على قيمة محدّدة
  • onUpdateVolume() — لإرسال طلب إلى الجهاز المستلِم لتعديل مستوى صوت التشغيل بمقدار محدّد
  • onUnselect() — يتم استدعاؤها عندما يلغي أحد التطبيقات اختيار مسار ما.
  • onRelease() — يتم استدعاء هذا الإجراء عندما لا يعود إطار العمل بحاجة إلى المسار، ما يسمح بإخلاء موارده.

يتم توجيه جميع طلبات التحكّم في التشغيل إلى طريقة onControlRequest()، باستثناء التغييرات في مستوى الصوت. يجب أن يؤدي تنفيذ هذه الطريقة إلى تحليل طلبات التحكّم والاستجابة لها بالشكل المناسب. في ما يلي مثال على تنفيذ هذه الطريقة التي تعالج الأوامر لمسار وسائط تشغيل عن بُعد:

Kotlin

private class SampleRouteController : MediaRouteProvider.RouteController() {
    ...

    override fun onControlRequest(
            intent: Intent,
            callback: MediaRouter.ControlRequestCallback?
    ): Boolean {
        return if (intent.hasCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
            val action = intent.action
            when (action) {
                MediaControlIntent.ACTION_PLAY -> handlePlay(intent, callback)
                MediaControlIntent.ACTION_ENQUEUE -> handleEnqueue(intent, callback)
                MediaControlIntent.ACTION_REMOVE -> handleRemove(intent, callback)
                MediaControlIntent.ACTION_SEEK -> handleSeek(intent, callback)
                MediaControlIntent.ACTION_GET_STATUS -> handleGetStatus(intent, callback)
                MediaControlIntent.ACTION_PAUSE -> handlePause(intent, callback)
                MediaControlIntent.ACTION_RESUME -> handleResume(intent, callback)
                MediaControlIntent.ACTION_STOP -> handleStop(intent, callback)
                MediaControlIntent.ACTION_START_SESSION -> handleStartSession(intent, callback)
                MediaControlIntent.ACTION_GET_SESSION_STATUS ->
                    handleGetSessionStatus(intent, callback)
                MediaControlIntent.ACTION_END_SESSION -> handleEndSession(intent, callback)
                else -> false
            }.also {
                Log.d(TAG, sessionManager.toString())
            }
        } else {
            false
        }
    }
    ...
}

Java

private final class SampleRouteController extends
        MediaRouteProvider.RouteController {
    ...

    @Override
    public boolean onControlRequest(Intent intent, ControlRequestCallback callback) {

        String action = intent.getAction();

        if (intent.hasCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
            boolean success = false;
            if (action.equals(MediaControlIntent.ACTION_PLAY)) {
                success = handlePlay(intent, callback);
            } else if (action.equals(MediaControlIntent.ACTION_ENQUEUE)) {
                success = handleEnqueue(intent, callback);
            } else if (action.equals(MediaControlIntent.ACTION_REMOVE)) {
                success = handleRemove(intent, callback);
            } else if (action.equals(MediaControlIntent.ACTION_SEEK)) {
                success = handleSeek(intent, callback);
            } else if (action.equals(MediaControlIntent.ACTION_GET_STATUS)) {
                success = handleGetStatus(intent, callback);
            } else if (action.equals(MediaControlIntent.ACTION_PAUSE)) {
                success = handlePause(intent, callback);
            } else if (action.equals(MediaControlIntent.ACTION_RESUME)) {
                success = handleResume(intent, callback);
            } else if (action.equals(MediaControlIntent.ACTION_STOP)) {
                success = handleStop(intent, callback);
            } else if (action.equals(MediaControlIntent.ACTION_START_SESSION)) {
                success = handleStartSession(intent, callback);
            } else if (action.equals(MediaControlIntent.ACTION_GET_SESSION_STATUS)) {
                success = handleGetSessionStatus(intent, callback);
            } else if (action.equals(MediaControlIntent.ACTION_END_SESSION)) {
                success = handleEndSession(intent, callback);
            }

            Log.d(TAG, sessionManager.toString());
            return success;
        }
        return false;
    }
    ...
}

من المهم فهم أنّ الفئة MediaRouteProvider.RouteController مصمّمة ليكون بمثابة برنامج تضمين لواجهة برمجة التطبيقات مع معدات تشغيل الوسائط. ويعتمد تنفيذ الطرق في هذه الفئة تمامًا على الواجهة الآلية التي يوفّرها الجهاز المستلِم.

نموذج التعليمات البرمجية

يعرض نموذج MediaRouter طريقة إنشاء موفِّر مسار مخصّص للوسائط.