إضافة ميزات متقدّمة إلى تطبيق Android

الفواصل الإعلانية

توفّر حزمة تطوير البرامج (SDK) لمُرسِل Android دعمًا للفواصل الإعلانية والإعلانات المصاحبة ضمن بث وسائط معيّن.

راجِع نظرة عامة على الفواصل الإعلانية لمستلِمي الويب لمزيد من المعلومات حول طريقة عمل الفواصل الإعلانية.

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

على نظام التشغيل Android، حدِّد الفواصل الإعلانية في أمر تحميل باستخدام AdBreakClipInfo وAdBreakInfo:

كولين
val breakClip1: AdBreakClipInfo =
    AdBreakClipInfo.Builder("bc0")
        .setTitle("Clip title")
        .setPosterUrl("https://www.some.url")
        .setDuration(60000)
        .setWhenSkippableInMs(5000)  // Set this field so that the ad is skippable
        .build()

val breakClip2: AdBreakClipInfo = …
val breakClip3: AdBreakClipInfo = …

val break1: AdBreakClipInfo =
    AdBreakInfo.Builder(/* playbackPositionInMs= */ 10000)
        .setId("b0")
        .setBreakClipIds({"bc0","bc1","bc2"})
        …
        .build()

val mediaInfo: MediaInfo = MediaInfo.Builder()
    …
    .setAdBreaks({break1})
    .setAdBreakClips({breakClip1, breakClip2, breakClip3})
    .build()

val mediaLoadRequestData: MediaLoadRequestData = MediaInfo.Builder()
    …
    .setMediaInfo(mediaInfo)
    .build()

remoteMediaClient.load(mediaLoadRequestData)
Java
AdBreakClipInfo breakClip1 =
    new AdBreakClipInfo.Builder("bc0")
        .setTitle("Clip title")
        .setPosterUrl("https://www.some.url")
        .setDuration(60000)
        .setWhenSkippableInMs(5000)  // Set this field so that the ad is skippable
        .build();

AdBreakClipInfo breakClip2 = …
AdBreakClipInfo breakClip3 = …

AdBreakInfo break1 =
    new AdBreakInfo.Builder(/* playbackPositionInMs= */ 10000)
        .setId("b0")
        .setBreakClipIds({"bc0","bc1","bc2"})
        …
        .build();

MediaInfo mediaInfo = new MediaInfo.Builder()
    …
    .setAdBreaks({break1})
    .setAdBreakClips({breakClip1, breakClip2, breakClip3})
    .build();

MediaLoadRequestData mediaLoadRequestData = new MediaInfo.Builder()
    …
    .setMediaInfo(mediaInfo)
    .build();

remoteMediaClient.load(mediaLoadRequestData);

إضافة إجراءات مخصّصة

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

// In AndroidManifest.xml
<receiver android:name="com.example.MyMediaIntentReceiver" />
Kotlin
// In your OptionsProvider
var mediaOptions = CastMediaOptions.Builder()
    .setMediaIntentReceiverClassName(MyMediaIntentReceiver::class.java.name)
    .build()

// Implementation of MyMediaIntentReceiver
internal class MyMediaIntentReceiver : MediaIntentReceiver() {
    override fun onReceiveActionTogglePlayback(currentSession: Session) {
    }

    override fun onReceiveActionMediaButton(currentSession: Session, intent: Intent) {
    }

    override fun onReceiveOtherAction(context: Context?, action: String, intent: Intent) {
    }
}
Java
// In your OptionsProvider
CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
        .setMediaIntentReceiverClassName(MyMediaIntentReceiver.class.getName())
        .build();

// Implementation of MyMediaIntentReceiver
class MyMediaIntentReceiver extends MediaIntentReceiver {
    @Override
    protected void onReceiveActionTogglePlayback(Session currentSession) {
    }

    @Override
    protected void onReceiveActionMediaButton(Session currentSession, Intent intent) {
    }

    @Override
    protected void onReceiveOtherAction(Context context, String action, Intent intent) {
    }
}

إضافة قناة مخصّصة

ليتواصل تطبيق المرسِل مع تطبيق جهاز الاستقبال، يحتاج تطبيقك إلى إنشاء قناة مخصّصة. يمكن للمرسل استخدام القناة المخصّصة لإرسال رسائل سلسلة إلى المتلقي. يتم تحديد مساحة اسم فريدة لكل قناة مخصّصة، ويجب أن تبدأ بالبادئة urn:x-cast:، على سبيل المثال، urn:x-cast:com.example.custom. من الممكن أن يكون لديك عدة قنوات مخصّصة، لكل منها مساحة اسم فريدة. يمكن لتطبيق الاستقبال أيضًا إرسال الرسائل واستلامها باستخدام مساحة الاسم نفسها.

يتم تنفيذ القناة المخصّصة باستخدام واجهة Cast.MessageReceivedCallback:

كولين
class HelloWorldChannel : MessageReceivedCallback {
    val namespace: String
        get() = "urn:x-cast:com.example.custom"

    override fun onMessageReceived(castDevice: CastDevice, namespace: String, message: String) {
        Log.d(TAG, "onMessageReceived: $message")
    }
}
Java
class HelloWorldChannel implements Cast.MessageReceivedCallback {
    public String getNamespace() {
        return "urn:x-cast:com.example.custom";
    }
    @Override
    public void onMessageReceived(CastDevice castDevice, String namespace, String message) {
        Log.d(TAG, "onMessageReceived: " + message);
    }
}

بعد ربط تطبيق المُرسِل بتطبيق المُستلِم، يمكن إنشاء القناة المخصّصة باستخدام الطريقة setMessageReceivedCallbacks:

كولين
try {
    mCastSession.setMessageReceivedCallbacks(
        mHelloWorldChannel.namespace,
        mHelloWorldChannel)
} catch (e: IOException) {
    Log.e(TAG, "Exception while creating channel", e)
}
Java
try {
    mCastSession.setMessageReceivedCallbacks(
            mHelloWorldChannel.getNamespace(),
            mHelloWorldChannel);
} catch (IOException e) {
    Log.e(TAG, "Exception while creating channel", e);
}

بعد إنشاء القناة المخصّصة، يمكن للمرسِل استخدام طريقة sendMessage لإرسال رسائل السلسلة إلى المُستلِم عبر هذه القناة:

كولين
private fun sendMessage(message: String) {
    if (mHelloWorldChannel != null) {
        try {
            mCastSession.sendMessage(mHelloWorldChannel.namespace, message)
                .setResultCallback { status ->
                    if (!status.isSuccess) {
                        Log.e(TAG, "Sending message failed")
                    }
                }
        } catch (e: Exception) {
            Log.e(TAG, "Exception while sending message", e)
        }
    }
}
Java
private void sendMessage(String message) {
    if (mHelloWorldChannel != null) {
        try {
            mCastSession.sendMessage(mHelloWorldChannel.getNamespace(), message)
                .setResultCallback( status -> {
                    if (!status.isSuccess()) {
                        Log.e(TAG, "Sending message failed");
                    }
                });
        } catch (Exception e) {
            Log.e(TAG, "Exception while sending message", e);
        }
    }
}

إتاحة التشغيل التلقائي

راجِع قسم واجهات برمجة تطبيقات التشغيل التلقائي والإضافة في قائمة المحتوى التالي.

إلغاء اختيار الصور للتطبيقات المصغّرة لتجربة المستخدم

وتعرض العديد من عناصر إطار العمل (أي مربع حوار البث ووحدة التحكّم الصغيرة وUIMediaController في حال ضبط هذه الإعدادات) العمل الفني لوسائط البث الحالية. عادةً ما يتم تضمين عناوين URL الخاصة بالصورة الفنية للصورة في MediaMetadata للوسائط، ولكن قد يكون لتطبيق المرسِل مصدر بديل لعناوين URL.

تحدِّد الفئة ImagePicker وسيلة لاختيار صورة مناسبة من قائمة الصور في MediaMetadata، استنادًا إلى استخدام الصورة، مثل الصورة المصغّرة للإشعار أو خلفية ملء الشاشة. إنّ طريقة تنفيذ ImagePicker التلقائية تختار دائمًا الصورة الأولى، أو تعرض القيمة "فارغ" إذا لم تتوفّر أي صورة في MediaMetadata. يمكن لتطبيقك استخدام الفئة الفرعية ImagePicker وإلغاء طريقة onPickImage(MediaMetadata, ImageHints) لتوفير طريقة تنفيذ بديلة، ثم اختيار تلك الفئة الفرعية باستخدام طريقة setImagePicker في CastMediaOptions.Builder. ImageHints يقدِّم تلميحات إلى ImagePicker عن نوع وحجم الصورة التي سيتم اختيارها للعرض في واجهة المستخدم.

تخصيص مربّعات حوار البث

إدارة دورة حياة الجلسة

تُعد SessionManager المكان المركزي لإدارة مراحل نشاط الجلسات. يستمع "SessionManager" إلى نظام Android MediaRouter لتوجيه تغييرات حالة الاختيار لبدء الجلسات واستئنافها وإنهائها. عند اختيار مسار، سينشئ SessionManager كائن Session ويحاول بدؤه أو استئنافه. في حال إلغاء اختيار مسار، سيتم إنهاء الجلسة الحالية من خلال SessionManager.

لذلك، لضمان أن SessionManager يدير دورات حياة الجلسة بشكل صحيح، عليك التأكّد مما يلي:

بناءً على كيفية إنشاء مربعات حوار البث، قد تحتاج إلى تنفيذ إجراءات إضافية:

  • إذا أنشأت مربّعات حوار بثّ باستخدام MediaRouteChooserDialog وMediaRouteControllerDialog، ستعدّل مربّعات الحوار هذه اختيار المسار في MediaRouter تلقائيًا، وليس عليك اتّخاذ أيّ إجراء.
  • في حال إعداد زر البث باستخدام CastButtonFactory.setUpMediaRouteButton(Context, Menu, int) أو CastButtonFactory.setUpMediaRouteButton(Context, MediaRouteButton)، سيتم إنشاء مربّعات الحوار باستخدام MediaRouteChooserDialog وMediaRouteControllerDialog، وبالتالي لا حاجة إلى تنفيذ أي إجراء أيضًا.
  • في حالات أخرى، سيتم إنشاء مربّعات حوار مخصّصة للبثّ، لذا عليك اتّباع التعليمات الواردة أعلاه لتعديل حالة اختيار المسار في MediaRouter.

حالة الأجهزة

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

إذا كنت تستخدم سياسة MediaRouteChooserDialog التلقائية، لن يتم التعامل مع حالة الأجهزة التي لا تتضمّن أي أجهزة.

الخطوات التالية

وبهذا نكون قد انتهينا من تحديد الميزات التي يمكنك إضافتها إلى تطبيق Android Sender. يمكنك الآن إنشاء تطبيق للمرسل لنظام أساسي آخر (iOS أو الويب)، أو إنشاء تطبيق مستقبِل الويب.