في صميم مكتبة ExoPlayer، تتوفر واجهة Player. يتيح Player
استخدام وظائف مشغّل الوسائط التقليدية العالية المستوى، مثل إمكانية
تخزين الوسائط مؤقتًا وتشغيلها وإيقافها مؤقتًا والبحث فيها. تم تصميم عملية التنفيذ التلقائية ExoPlayer لتقديم افتراضات قليلة حول نوع الوسائط التي يتم تشغيلها، وكيفية تخزينها ومكانها، وكيفية عرضها (وبالتالي فرض قيود قليلة عليها). بدلاً من تنفيذ تحميل الوسائط وعرضها مباشرةً،
تفوّض عمليات تنفيذ ExoPlayer هذا العمل إلى المكوّنات التي يتم إدخالها
عند إنشاء مشغّل أو عند تمرير مصادر وسائط جديدة إلى المشغّل.
المكوّنات الشائعة في جميع عمليات تنفيذ ExoPlayer هي:
MediaSourceالتي تحدّد الوسائط المطلوب تشغيلها، وتحميل الوسائط، والتي يمكن قراءة الوسائط المحمَّلة منها. يتم إنشاء مثيلMediaSourceمنMediaItemبواسطةMediaSource.Factoryداخل المشغّل. ويمكن أيضًا تمريرها مباشرةً إلى المشغّل باستخدام واجهة برمجة تطبيقات قائمة التشغيل المستندة إلى مصدر الوسائط.- مثيل
MediaSource.FactoryيحوّلMediaItemإلىMediaSource. يتم إدخالMediaSource.Factoryعند إنشاء المشغّل. Rendererمثيلات تعرض مكوّنات فردية للوسائط ويتم إدخالها عند إنشاء المشغّل.-
TrackSelectorيختار المقاطع الصوتية التي يوفّرهاMediaSourceليتم تشغيلها على كلRendererمتاح. يتم إدخالTrackSelectorعند إنشاء المشغّل. -
LoadControlيتحكّم في وقت تخزينMediaSourceالمزيد من الوسائط مؤقتًا ومقدار الوسائط التي يتم تخزينها مؤقتًا يتم إدخالLoadControlعند إنشاء المشغّل. -
LivePlaybackSpeedControlيتحكّم في سرعة التشغيل أثناء عمليات التشغيل المباشر للسماح للمشغّل بالبقاء قريبًا من الإزاحة المباشرة التي تم ضبطها يتم إدخالLivePlaybackSpeedControlعند إنشاء المشغّل.
يتوفّر مفهوم إدخال المكوّنات التي تنفّذ أجزاء من وظائف المشغّل في جميع أنحاء المكتبة. تفوّض عمليات التنفيذ التلقائية لبعض المكوّنات العمل إلى مكوّنات أخرى تم إدخالها. يتيح ذلك استبدال العديد من المكوّنات الفرعية بشكل فردي بعمليات تنفيذ تم ضبطها بطريقة مخصّصة.
تخصيص المشغّل
في ما يلي بعض الأمثلة الشائعة على تخصيص المشغّل من خلال إدخال المكوّنات.
ضبط حزم الشبكات
لدينا صفحة حول تخصيص حزم الشبكات التي تستخدمها ExoPlayer.
تخزين البيانات المحمَّلة من الشبكة مؤقتًا
اطّلِع على أدلة التخزين المؤقت الفوري وتنزيل الوسائط.
تخصيص التفاعلات مع الخادم
قد ترغب بعض التطبيقات في اعتراض طلبات واستجابات HTTP. قد تحتاج إلى إدخال عناوين طلبات مخصّصة، وقراءة عناوين استجابة الخادم، وتعديل معرّفات الموارد المنتظمة (URI) للطلبات، وما إلى ذلك. على سبيل المثال، قد تتم مصادقة تطبيقك من خلال إدخال رمز مميّز كعنوان عند طلب أجزاء الوسائط.
يوضّح المثال التالي كيفية تنفيذ هذه السلوكيات من خلال إدخال
DataSource.Factory مخصّص في DefaultMediaSourceFactory:
Kotlin
val dataSourceFactory = DataSource.Factory { val dataSource = httpDataSourceFactory.createDataSource() // Set a custom authentication request header. dataSource.setRequestProperty("Header", "Value") dataSource } val player = ExoPlayer.Builder(context) .setMediaSourceFactory( DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory) ) .build()
Java
DataSource.Factory dataSourceFactory = () -> { HttpDataSource dataSource = httpDataSourceFactory.createDataSource(); // Set a custom authentication request header. dataSource.setRequestProperty("Header", "Value"); return dataSource; }; ExoPlayer player = new ExoPlayer.Builder(context) .setMediaSourceFactory( new DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory)) .build();
في مقتطف الرمز السابق، يتضمّن HttpDataSource الذي تم إدخاله العنوان
"Header: Value" في كل طلب HTTP. هذا السلوك ثابت لكل تفاعل مع مصدر HTTP.
للحصول على نهج أكثر دقة، يمكنك إدخال سلوك في الوقت المناسب باستخدام
ResolvingDataSource. يوضّح مقتطف الرمز التالي كيفية إدخال عناوين الطلبات قبل التفاعل مع مصدر HTTP مباشرةً:
Kotlin
val dataSourceFactory: DataSource.Factory = ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec -> // Provide just-in-time request headers. dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri)) }
Java
DataSource.Factory dataSourceFactory = new ResolvingDataSource.Factory( httpDataSourceFactory, // Provide just-in-time request headers. dataSpec -> dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri)));
يمكنك أيضًا استخدام ResolvingDataSource لإجراء تعديلات في الوقت المناسب
على معرّف الموارد المنتظم (URI)، كما هو موضّح في المقتطف التالي:
Kotlin
val dataSourceFactory: DataSource.Factory = ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec -> // Provide just-in-time URI resolution logic. dataSpec.withUri(resolveUri(dataSpec.uri)) }
Java
DataSource.Factory dataSourceFactory = new ResolvingDataSource.Factory( httpDataSourceFactory, // Provide just-in-time URI resolution logic. dataSpec -> dataSpec.withUri(resolveUri(dataSpec.uri)));
تخصيص معالجة الأخطاء
يتيح تنفيذ LoadErrorHandlingPolicy مخصّص للتطبيقات تخصيص طريقة تفاعل ExoPlayer مع أخطاء التحميل. على سبيل المثال، قد يحتاج التطبيق إلى إيقاف المحاولة بسرعة بدلاً من إعادة المحاولة عدة مرات، أو قد يحتاج إلى تخصيص منطق التراجع الذي يتحكّم في مدة انتظار المشغّل بين كل إعادة محاولة. يوضّح المقتطف التالي كيفية تنفيذ منطق مخصّص للتراجع:
Kotlin
val loadErrorHandlingPolicy: LoadErrorHandlingPolicy = object : DefaultLoadErrorHandlingPolicy() { override fun getRetryDelayMsFor( loadErrorInfo: LoadErrorHandlingPolicy.LoadErrorInfo ): Long { // Implement custom back-off logic here. return 0 } } val player = ExoPlayer.Builder(context) .setMediaSourceFactory( DefaultMediaSourceFactory(context).setLoadErrorHandlingPolicy(loadErrorHandlingPolicy) ) .build()
Java
LoadErrorHandlingPolicy loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy() { @Override public long getRetryDelayMsFor(LoadErrorHandlingPolicy.LoadErrorInfo loadErrorInfo) { // Implement custom back-off logic here. return 0; } }; ExoPlayer player = new ExoPlayer.Builder(context) .setMediaSourceFactory( new DefaultMediaSourceFactory(context) .setLoadErrorHandlingPolicy(loadErrorHandlingPolicy)) .build();
تحتوي الوسيطة LoadErrorInfo على مزيد من المعلومات حول عملية التحميل التي تعذّر إجراؤها لتخصيص المنطق استنادًا إلى نوع الخطأ أو الطلب الذي تعذّر إجراؤه.
تخصيص علامات الاستخراج
يمكن استخدام علامات الاستخراج لتخصيص كيفية استخراج التنسيقات الفردية من الوسائط التدريجية. ويمكن ضبطها على DefaultExtractorsFactory الذي يتم تقديمه إلى DefaultMediaSourceFactory. يمرّر المثال التالي علامة تتيح البحث المستند إلى الفهرس لملفات MP3.
Kotlin
val extractorsFactory = DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING) val player = ExoPlayer.Builder(context) .setMediaSourceFactory(DefaultMediaSourceFactory(context, extractorsFactory)) .build()
Java
DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING); ExoPlayer player = new ExoPlayer.Builder(context) .setMediaSourceFactory(new DefaultMediaSourceFactory(context, extractorsFactory)) .build();
تفعيل البحث عن معدل نقل البيانات الثابت
بالنسبة إلى أحداث البث بتنسيقات MP3 وADTS وAMR، يمكنك تفعيل البحث التقريبي باستخدام افتراض معدل نقل بيانات ثابت مع علامات FLAG_ENABLE_CONSTANT_BITRATE_SEEKING.
يمكن ضبط هذه العلامات لأدوات استخراج فردية باستخدام طرق DefaultExtractorsFactory.setXyzExtractorFlags الفردية كما هو موضّح سابقًا. لتفعيل البحث عن معدل نقل البيانات الثابت لجميع أدوات الاستخراج التي تتيح ذلك، استخدِم DefaultExtractorsFactory.setConstantBitrateSeekingEnabled.
Kotlin
val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)
Java
DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);
ويمكن بعد ذلك إدخال ExtractorsFactory باستخدام DefaultMediaSourceFactory كما هو موضّح في قسم تخصيص علامات الاستخراج سابقًا.
تفعيل قائمة انتظار المخزن المؤقت غير المتزامن
"قائمة انتظار المخزن المؤقت غير المتزامن" هي تحسين في مسار عرض ExoPlayer، والذي يشغّل مثيلات MediaCodec في الوضع غير المتزامن ويستخدم سلاسل محادثات إضافية لجدولة فك ترميز البيانات وعرضها. ويمكن أن يؤدي تفعيلها إلى تقليل عدد اللقطات التي تم إسقاطها وحالات نقص البيانات الصوتية.
يتم تفعيل ميزة "إضافة قائمة انتظار المخزن المؤقت بشكل غير متزامن" تلقائيًا على الأجهزة التي تعمل بنظام التشغيل Android 12 (مستوى واجهة برمجة التطبيقات 31) أو الإصدارات الأحدث، ويمكن تفعيلها يدويًا بدءًا من الإصدار Android 6.0 (مستوى واجهة برمجة التطبيقات 23). ننصحك بتفعيل الميزة على أجهزة معيّنة تلاحظ فيها انخفاضًا في عدد اللقطات أو انقطاعًا في الصوت، خاصةً عند تشغيل محتوى محميّ بإدارة الحقوق الرقمية أو محتوى يتضمّن عدد اللقطات في الثانية كبيرًا.
في أبسط الحالات، عليك إدخال DefaultRenderersFactory إلى
المشغّل على النحو التالي:
Kotlin
val renderersFactory = DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing() val exoPlayer = ExoPlayer.Builder(context, renderersFactory).build()
Java
DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing(); ExoPlayer exoPlayer = new ExoPlayer.Builder(context, renderersFactory).build();
إذا كنت تنشئ مثيلات لبرامج العرض مباشرةً، مرِّر new
DefaultMediaCodecAdapter.Factory(context).forceEnableAsynchronous() إلى دالتي الإنشاء MediaCodecVideoRenderer وMediaCodecAudioRenderer.
تخصيص العمليات باستخدام ForwardingSimpleBasePlayer
يمكنك تخصيص بعض سلوك مثيل Player من خلال تضمينه في فئة فرعية من ForwardingSimpleBasePlayer. تتيح لك هذه الفئة اعتراض "عمليات" معيّنة، بدلاً من تنفيذ طرق Player مباشرةً. يضمن ذلك اتّساق سلوك play() وpause() وsetPlayWhenReady(boolean) على سبيل المثال. ويضمن أيضًا نشر جميع تغييرات الحالة بشكل صحيح
إلى مثيلات Player.Listener المسجّلة. بالنسبة إلى معظم حالات استخدام التخصيص، يُفضَّل استخدام ForwardingSimpleBasePlayer بدلاً من ForwardingPlayer الأكثر عرضة للأخطاء بسبب ضمانات الاتساق هذه.
على سبيل المثال، لإضافة بعض المنطق المخصّص عند بدء التشغيل أو إيقافه:
Kotlin
class PlayerWithCustomPlay(player: Player) : ForwardingSimpleBasePlayer(player) { override fun handleSetPlayWhenReady(playWhenReady: Boolean): ListenableFuture<*> { // Add custom logic return super.handleSetPlayWhenReady(playWhenReady) } }
Java
public static final class PlayerWithCustomPlay extends ForwardingSimpleBasePlayer { public PlayerWithCustomPlay(Player player) { super(player); } @Override protected ListenableFuture<?> handleSetPlayWhenReady(boolean playWhenReady) { // Add custom logic return super.handleSetPlayWhenReady(playWhenReady); } }
أو لعدم السماح بالأمر SEEK_TO_NEXT (والتأكّد من أنّ Player.seekToNext هو
no-op):
Kotlin
class PlayerWithoutSeekToNext(player: Player) : ForwardingSimpleBasePlayer(player) { override fun getState(): State { val state = super.getState() return state .buildUpon() .setAvailableCommands( state.availableCommands.buildUpon().remove(COMMAND_SEEK_TO_NEXT).build() ) .build() } // We don't need to override handleSeek, because it is guaranteed not to be called for // COMMAND_SEEK_TO_NEXT since we've marked that command unavailable. }
Java
public static final class PlayerWithoutSeekToNext extends ForwardingSimpleBasePlayer { public PlayerWithoutSeekToNext(Player player) { super(player); } @Override protected State getState() { State state = super.getState(); return state .buildUpon() .setAvailableCommands( state.availableCommands.buildUpon().remove(COMMAND_SEEK_TO_NEXT).build()) .build(); } // We don't need to override handleSeek, because it is guaranteed not to be called for // COMMAND_SEEK_TO_NEXT since we've marked that command unavailable. }
تخصيص MediaSource
تُدخل الأمثلة السابقة مكوّنات مخصّصة لاستخدامها أثناء تشغيل جميع عناصر MediaItem التي يتم تمريرها إلى المشغّل. عند الحاجة إلى تخصيص دقيق، يمكن أيضًا إدخال مكوّنات مخصّصة في نُسخ MediaSource فردية، والتي يمكن تمريرها مباشرةً إلى المشغّل. يوضّح المثال التالي كيفية تخصيص ProgressiveMediaSource
لاستخدام DataSource.Factory وExtractorsFactory وLoadErrorHandlingPolicy مخصّصة:
Kotlin
val mediaSource = ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory) .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy) .createMediaSource(MediaItem.fromUri(streamUri))
Java
ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory) .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy) .createMediaSource(MediaItem.fromUri(streamUri));
إنشاء مكوّنات مخصّصة
توفّر المكتبة عمليات تنفيذ تلقائية للمكوّنات المدرَجة في أعلى هذه الصفحة لحالات الاستخدام الشائعة. يمكن لـ ExoPlayer استخدام هذه المكوّنات، ولكن
يمكن أيضًا إنشاؤها لاستخدام عمليات تنفيذ مخصّصة إذا كانت هناك حاجة إلى سلوكيات غير عادية. في ما يلي بعض حالات استخدام عمليات التنفيذ المخصّصة:
-
Renderer: قد تحتاج إلى تنفيذRendererمخصّص لمعالجة نوع وسائط غير متوافق مع عمليات التنفيذ التلقائية التي توفّرها المكتبة. -
TrackSelector: يتيح تنفيذTrackSelectorمخصّص لمطوّر التطبيقات تغيير طريقة اختيار المقاطع الصوتية التي يعرضهاMediaSourceليتم تشغيلها من خلال كل منRendererالمتاحة. LoadControl: يتيح تنفيذLoadControlمخصّص لمطوّر التطبيقات تغيير سياسة التخزين المؤقت للمشغّل.-
Extractor: إذا أردت استخدام تنسيق حاوية غير متوافق حاليًا مع المكتبة، يمكنك تنفيذ فئةExtractorمخصّصة. MediaSource: قد يكون تنفيذ فئةMediaSourceمخصّصة مناسبًا إذا أردت الحصول على عيّنات وسائط لإدخالها إلى أدوات العرض بطريقة مخصّصة، أو إذا أردت تنفيذ سلوك مخصّص لتركيبMediaSource.-
MediaSource.Factory: يتيح تنفيذMediaSource.Factoryمخصّص للتطبيق تخصيص طريقة إنشاءMediaSourceمنMediaItem. DataSource: تحتوي حزمة ExoPlayer الأساسية على عدد من عمليات تنفيذDataSourceلحالات استخدام مختلفة. يمكنك تنفيذ فئةDataSourceالخاصة بك لتحميل البيانات بطريقة أخرى، مثل استخدام بروتوكول مخصّص أو حزمة HTTP مخصّصة أو ذاكرة تخزين مؤقت مخصّصة ومستمرة.
عند إنشاء مكوّنات مخصّصة، ننصحك بما يلي:
- إذا كان أحد المكوّنات المخصّصة يحتاج إلى إرسال تقارير عن الأحداث إلى التطبيق، ننصحك
بإجراء ذلك باستخدام النموذج نفسه الذي تستخدمه مكوّنات ExoPlayer الحالية، على
سبيل المثال، باستخدام فئات
EventDispatcherأو تمريرHandlerمع مستمع إلى دالة الإنشاء الخاصة بالمكوّن. - ننصح بأن تستخدم المكوّنات المخصّصة النموذج نفسه الذي تستخدمه مكوّنات ExoPlayer الحالية للسماح للتطبيق بإعادة ضبط الإعدادات أثناء التشغيل. لإجراء ذلك،
يجب أن تنفّذ المكوّنات المخصّصة
PlayerMessage.Targetوتتلقّى تغييرات الإعدادات في طريقةhandleMessage. يجب أن يمرّر الرمز البرمجي للتطبيق تغييرات الضبط من خلال استدعاء طريقةcreateMessageفي ExoPlayer، وضبط الرسالة، وإرسالها إلى المكوّن باستخدامPlayerMessage.send. يضمن إرسال الرسائل ليتم تسليمها في سلسلة عمليات التشغيل تنفيذها بالترتيب مع أي عمليات أخرى يتم إجراؤها على المشغّل.