در هسته کتابخانه ExoPlayer، رابط کاربری Player قرار دارد. یک Player قابلیتهای سنتی پخشکنندههای رسانهای سطح بالا مانند قابلیت بافر کردن رسانه، پخش، مکث و جستجو را ارائه میدهد. پیادهسازی پیشفرض ExoPlayer به گونهای طراحی شده است که فرضیات کمی در مورد نوع رسانه در حال پخش، نحوه و محل ذخیره آن و نحوه رندر شدن آن داشته باشد (و از این رو محدودیتهای کمی بر آن اعمال کند). پیادهسازیهای ExoPlayer به جای پیادهسازی مستقیم بارگذاری و رندر رسانه، این کار را به اجزایی واگذار میکنند که هنگام ایجاد یک پخشکننده یا هنگام انتقال منابع رسانهای جدید به پخشکننده تزریق میشوند. اجزای مشترک در همه پیادهسازیهای ExoPlayer عبارتند از:
- نمونههای
MediaSourceکه رسانههایی را که باید پخش شوند تعریف میکنند، رسانهها را بارگذاری میکنند و رسانههای بارگذاری شده از آنها قابل خواندن هستند. یک نمونهMediaSourceاز یکMediaItemتوسطMediaSource.Factoryدرون پخشکننده ایجاد میشود. آنها همچنین میتوانند مستقیماً با استفاده از API لیست پخش مبتنی بر منبع رسانه به پخشکننده منتقل شوند. - یک نمونه از
MediaSource.Factoryکه یکMediaItemرا بهMediaSourceتبدیل میکند.MediaSource.Factoryهنگام ایجاد پخشکننده تزریق میشود. - نمونههای
Rendererکه اجزای مجزای رسانه را رندر میکنند. این موارد هنگام ایجاد پخشکننده تزریق میشوند. - یک
TrackSelectorکه آهنگهای ارائه شده توسطMediaSourceرا برای استفاده توسط هرRendererموجود انتخاب میکند. یکTrackSelectorهنگام ایجاد پخشکننده تزریق میشود. - یک
LoadControlکه کنترل میکندMediaSourceچه زمانی رسانههای بیشتری را بافر میکند و چه مقدار رسانه بافر میشود. یکLoadControlهنگام ایجاد پخشکننده تزریق میشود. - یک
LivePlaybackSpeedControlکه سرعت پخش را در طول پخش زنده کنترل میکند تا به پخشکننده اجازه دهد نزدیک به یک آفست زنده پیکربندیشده باقی بماند. یکLivePlaybackSpeedControlهنگام ایجاد پخشکننده تزریق میشود.
مفهوم تزریق کامپوننتهایی که بخشهایی از قابلیتهای پخشکننده را پیادهسازی میکنند، در سراسر کتابخانه وجود دارد. پیادهسازیهای پیشفرض برخی از کامپوننتها، کار را به کامپوننتهای تزریقشدهی بیشتر واگذار میکنند. این امر به بسیاری از زیرکامپوننتها اجازه میدهد تا بهصورت جداگانه با پیادهسازیهایی که به روشی سفارشی پیکربندی شدهاند، جایگزین شوند.
سفارشی سازی بازیکن
برخی از نمونههای رایج سفارشیسازی پخشکننده با تزریق اجزا در زیر شرح داده شده است.
پیکربندی پشته شبکه
ما صفحهای در مورد سفارشیسازی پشته شبکه مورد استفاده ExoPlayer داریم.
ذخیره سازی داده های بارگذاری شده از شبکه
برای ذخیره موقت و دانلود رسانه در لحظه، به راهنماها مراجعه کنید.
سفارشیسازی تعاملات سرور
برخی از برنامهها ممکن است بخواهند درخواستها و پاسخهای HTTP را رهگیری کنند. شما ممکن است بخواهید هدرهای درخواست سفارشی را تزریق کنید، هدرهای پاسخ سرور را بخوانید، URIهای درخواستها را تغییر دهید و غیره. به عنوان مثال، برنامه شما ممکن است با تزریق یک توکن به عنوان هدر هنگام درخواست بخشهای رسانه، خود را احراز هویت کند.
مثال زیر نحوه پیادهسازی این رفتارها را با تزریق یک DataSource.Factory سفارشی به DefaultMediaSourceFactory نشان میدهد:
کاتلین
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()
جاوا
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 رفتار just-in-time را تزریق کنید. قطعه کد زیر نحوه تزریق هدرهای درخواست درست قبل از تعامل با منبع HTTP را نشان میدهد:
کاتلین
val dataSourceFactory: DataSource.Factory = ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec -> // Provide just-in-time request headers. dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri)) }
جاوا
DataSource.Factory dataSourceFactory = new ResolvingDataSource.Factory( httpDataSourceFactory, // Provide just-in-time request headers. dataSpec -> dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri)));
همچنین میتوانید از یک ResolvingDataSource برای انجام تغییرات در لحظه (just-in-time) در URI استفاده کنید، همانطور که در قطعه کد زیر نشان داده شده است:
کاتلین
val dataSourceFactory: DataSource.Factory = ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec -> // Provide just-in-time URI resolution logic. dataSpec.withUri(resolveUri(dataSpec.uri)) }
جاوا
DataSource.Factory dataSourceFactory = new ResolvingDataSource.Factory( httpDataSourceFactory, // Provide just-in-time URI resolution logic. dataSpec -> dataSpec.withUri(resolveUri(dataSpec.uri)));
سفارشیسازی مدیریت خطا
پیادهسازی یک LoadErrorHandlingPolicy سفارشی به برنامهها اجازه میدهد تا نحوه واکنش ExoPlayer به خطاهای بارگذاری را سفارشی کنند. برای مثال، یک برنامه ممکن است بخواهد به جای تلاش مجدد چندین باره، سریعاً با شکست مواجه شود، یا ممکن است بخواهد منطق back-off را که مدت زمان انتظار بازیکن بین هر تلاش مجدد را کنترل میکند، سفارشی کند. قطعه کد زیر نحوه پیادهسازی منطق back-off سفارشی را نشان میدهد:
کاتلین
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()
جاوا
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 فعال میکند.
کاتلین
val extractorsFactory = DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING) val player = ExoPlayer.Builder(context) .setMediaSourceFactory(DefaultMediaSourceFactory(context, extractorsFactory)) .build()
جاوا
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 استفاده کنید.
کاتلین
val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)
جاوا
DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);
سپس ExtractorsFactory میتواند از طریق DefaultMediaSourceFactory همانطور که برای سفارشیسازی پرچمهای استخراجکننده در بالا توضیح داده شد، تزریق شود.
فعال کردن صفبندی بافر ناهمزمان
صفبندی بافر ناهمزمان، پیشرفتی در خط لوله رندر ExoPlayer است که نمونههای MediaCodec را در حالت ناهمزمان اجرا میکند و از رشتههای اضافی برای زمانبندی رمزگشایی و رندر دادهها استفاده میکند. فعال کردن آن میتواند فریمهای از دست رفته و افت کیفیت صدا را کاهش دهد.
صفبندی بافر ناهمزمان به طور پیشفرض در دستگاههایی که اندروید ۱۲ (سطح API ۳۱) و بالاتر را اجرا میکنند فعال است و میتوان آن را به صورت دستی از اندروید ۶.۰ (سطح API ۲۳) فعال کرد. فعال کردن این ویژگی را برای دستگاههای خاصی که در آنها افت فریم یا افت صدا مشاهده میکنید، به ویژه هنگام پخش محتوای محافظتشده با DRM یا با نرخ فریم بالا، در نظر بگیرید.
در سادهترین حالت، باید یک DefaultRenderersFactory به پخشکننده به صورت زیر تزریق کنید:
کاتلین
val renderersFactory = DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing() val exoPlayer = ExoPlayer.Builder(context, renderersFactory).build()
جاوا
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 که مستعد خطا است، ترجیح داده شود.
برای مثال، برای اضافه کردن منطق سفارشی هنگام شروع یا توقف پخش:
کاتلین
class PlayerWithCustomPlay(player: Player) : ForwardingSimpleBasePlayer(player) { override fun handleSetPlayWhenReady(playWhenReady: Boolean): ListenableFuture<*> { // Add custom logic return super.handleSetPlayWhenReady(playWhenReady) } }
جاوا
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 غیرفعال است):
کاتلین
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. }
جاوا
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. }
سفارشیسازی مدیاسورس
مثالهای بالا، اجزای سفارشیشده را برای استفاده در طول پخش تمام اشیاء MediaItem که به پخشکننده منتقل میشوند، تزریق میکنند. در جایی که سفارشیسازی دقیق مورد نیاز است، میتوان اجزای سفارشیشده را به نمونههای MediaSource منفرد نیز تزریق کرد که میتوانند مستقیماً به پخشکننده منتقل شوند. مثال زیر نحوه سفارشیسازی یک ProgressiveMediaSource را برای استفاده از DataSource.Factory ، ExtractorsFactory و LoadErrorHandlingPolicy سفارشی نشان میدهد:
کاتلین
val mediaSource = ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory) .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy) .createMediaSource(MediaItem.fromUri(streamUri))
جاوا
ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory) .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy) .createMediaSource(MediaItem.fromUri(streamUri));
ایجاد کامپوننتهای سفارشی
این کتابخانه پیادهسازیهای پیشفرض از اجزای ذکر شده در بالای این صفحه را برای موارد استفاده رایج ارائه میدهد. یک ExoPlayer میتواند از این اجزا استفاده کند، اما در صورت نیاز به رفتارهای غیراستاندارد، ممکن است طوری ساخته شود که از پیادهسازیهای سفارشی نیز استفاده کند. برخی از موارد استفاده برای پیادهسازیهای سفارشی عبارتند از:
-
Renderer- ممکن است بخواهید یکRendererسفارشی پیادهسازی کنید تا نوع رسانهای را که توسط پیادهسازیهای پیشفرض ارائه شده توسط کتابخانه پشتیبانی نمیشود، مدیریت کند. -
TrackSelector- پیادهسازی یکTrackSelectorسفارشی به توسعهدهنده برنامه اجازه میدهد تا نحوه انتخاب Trackهای نمایش داده شده توسطMediaSourceبرای مصرف توسط هر یک ازRendererهای موجود را تغییر دهد. -
LoadControl- پیادهسازی یکLoadControlسفارشی به توسعهدهنده برنامه اجازه میدهد تا سیاست بافرینگ پخشکننده را تغییر دهد. -
Extractor- اگر نیاز به پشتیبانی از قالب کانتینری دارید که در حال حاضر توسط کتابخانه پشتیبانی نمیشود، پیادهسازی یک کلاسExtractorسفارشی را در نظر بگیرید. -
MediaSource- پیادهسازی یک کلاسMediaSourceسفارشی ممکن است مناسب باشد اگر میخواهید نمونههای رسانهای را برای ارائه به رندرکنندهها به روشی سفارشی دریافت کنید، یا اگر میخواهید رفتار ترکیبیMediaSourceسفارشی را پیادهسازی کنید. -
MediaSource.Factory- پیادهسازی یکMediaSource.Factoryسفارشی به یک برنامه اجازه میدهد تا نحوه ایجاد یکMediaSourceاز یکMediaItemرا سفارشی کند. -
DataSource- بسته بالادستی ExoPlayer از قبل شامل تعدادی پیادهسازیDataSourceبرای موارد استفاده مختلف است. ممکن است بخواهید کلاسDataSourceخودتان را برای بارگذاری دادهها به روش دیگری، مانند یک پروتکل سفارشی، استفاده از یک پشته HTTP سفارشی یا از یک حافظه پنهان پایدار سفارشی، پیادهسازی کنید.
هنگام ساخت قطعات سفارشی، موارد زیر را توصیه میکنیم:
- اگر یک کامپوننت سفارشی نیاز به گزارش رویدادها به برنامه دارد، توصیه میکنیم این کار را با استفاده از همان مدل کامپوننتهای موجود ExoPlayer انجام دهید، برای مثال استفاده از کلاسهای
EventDispatcherیا ارسال یکHandlerبه همراه یک listener به سازنده کامپوننت. - ما توصیه کردیم که کامپوننتهای سفارشی از همان مدل کامپوننتهای موجود ExoPlayer استفاده کنند تا امکان پیکربندی مجدد توسط برنامه در حین پخش فراهم شود. برای انجام این کار، کامپوننتهای سفارشی باید
PlayerMessage.Targetرا پیادهسازی کرده و تغییرات پیکربندی را در متدhandleMessageدریافت کنند. کد برنامه باید تغییرات پیکربندی را با فراخوانی متدcreateMessageاز ExoPlayer، پیکربندی پیام و ارسال آن به کامپوننت با استفاده ازPlayerMessage.sendارسال کند. ارسال پیامها برای تحویل در نخ پخش، تضمین میکند که آنها به ترتیب با سایر عملیات انجام شده روی پخشکننده اجرا میشوند.