اخبار محصول

افزایش کیفیت پخش رسانه: نگاهی عمیق به PreloadManager در Media3 - بخش 2

۹ دقیقه مطالعه
Mayuri Khinvasara Khabya
مهندس روابط توسعه‌دهنده

به بخش دوم از مجموعه سه قسمتی ما در مورد پیش بارگذاری رسانه با Media3 خوش آمدید. این مجموعه به گونه‌ای طراحی شده است که شما را در فرآیند ساخت تجربیات رسانه‌ای بسیار واکنش‌گرا و با تأخیر کم در برنامه‌های اندروید شما راهنمایی کند.

  • بخش ۱: معرفی پیش‌بارگذاری با Media3 اصول اولیه را پوشش داد. ما تفاوت بین PreloadConfiguration برای لیست‌های پخش ساده و DefaultPreloadManager قدرتمندتر برای رابط‌های کاربری پویا را بررسی کردیم. شما یاد گرفتید که چگونه چرخه عمر API اولیه را پیاده‌سازی کنید: اضافه کردن رسانه با add()، بازیابی یک MediaSource آماده با getMediaSource()، مدیریت اولویت‌ها با setCurrentPlayingIndex() و invalidate()، و آزاد کردن منابع با remove() و release().
  • بخش ۲ (این پست): در این وبلاگ، قابلیت‌های پیشرفته DefaultPreloadManager را بررسی می‌کنیم. نحوه کسب بینش با PreloadManagerListener ، پیاده‌سازی بهترین شیوه‌های آماده برای تولید مانند اشتراک‌گذاری اجزای اصلی با ExoPlayer و تسلط بر الگوی پنجره کشویی برای مدیریت مؤثر حافظه را پوشش می‌دهیم.
  • بخش ۳: بخش پایانی این مجموعه به ادغام PreloadManager با یک حافظه پنهان دیسک پایدار می‌پردازد که به شما امکان می‌دهد با مدیریت منابع، مصرف داده را کاهش داده و یک تجربه یکپارچه ارائه دهید.

اگر در پیش‌بارگذاری در Media3 تازه‌کار هستید، اکیداً توصیه می‌کنیم قبل از ادامه، بخش ۱ را مطالعه کنید. برای کسانی که آماده‌اند فراتر از اصول اولیه حرکت کنند، بیایید بررسی کنیم که چگونه می‌توان اجرای پخش رسانه خود را ارتقا داد.

گوش دادن به: دریافت تحلیل‌ها با PreloadManagerListener

وقتی می‌خواهید یک ویژگی را در مرحله تولید راه‌اندازی کنید، به عنوان یک توسعه‌دهنده برنامه، می‌خواهید تحلیل‌های پشت آن را نیز درک و ثبت کنید. چگونه می‌توانید مطمئن شوید که استراتژی پیش‌بارگذاری شما در محیط دنیای واقعی مؤثر است؟ پاسخ به این سوال نیاز به داده‌هایی در مورد میزان موفقیت، شکست‌ها و عملکرد دارد. رابط PreloadManagerListener مکانیسم اصلی جمع‌آوری این داده‌ها است.

PreloadManagerListener دو تابع فراخوانی ضروری ارائه می‌دهد که بینش‌های مهمی در مورد فرآیند و وضعیت پیش‌بارگذاری ارائه می‌دهند.

  • onCompleted (MediaItem mediaItem) : این فراخوانی پس از اتمام موفقیت‌آمیز یک درخواست پیش‌بارگذاری، همانطور که توسط TargetPreloadStatusControl شما تعریف شده است، فراخوانی می‌شود.
  • onError (خطای PreloadException) : این فراخوانی می‌تواند برای اشکال‌زدایی و نظارت مفید باشد. این فراخوانی زمانی انجام می‌شود که یک پیش‌بارگذاری با شکست مواجه شود و خطای مربوطه را ارائه دهد.

شما می‌توانید یک شنونده (listener) را با یک فراخوانی متد ثبت کنید، همانطور که در کد مثال زیر نشان داده شده است:

  val preloadManagerListener = object : PreloadManagerListener {
    override fun onCompleted(mediaItem: MediaItem) {
        // Log success for analytics. 
        Log.d("PreloadAnalytics", "Preload completed for $mediaItem")
    }

    override fun onError( preloadError: PreloadException) {
        // Log the specific error for debugging and monitoring.
        Log.e("PreloadAnalytics", "Preload error ", preloadError)
    }
}

preloadManager.addListener(preloadManagerListener)

استخراج بینش از شنونده

این فراخوانی‌های شنونده می‌توانند به خط لوله تحلیلی شما متصل شوند. با ارسال این رویدادها به موتور تحلیلی خود، می‌توانید به سؤالات کلیدی مانند موارد زیر پاسخ دهید:

  • نرخ موفقیت پیش‌بارگذاری ما چقدر است؟ (نسبت رویدادهای تکمیل‌شده به کل تلاش‌های پیش‌بارگذاری)
  • کدام CDNها یا فرمت‌های ویدیویی بالاترین نرخ خطا را نشان می‌دهند؟ (با تجزیه استثنائات از onError)
  • نرخ خطای پیش‌بارگذاری ما چقدر است؟ (نسبت رویدادهای onError به کل تلاش‌های پیش‌بارگذاری)

این داده‌ها می‌توانند بازخورد کمی در مورد استراتژی پیش‌بارگذاری شما ارائه دهند، که امکان آزمایش A/B و بهبودهای مبتنی بر داده را برای تجربه کاربری شما فراهم می‌کند. این داده‌ها همچنین می‌توانند به شما کمک کنند تا مدت زمان پیش‌بارگذاری و تعداد ویدیوهایی که می‌خواهید پیش‌بارگذاری کنید و همچنین بافرهایی که اختصاص می‌دهید را هوشمندانه تنظیم کنید .

فراتر از اشکال‌زدایی: استفاده از onError برای جایگزینی رابط کاربری زیبا

پیش‌بارگذاری ناموفق، نشانه‌ای قوی از یک رویداد بافرینگ قریب‌الوقوع برای کاربر است. فراخوانی onError به شما امکان می‌دهد تا به صورت واکنشی پاسخ دهید. به جای صرفاً ثبت خطا، می‌توانید رابط کاربری را تطبیق دهید. به عنوان مثال، اگر ویدیوی پیش‌رو پیش‌بارگذاری نشود، برنامه شما می‌تواند پخش خودکار را برای کشیدن انگشت بعدی غیرفعال کند و برای شروع پخش، به لمس کاربر نیاز داشته باشد.

علاوه بر این، با بررسی نوع PreloadException می‌توانید یک استراتژی تلاش مجدد هوشمندانه‌تر تعریف کنید. یک برنامه می‌تواند بر اساس پیام خطا یا کد وضعیت HTTP، بلافاصله منبع ناموفق را از مدیریت حذف کند. بر این اساس، این مورد باید از جریان رابط کاربری حذف شود تا مشکلات بارگیری به تجربه کاربر نفوذ نکند. همچنین می‌توانید داده‌های جزئی‌تری مانند HttpDataSourceException را از PreloadException دریافت کنید تا خطاها را بیشتر بررسی کنید. درباره عیب‌یابی ExoPlayer بیشتر بخوانید.

سیستم رفاقت: چرا اشتراک‌گذاری اجزا با ExoPlayer ضروری است؟

DefaultPreloadManager و ExoPlayer طوری طراحی شده‌اند که با هم کار کنند. برای اطمینان از پایداری و کارایی، آنها باید چندین مؤلفه اصلی را به اشتراک بگذارند. اگر آنها با مؤلفه‌های جداگانه و ناهماهنگ کار کنند، می‌تواند بر ایمنی نخ و قابلیت استفاده از آهنگ‌های از پیش بارگذاری شده در پخش‌کننده تأثیر بگذارد، زیرا باید اطمینان حاصل کنیم که آهنگ‌های از پیش بارگذاری شده باید روی پخش‌کننده صحیح پخش شوند. اجزای جداگانه همچنین می‌توانند برای منابع محدودی مانند پهنای باند شبکه و حافظه رقابت کنند که می‌تواند منجر به تخریب عملکرد شود. بخش مهمی از چرخه عمر، مدیریت دفع مناسب است، ترتیب توصیه شده برای دفع، ابتدا PreloadManager و پس از آن ExoPlayer است.

DefaultPreloadManager.Builder برای تسهیل این اشتراک‌گذاری طراحی شده است و دارای APIهایی برای نمونه‌سازی PreloadManager شما و یک نمونه پخش‌کننده لینک‌شده است. بیایید ببینیم چرا اجزایی مانند BandwidthMeter، LoadControl، TrackSelector، Looper باید به اشتراک گذاشته شوند. نمایش بصری نحوه تعامل این اجزا با ExoPlayer Playback را بررسی کنید.

preloadManager2.png

جلوگیری از تداخل پهنای باند با BandwidthMeter مشترک

BandwidthMeter تخمینی از پهنای باند شبکه موجود را بر اساس نرخ انتقال داده قبلی ارائه می‌دهد. اگر PreloadManager و پخش‌کننده از نمونه‌های جداگانه‌ای استفاده کنند، از فعالیت شبکه یکدیگر بی‌اطلاع خواهند بود که می‌تواند منجر به سناریوهای خرابی شود. به عنوان مثال، سناریویی را در نظر بگیرید که در آن کاربر در حال تماشای یک ویدیو است، اتصال شبکه او کاهش می‌یابد و MediaSource در حال بارگیری همزمان دانلود تهاجمی برای ویدیوی آینده را آغاز می‌کند. فعالیت MediaSource در حال بارگیری، پهنای باند مورد نیاز پخش‌کننده فعال را مصرف می‌کند و باعث می‌شود ویدیوی فعلی متوقف شود. توقف در حین پخش، یک خرابی قابل توجه در تجربه کاربری است.

با به اشتراک گذاشتن یک BandwidthMeter واحد، TrackSelector قادر است با توجه به شرایط فعلی شبکه و وضعیت بافر، در حین پیش‌بارگذاری یا پخش، آهنگ‌هایی با بالاترین کیفیت را انتخاب کند. سپس می‌تواند تصمیمات هوشمندانه‌ای برای محافظت از جلسه پخش فعال و تضمین یک تجربه روان بگیرد.

preloadManagerBuilder.setBandwidthMeter(customBandwidthMeter)

تضمین سازگاری با کامپوننت‌های مشترک LoadControl، TrackSelector و Renderer در ExoPlayer

  • LoadControl : این مؤلفه، سیاست بافرینگ را تعیین می‌کند، مانند اینکه قبل از شروع پخش، چه مقدار داده باید بافر شود و چه زمانی بارگذاری داده‌های بیشتر شروع یا متوقف شود. اشتراک‌گذاری LoadControl تضمین می‌کند که مصرف حافظه پخش‌کننده و PreloadManager توسط یک استراتژی بافرینگ واحد و هماهنگ در هر دو رسانه از پیش بارگذاری شده و فعال در حال پخش هدایت شود و از تداخل منابع جلوگیری کند. برای اطمینان از سازگاری، باید هوشمندانه اندازه بافر را با توجه به تعداد آیتم‌هایی که پیش‌بارگذاری می‌کنید و مدت زمان آن اختصاص دهید. در مواقع تداخل، پخش‌کننده اولویت پخش آیتم فعلی نمایش داده شده روی صفحه را تعیین می‌کند. با یک LoadControl مشترک، مدیر پیش‌بارگذاری تا زمانی که بایت‌های بافر هدف اختصاص داده شده برای پیش‌بارگذاری به حد بالایی نرسیده باشد، به پیش‌بارگذاری ادامه می‌دهد و منتظر نمی‌ماند تا بارگذاری برای پخش انجام شود.

توجه: اشتراک‌گذاری LoadControl در آخرین نسخه Media3 (1.8) تضمین می‌کند که Allocator آن می‌تواند به درستی با PreloadManager و پخش‌کننده به اشتراک گذاشته شود. استفاده از LoadControl برای کنترل مؤثر پیش‌بارگذاری، ویژگی‌ای است که در نسخه 1.9 مدیا3 در دسترس خواهد بود.

preloadManagerBuilder.setLoadControl(customLoadControl)

  • TrackSelector : این کامپوننت مسئول انتخاب آهنگ‌هایی (مثلاً ویدیویی با وضوح خاص، صوتی با زبانی خاص) برای بارگذاری و پخش است. اشتراک‌گذاری تضمین می‌کند که آهنگ‌های انتخاب‌شده در طول پیش‌بارگذاری، همان آهنگ‌هایی باشند که پخش‌کننده از آن‌ها استفاده خواهد کرد. این کار از سناریوی بیهوده‌ای که در آن یک آهنگ ویدیویی 480p از قبل بارگذاری می‌شود، اما پخش‌کننده بلافاصله آن را کنار می‌گذارد و پس از پخش، یک آهنگ 720p دریافت می‌کند، جلوگیری می‌کند.<br /> مدیر پیش‌بارگذاری نباید نمونه‌ی یکسانی از TrackSelector را با پخش‌کننده به اشتراک بگذارد. در عوض، آن‌ها باید از نمونه‌ی TrackSelector متفاوت اما با پیاده‌سازی یکسان استفاده کنند. به همین دلیل است که ما TrackSelectorFactory را به جای TrackSelector در DefaultPreloadManager.Builder تنظیم می‌کنیم.

preloadManagerBuilder.setTrackSelectorFactory(customTrackSelectorFactory)

  • رندرکننده : این مؤلفه مسئول درک قابلیت‌های پخش‌کننده بدون ایجاد رندرکننده‌های کامل است. این مؤلفه این طرح اولیه را بررسی می‌کند تا ببیند پخش‌کننده نهایی از کدام فرمت‌های ویدیویی، صوتی و متنی پشتیبانی می‌کند. این امر به آن اجازه می‌دهد تا هوشمندانه فقط آهنگ رسانه‌ای سازگار را انتخاب و دانلود کند و از هدر رفتن پهنای باند برای محتوایی که پخش‌کننده در واقع نمی‌تواند پخش کند، جلوگیری می‌کند.

preloadManagerBuilder.setRenderersFactory(customRenderersFactory)

درباره اجزای بیشتر Exoplayer بیشتر بخوانید.

قانون طلایی: یک لوپر پخش مشترک برای همه آنها

می‌توان با ارسال یک Looper هنگام ایجاد پخش‌کننده، به طور صریح مشخص کرد که در کدام thread می‌توان به یک نمونه ExoPlayer دسترسی پیدا کرد. Looper مربوط به thread که پخش‌کننده باید از آن دسترسی پیدا کند، می‌تواند با استفاده از Player.getApplicationLooper جستجو شود. با حفظ یک Looper مشترک بین player و PreloadManager، تضمین می‌شود که تمام عملیات روی این اشیاء رسانه‌ای مشترک در صف پیام یک thread واحد سریال‌سازی شوند. این می‌تواند اشکالات همزمانی را کاهش دهد.

تمام تعاملات بین PreloadManager و پخش‌کننده با منابع رسانه‌ای که باید بارگیری یا از پیش بارگذاری شوند، باید در همان نخ پخش اتفاق بیفتد. اشتراک‌گذاری Looper برای ایمنی نخ ضروری است و از این رو باید PlaybackLooper را بین PreloadManager و پخش‌کننده به اشتراک بگذاریم.

PreloadManager یک شیء MediaSource با وضعیت (stateful) را در پس‌زمینه آماده می‌کند. وقتی کد رابط کاربری شما player.setMediaSource(mediaSource) را فراخوانی می‌کند، شما در حال انجام یک handoff از این شیء پیچیده و با وضعیت از MediaSource در حال بارگذاری اولیه به پخش‌کننده هستید. در این سناریو، کل PreloadMediaSource از manager به پخش‌کننده منتقل می‌شود. همه این تعاملات و handoffها باید در همان PlaybackLooper رخ دهند.

اگر PreloadManager و ExoPlayer روی threadهای مختلف کار می‌کردند، ممکن است شرایط رقابتی رخ دهد. thread مربوط به PreloadManager می‌تواند وضعیت داخلی MediaSource را تغییر دهد (مثلاً داده‌های جدید را در بافر بنویسد) دقیقاً در همان لحظه‌ای که thread مربوط به player سعی در خواندن از آن دارد. این منجر به رفتار غیرقابل پیش‌بینی، IllegalStateException می‌شود که اشکال‌زدایی آن دشوار است.

preloadManagerBuilder.setPreloadLooper(playbackLooper)

بیایید ببینیم چگونه می‌توانید تمام اجزای فوق را بین ExoPlayer و DefaultPreloadManager در خودِ تنظیمات به اشتراک بگذارید.

  val preloadManagerBuilder =
DefaultPreloadManager.Builder(context, targetPreloadStatusControl)

// Optional - Share components between ExoPlayer and DefaultPreloadManager
preloadManagerBuilder
     .setBandwidthMeter(customBandwidthMeter)
     .setLoadControl(customLoadControl)
     .setMediaSourceFactory(customMediaSourceFactory)
     .setTrackSelectorFactory(customTrackSelectorFactory)
     .setRenderersFactory(customRenderersFactory)
     .setPreloadLooper(playbackLooper)

val preloadManager = val preloadManagerBuilder.build()

نکته: اگر از اجزای پیش‌فرض در ExoPlayer مانند DefaultLoadControl و غیره استفاده می‌کنید ، نیازی نیست که آنها را صریحاً با DefaultPreloadManager به اشتراک بگذارید. وقتی نمونه ExoPlayer خود را از طریق buildExoPlayer از DefaultPreloadManager.Builder می‌سازید، این اجزا به طور خودکار به یکدیگر ارجاع داده می‌شوند، اگر از پیاده‌سازی‌های پیش‌فرض با پیکربندی‌های پیش‌فرض استفاده می‌کنید. اما اگر از اجزای سفارشی یا پیکربندی‌های سفارشی استفاده می‌کنید، باید صریحاً از طریق APIهای فوق به DefaultPreloadManager در مورد آنها اطلاع دهید.

پیش بارگذاری آماده تولید: الگوی پنجره کشویی

در یک فید پویا، کاربر می‌تواند تقریباً بی‌نهایت محتوا را پیمایش کند. اگر به طور مداوم ویدیوها را به DefaultPreloadManager بدون استراتژی حذف مربوطه اضافه کنید، ناگزیر باعث خطای OutOfMemoryError خواهید شد. هر MediaSource از پیش بارگذاری شده، یک SampleQueue را در خود نگه می‌دارد که بافرهای حافظه را اختصاص می‌دهد. با انباشته شدن این بافرها، می‌توانند فضای پشته برنامه را تمام کنند. راه حل، الگوریتمی است که ممکن است از قبل با آن آشنا باشید، به نام پنجره کشویی. الگوی پنجره کشویی، مجموعه‌ای کوچک و قابل مدیریت از آیتم‌ها را در حافظه نگه می‌دارد که منطقاً مجاور موقعیت فعلی کاربر در فید هستند. با پیمایش کاربر، این "پنجره" از آیتم‌های مدیریت شده با آنها پیمایش می‌شود و آیتم‌های جدیدی که به نمایش در می‌آیند را اضافه می‌کند و همچنین آیتم‌هایی را که اکنون دور هستند حذف می‌کند.

پنجره کشویی.png

پیاده‌سازی الگوی پنجره کشویی

درک این نکته ضروری است که PreloadManager متد setWindowSize() داخلی ارائه نمی‌دهد. پنجره کشویی یک الگوی طراحی است که شما، به عنوان توسعه‌دهنده، مسئول پیاده‌سازی آن با استفاده از متدهای اولیه add() و remove() هستید. منطق برنامه شما باید رویدادهای رابط کاربری، مانند اسکرول یا تغییر صفحه، را به این فراخوانی‌های API متصل کند. اگر به دنبال مرجع کد برای این مورد هستید، ما این الگوی پنجره کشویی را در نمونه socialite پیاده‌سازی کرده‌ایم که شامل PreloadManagerWrapper نیز می‌شود که یک پنجره کشویی را تقلید می‌کند.

فراموش نکنید که وقتی احتمال نمایش زودهنگام آیتم در دید کاربر وجود ندارد، preloadManager.remove(mediaItem) را در پیاده‌سازی خود اضافه کنید. عدم حذف آیتم‌هایی که دیگر به کاربر نزدیک نیستند، دلیل اصلی مشکلات حافظه در پیاده‌سازی‌های پیش‌بارگذاری است. فراخوانی remove() تضمین می‌کند که منابعی آزاد می‌شوند که به شما کمک می‌کند میزان استفاده از حافظه برنامه خود را محدود و پایدار نگه دارید.

تنظیم دقیق یک استراتژی پیش بارگذاری طبقه‌بندی‌شده با TargetPreloadStatusControl

حالا که مشخص کردیم چه چیزهایی را باید از قبل بارگذاری کنیم (آیتم‌های موجود در پنجره‌مان)، می‌توانیم یک استراتژی مشخص برای میزان پیش‌بارگذاری هر آیتم اعمال کنیم. قبلاً نحوه دستیابی به این جزئیات را با تنظیمات TargetPreloadStatusControl در بخش 1 دیدیم.

برای یادآوری، یک آیتم در موقعیت +/- ۱ می‌تواند احتمال پخش بیشتری نسبت به یک آیتم در موقعیت +/- ۴ داشته باشد. شما می‌توانید منابع بیشتری (شبکه، پردازنده، حافظه) را به آیتم‌هایی اختصاص دهید که کاربر به احتمال زیاد در مرحله بعد آنها را مشاهده خواهد کرد. این یک استراتژی "پیش‌بارگذاری" بر اساس نزدیکی ایجاد می‌کند، که کلید ایجاد تعادل بین پخش فوری و استفاده کارآمد از منابع است.

شما می‌توانید از داده‌های تحلیلی از طریق PreloadManagerListener همانطور که در بخش‌های قبلی بحث شد، برای تصمیم‌گیری در مورد استراتژی مدت زمان پیش‌بارگذاری خود استفاده کنید.

نتیجه‌گیری و مراحل بعدی

اکنون شما به دانش پیشرفته‌ای برای ساخت فیدهای رسانه‌ای سریع، پایدار و با بهره‌وری از منابع با استفاده از DefaultPreloadManager مدیا۳ مجهز شده‌اید.

بیایید نکات کلیدی را خلاصه کنیم:

  • از PreloadManagerListener برای جمع‌آوری بینش‌های تحلیلی و پیاده‌سازی مدیریت خطای قوی استفاده کنید.
  • همیشه از یک DefaultPreloadManager.Builder واحد برای ایجاد هر دو نمونه مدیر و بازیکن خود استفاده کنید تا از اشتراک‌گذاری اجزای مهم اطمینان حاصل شود.
  • الگوی پنجره کشویی را با مدیریت فعال فراخوانی‌های add() و remove() پیاده‌سازی کنید تا از OutOfMemoryError جلوگیری شود.
  • از TargetPreloadStatusControl برای ایجاد یک استراتژی پیش‌بارگذاری هوشمند و چندلایه که عملکرد و مصرف منابع را متعادل می‌کند، استفاده کنید.

بخش بعدی بخش ۳: ذخیره‌سازی با رسانه‌های از پیش بارگذاری شده

پیش‌بارگذاری داده‌ها در حافظه، مزیت عملکردی فوری را ارائه می‌دهد، اما می‌تواند با معایبی نیز همراه باشد. به محض اینکه برنامه بسته شود یا رسانه‌ی پیش‌بارگذاری شده از مدیریت حذف شود، داده‌ها از بین می‌روند. برای دستیابی به سطح پایدارتری از بهینه‌سازی، می‌توانیم پیش‌بارگذاری را با ذخیره‌سازی دیسک ترکیب کنیم. این ویژگی در حال توسعه‌ی فعال است و به زودی در چند ماه آینده ارائه خواهد شد.

آیا نظری برای به اشتراک گذاشتن دارید؟ ما مشتاق شنیدن نظرات شما هستیم.

با ما همراه باشید و پخش ویدیوی خود را سریع‌تر کنید! 🚀

    نوشته شده توسط:

    ادامه مطلب