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

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

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

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

يمكن تحديد الفواصل على كلّ من المرسِل والمستلِم، ولكن يُنصح بتحديدها في جهاز الاستقبال على الويب وجهاز استقبال 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" />
كوتلين
// 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);
        }
    }
}

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

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

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

ستعرض مكونات مختلفة لإطار العمل (لا سيّما مربع حوار Cast ووحدة التحكم الصغيرة و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 أو Web)، أو إنشاء تطبيق WebRecipient.