في صميم مكتبة 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 واستجاباته. قد تحتاج إلى إدخال رؤوس طلبات مخصّصة، وقراءة رؤوس استجابة الخادم، وتعديل معرّفات الموارد الموحّدة للطلبات، وما إلى ذلك. على سبيل المثال، قد يصادق تطبيقك على نفسه من خلال إدخال رمز مميز كرأس عند طلب مقاطع الوسائط.
يوضّح المثال التالي كيفية تنفيذ هذه السلوكيات من خلال إدخال 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: 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(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
في الوضع غير المتزامن وتستخدم سلاسل محادثات إضافية لجدولة فك ترميز البيانات وعرضها. يمكن أن يؤدي تفعيلها إلى تقليل عدد اللقطات التي تم إسقاطها ونقص الصوت.
يتم تفعيل ميزة "وضع البيانات في قائمة انتظار المخزن المؤقت غير المتزامن" تلقائيًا على الأجهزة التي تعمل بالإصدار 12 من نظام التشغيل Android (المستوى 31 من واجهة برمجة التطبيقات) والإصدارات الأحدث، ويمكن تفعيلها يدويًا بدءًا من الإصدار 6.0 من نظام التشغيل Android (المستوى 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
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
لا يؤدي إلى أي عملية):
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
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
. يضمن إرسال الرسائل ليتم تسليمها في سلسلة تشغيل الفيديو تنفيذها بالترتيب مع أي عمليات أخرى يتم إجراؤها على المشغّل.