Ürün Haberleri

Medya oynatmayı iyileştirme: Media3'ün PreloadManager'ını ayrıntılı inceleme - 2. bölüm

Okuma süresi: 9 dk.
Mayuri Khinvasara Khabya adlı kullanıcının profilini görüntüleyin
Mayuri Khinvasara Khabya Geliştirici İlişkileri Mühendisi

Media3 ile medya ön yükleme hakkındaki üç bölümlük serimizin ikinci bölümüne hoş geldiniz. Bu seride, Android uygulamalarınızda yüksek düzeyde duyarlı ve düşük gecikmeli medya deneyimleri oluşturma süreci anlatılmaktadır.

  • 1. Bölüm: Media3 ile Ön Yüklemeye Giriş başlıklı makalede temel konular ele alınmıştı. Basit oynatma listeleri için PreloadConfiguration ile dinamik kullanıcı arayüzleri için daha güçlü olan DefaultPreloadManager arasındaki farkı inceledik. Temel API yaşam döngüsünü nasıl uygulayacağınızı öğrendiniz: add() ile medya ekleme, getMediaSource() ile hazırlanmış bir MediaSource alma, setCurrentPlayingIndex() ve invalidate() ile öncelikleri yönetme ve remove() ile release() kullanarak kaynakları serbest bırakma.
  • 2. Bölüm (Bu yayın): Bu blogda DefaultPreloadManager'ın gelişmiş özelliklerini inceleyeceğiz. PreloadManagerListener ile analizler elde etme, temel bileşenleri ExoPlayer ile paylaşma gibi üretime hazır en iyi uygulamaları hayata geçirme ve kayan pencere kalıbında uzmanlaşarak belleği etkili bir şekilde yönetme konularını ele alıyoruz.
  • 3. Bölüm: Bu serinin son bölümünde, PreloadManager'ı kalıcı bir disk önbelleğiyle entegre etme konusunu ele alacağız. Bu sayede, kaynak yönetimiyle veri tüketimini azaltabilir ve sorunsuz bir deneyim sunabilirsiniz.

Media3'te önceden yükleme özelliğini kullanmaya yeni başladıysanız devam etmeden önce 1. bölümü okumanızı önemle tavsiye ederiz. Temel bilgilerin ötesine geçmeye hazır olanlar için medya oynatma uygulamanızı nasıl geliştireceğinizi inceleyelim.

Dinleme: PreloadManagerListener ile analizleri getirme

Bir uygulama geliştirici olarak, üretimde bir özellik kullanıma sunmak istediğinizde bu özelliğin arkasındaki analizleri de anlamak ve yakalamak istersiniz. Önceden yükleme stratejinizin gerçek dünyada etkili olduğundan nasıl emin olabilirsiniz? Bu soruyu yanıtlamak için başarı oranları, başarısızlıklar ve performansla ilgili verilere ihtiyaç vardır. PreloadManagerListener arayüzü, bu verileri toplamak için kullanılan birincil mekanizmadır.

PreloadManagerListener, önceden yükleme süreci ve durumu hakkında önemli bilgiler sunan iki temel geri çağırma işlevi sağlar.

  • onCompleted(MediaItem mediaItem): Bu geri çağırma, TargetPreloadStatusControl tarafından tanımlandığı şekilde bir önceden yükleme isteği başarıyla tamamlandığında çağrılır.
  • onError(PreloadException error): Bu geri çağırma, hata ayıklama ve izleme için yararlı olabilir. Önceden yükleme başarısız olduğunda çağrılır ve ilişkili istisnayı sağlar.

Aşağıdaki örnek kodda gösterildiği gibi tek bir yöntem çağrısıyla dinleyici kaydedebilirsiniz:

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)

Dinleyiciden analizler elde etme 

Bu dinleyici geri çağırmaları, analiz ardışık düzeninize bağlanabilir. Bu etkinlikleri analiz motorunuza yönlendirerek aşağıdaki gibi önemli soruları yanıtlayabilirsiniz:

  • Ön yükleme başarı oranımız nedir? (onCompleted etkinliklerinin toplam önceden yükleme denemelerine oranı)
  • En yüksek hata oranları hangi CDN'lerde veya video biçimlerinde görülüyor? (onError'dan gelen istisnaları ayrıştırarak)
  • Ön yükleme hata oranımız nedir? (onError etkinliklerinin toplam ön yükleme denemelerine oranı)

Bu veriler, önceden yükleme stratejiniz hakkında nicel geri bildirim sağlayarak A/B testi yapmanıza ve kullanıcı deneyiminizde veriye dayalı iyileştirmeler yapmanıza olanak tanır. Bu veriler, önceden yükleme sürelerinizi, önceden yüklemek istediğiniz video sayısını ve ayırdığınız arabellekleri akıllıca hassaslaştırmanıza da yardımcı olabilir.

Hata ayıklamanın ötesinde: Kullanıcı arayüzünde sorunsuz yedek için onError kullanma

Ön yüklemenin başarısız olması, kullanıcının yakında bir önbelleğe alma etkinliği yaşayacağının güçlü bir göstergesidir. onError geri çağırma işlevi, tepki vererek yanıt vermenize olanak tanır. Hatayı yalnızca günlüğe kaydetmek yerine kullanıcı arayüzünü uyarlayabilirsiniz. Örneğin, sıradaki video önceden yüklenemezse uygulamanız bir sonraki kaydırma için otomatik oynatmayı devre dışı bırakabilir ve oynatmayı başlatmak için kullanıcının dokunmasını gerektirebilir.

Ayrıca, PreloadException türünü inceleyerek daha akıllı bir yeniden deneme stratejisi tanımlayabilirsiniz. Uygulama, hata mesajına veya HTTP durum koduna göre başarısız olan bir kaynağı yöneticiden hemen kaldırmayı seçebilir. Yükleme sorunlarının kullanıcı deneyimini etkilememesi için öğenin kullanıcı arayüzü akışından kaldırılması gerekir. Hataları daha ayrıntılı incelemek için PreloadException'dan HttpDataSourceException gibi daha ayrıntılı veriler de alabilirsiniz. ExoPlayer sorunlarını giderme hakkında daha fazla bilgi edinin.

Arkadaş sistemi: Bileşenleri ExoPlayer ile paylaşmak neden gereklidir?

DefaultPreloadManager ve ExoPlayer birlikte çalışacak şekilde tasarlanmıştır. Kararlılık ve verimlilik sağlamak için birkaç temel bileşeni paylaşmaları gerekir. Ayrı ve koordine edilmemiş bileşenlerle çalışırlarsa önceden yüklenmiş parçaların doğru oynatıcıda oynatılmasını sağlamamız gerektiğinden, oynatıcıda önceden yüklenmiş parçaların iş parçacığı güvenliği ve kullanılabilirliği etkilenebilir. Ayrı bileşenler, ağ bant genişliği ve bellek gibi sınırlı kaynaklar için de rekabet edebilir. Bu da performansın düşmesine neden olabilir. Yaşam döngüsünün önemli bir parçası, uygun imha işlemini gerçekleştirmektir. Önerilen imha sırası, önce PreloadManager'ı, ardından ExoPlayer'ı serbest bırakmaktır.

DefaultPreloadManager.Builder, bu paylaşımı kolaylaştırmak için tasarlanmıştır ve hem PreloadManager'ınızı hem de bağlı bir oynatıcı örneğini örneklendirmek için API'lere sahiptir. BandwidthMeter, LoadControl, TrackSelector, Looper gibi bileşenlerin neden paylaşılması gerektiğini inceleyelim. Bu bileşenlerin ExoPlayer oynatma ile nasıl etkileşim kurduğunu gösteren görsel temsili inceleyin.

preloadManager2.png

Paylaşılan BandwidthMeter ile bant genişliği çakışmalarını önleme

BandwidthMeter, geçmiş aktarım hızlarına göre kullanılabilir ağ bant genişliğinin tahminini sağlar. PreloadManager ve oynatıcı ayrı örnekler kullanıyorsa birbirlerinin ağ etkinliğinden haberdar olmazlar. Bu durum, hata senaryolarına yol açabilir. Örneğin, bir kullanıcının video izlediği, ağ bağlantısının kötüleştiği ve önceden yüklenen MediaSource'un aynı anda gelecekteki bir video için agresif bir indirme işlemi başlattığı senaryoyu ele alalım. Önceden yüklenen MediaSource'un etkinliği, etkin oynatıcının ihtiyaç duyduğu bant genişliğini tüketerek mevcut videonun duraklamasına neden olur. Oynatma sırasında yaşanan duraklama, kullanıcı deneyimi açısından önemli bir sorundur.

Tek bir BandwidthMeter paylaşarak TrackSelector, önceden yükleme veya oynatma sırasında mevcut ağ koşulları ve arabelleğin durumu göz önüne alındığında en yüksek kalitede parçaları seçebilir. Ardından, etkin oynatma oturumunu korumak ve sorunsuz bir deneyim sunmak için akıllı kararlar verebilir.

preloadManagerBuilder.setBandwidthMeter(customBandwidthMeter)

ExoPlayer'ın paylaşılan LoadControl, TrackSelector ve Renderer bileşenleriyle tutarlılık sağlama

  • LoadControl: Bu bileşen, oynatmayı başlatmadan önce ne kadar verinin arabelleğe alınacağı ve daha fazla veri yüklemeye ne zaman başlanacağı veya durdurulacağı gibi arabelleğe alma politikasını belirler. LoadControl'ün paylaşılması, oynatıcı ve PreloadManager'ın bellek tüketiminin hem önceden yüklenmiş hem de etkin olarak oynatılan medyada tek ve koordineli bir tamponlama stratejisiyle yönlendirilmesini sağlayarak kaynak çakışmasını önler. Tutarlılığı sağlamak için önceden yüklediğiniz öğe sayısına ve süresine göre arabellek boyutunu akıllıca tahsis etmeniz gerekir. Çakışma durumlarında oynatıcı, ekranda gösterilen mevcut öğenin oynatılmasına öncelik verir. Paylaşılan bir LoadControl ile önceden yükleme yöneticisi, önceden yükleme için ayrılan hedef arabellek baytları üst sınıra ulaşmadığı sürece önceden yüklemeye devam eder ve oynatma için yükleme işlemi tamamlanana kadar beklemez.

Not: Media3'ün (1.8) en yeni sürümünde LoadControl'ün paylaşılması, Allocator'ın PreloadManager ve oynatıcıyla doğru şekilde paylaşılabilmesini sağlar. Önceden yüklemeyi etkili bir şekilde kontrol etmek için LoadControl'u kullanma özelliği, Media3'ün yakında çıkacak 1.9 sürümünde kullanıma sunulacaktır.

preloadManagerBuilder.setLoadControl(customLoadControl)

  • TrackSelector: Bu bileşen, hangi parçaların (ör. belirli bir çözünürlükteki video, belirli bir dildeki ses) yüklenip oynatılacağını seçer. Paylaşım, önceden yükleme sırasında seçilen parçaların oynatıcının kullanacağı parçalarla aynı olmasını sağlar. Bu sayede, 480p video parçasının önceden yüklendiği ancak oynatıcının bu parçayı hemen silip oynatma sırasında 720p parça getirdiği gereksiz bir senaryo önlenir.< br /> Önceden yükleme yöneticisi, TrackSelector'ın aynı örneğini oynatıcıyla PAYLAŞMAMALIDIR. Bunun yerine, farklı bir TrackSelector örneği kullanmaları gerekir ancak bu örnek aynı uygulamaya ait olmalıdır. Bu nedenle, DefaultPreloadManager.Builder'da TrackSelector yerine TrackSelectorFactory'yi ayarladık.

preloadManagerBuilder.setTrackSelectorFactory(customTrackSelectorFactory)

  • Oluşturucu: Bu bileşen, tam oluşturucuları oluşturmadan oynatıcının özelliklerini anlamaktan sorumludur. Nihai oynatıcının hangi video, ses ve metin biçimlerini destekleyeceğini görmek için bu planı kontrol eder. Bu sayede, yalnızca uyumlu medya parçasını akıllıca seçip indirebilir ve oynatıcının oynatamayacağı içerikler için bant genişliğinin boşa harcanmasını önler.

preloadManagerBuilder.setRenderersFactory(customRenderersFactory)

Exoplayer bileşenleri hakkında daha fazla bilgi edinin.

Altın kural: Hepsini yöneten ortak bir Playback Looper

ExoPlayer örneğine erişilebilen iş parçacığı, oynatıcı oluşturulurken bir Looper iletilerek açıkça belirtilebilir. Oynatıcıya erişilmesi gereken iş parçacığının Looper'ı, Player.getApplicationLooper kullanılarak sorgulanabilir. Oynatıcı ile PreloadManager arasında paylaşılan bir Looper kullanıldığında, bu paylaşılan medya nesnelerindeki tüm işlemlerin tek bir iş parçacığının mesaj sırasına göre sıralanması garanti edilir. Bu sayede eşzamanlılık hataları azaltılabilir.

PreloadManager ile yüklenecek veya önceden yüklenecek medya kaynaklarını içeren oynatıcı arasındaki tüm etkileşimlerin aynı oynatma iş parçacığında gerçekleşmesi gerekir. İş parçacığı güvenliği için Looper'ın paylaşılması gerekir. Bu nedenle, PlaybackLooper'ı PreloadManager ile oynatıcı arasında paylaşmamız gerekir.

PreloadManager, arka planda durum bilgisi olan bir MediaSource nesnesi hazırlar. Kullanıcı arayüzü kodunuz player.setMediaSource(mediaSource) yöntemini çağırdığında, bu karmaşık ve durum bilgisi içeren nesneyi önceden yüklenen MediaSource'tan oynatıcıya aktarırsınız. Bu senaryoda, PreloadMediaSource'un tamamı yöneticiden oynatıcıya taşınır. Tüm bu etkileşimler ve aktarımlar aynı PlaybackLooper'da gerçekleşmelidir.

PreloadManager ve ExoPlayer farklı iş parçacıklarında çalışıyorsa yarışma durumu oluşabilir. PreloadManager'ın iş parçacığı, oynatıcının iş parçacığı MediaSource'tan okumaya çalışırken MediaSource'un dahili durumunu değiştirebilir (ör.arabelleğe yeni veri yazabilir). Bu durum, tahmin edilemeyen davranışlara ve hata ayıklaması zor olan IllegalStateException'a yol açar.

preloadManagerBuilder.setPreloadLooper(playbackLooper)

Yukarıdaki tüm bileşenleri kurulum sırasında ExoPlayer ve DefaultPreloadManager arasında nasıl paylaşabileceğinizi görelim.

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()

İpucu: ExoPlayer'da DefaultLoadControl gibi varsayılan bileşenleri kullanıyorsanız bunları DefaultPreloadManager ile açıkça paylaşmanız gerekmez. ExoPlayer örneğinizi DefaultPreloadManager.Builder'ın buildExoPlayer aracılığıyla oluşturduğunuzda, varsayılan yapılandırmalarla varsayılan uygulamaları kullanırsanız bu bileşenler otomatik olarak birbirine referans verir. Ancak özel bileşenler veya özel yapılandırmalar kullanıyorsanız yukarıdaki API'ler aracılığıyla DefaultPreloadManager'ı bunlar hakkında açıkça bilgilendirmeniz gerekir.

Üretime hazır önceden yükleme: Kayan pencere deseni

Dinamik feed'de kullanıcılar neredeyse sonsuz miktarda içerik arasında gezinebilir. DefaultPreloadManager'a sürekli olarak video ekler ve ilgili kaldırma stratejisini uygulamazsanız kaçınılmaz olarak OutOfMemoryError hatasıyla karşılaşırsınız. Önceden yüklenen her MediaSource, bellek arabelleklerini ayıran bir SampleQueue tutar. Bunlar biriktikçe uygulamanın yığın alanını tüketebilir. Çözüm, kayan pencere olarak bilinen ve aşina olabileceğiniz bir algoritmadır. Kayan pencere deseni, bellekte kullanıcının feed'deki mevcut konumuna mantıksal olarak bitişik olan küçük ve yönetilebilir bir öğe grubu tutar. Kullanıcı sayfayı kaydırdıkça yönetilen öğelerin bu "penceresi" de kayar, görünür hale gelen yeni öğeler eklenir ve artık uzakta olan öğeler kaldırılır.

slidingwindow.png

Kayan pencere desenini uygulama

PreloadManager'ın yerleşik bir setWindowSize() yöntemi sağlamadığını anlamak önemlidir. Kayan pencere, geliştirici olarak sizin ilkel add() ve remove() yöntemlerini kullanarak uygulamaktan sorumlu olduğunuz bir tasarım kalıbıdır. Uygulama mantığınız, kaydırma veya sayfa değişikliği gibi kullanıcı arayüzü etkinliklerini bu API çağrılarına bağlamalıdır. Bu konuda bir kod referansı isterseniz socialite örneğinde bu kayan pencere kalıbını uyguladık. Bu örnekte, kayan pencereyi taklit eden bir PreloadManagerWrapper da bulunur.

Öğenin kullanıcının görüntüleme listesinde yakında görünme olasılığı kalmadığında uygulamanıza preloadManager.remove(mediaItem) eklemeyi unutmayın. Artık kullanıcıya yakın olmayan öğelerin kaldırılmaması, önceden yükleme uygulamalarındaki bellek sorunlarının birincil nedenidir. remove() çağrısı, uygulamanızın bellek kullanımını sınırlı ve kararlı tutmanıza yardımcı olan kaynakların serbest bırakılmasını sağlar.

TargetPreloadStatusControl ile kategorilere ayrılmış bir önceden yükleme stratejisinde ince ayar yapma

Nelerin önceden yükleneceğini (penceremizdeki öğeler) tanımladığımıza göre, her öğe için ne kadar önceden yükleme yapılacağına dair iyi tanımlanmış bir strateji uygulayabiliriz. 1. bölümde TargetPreloadStatusControl kurulumuyla bu ayrıntı düzeyine nasıl ulaşacağımızı görmüştük.

Hatırlatmak gerekirse +/- 1 konumundaki bir öğenin oynatılma olasılığı, +/- 4 konumundaki bir öğeye göre daha yüksek olabilir. Kullanıcının bir sonraki adımda görüntüleme olasılığı en yüksek olan öğelere daha fazla kaynak (ağ, CPU, bellek) ayırabilirsiniz. Bu, yakınlığa dayalı bir "önceden yükleme" stratejisi oluşturur. Bu strateji, anında oynatmayı verimli kaynak kullanımıyla dengelemenin anahtarıdır.

Önceden yükleme süresi stratejinize karar vermek için önceki bölümlerde ele alındığı gibi PreloadManagerListener üzerinden analiz verilerini kullanabilirsiniz.

Sonuç ve sıradaki adımlar

Artık Media3'ün DefaultPreloadManager'ını kullanarak hızlı, kararlı ve kaynak açısından verimli medya feed'leri oluşturmak için gereken ileri düzey bilgilere sahipsiniz.

Önemli noktaları özetleyelim:

  • Analizlerden elde edilen bilgileri toplamak ve hataların etkili bir şekilde yönetilmesini sağlamak için PreloadManagerListener'ı kullanın.
  • Önemli bileşenlerin paylaşıldığından emin olmak için hem yönetici hem de oynatıcı örneklerinizi oluştururken her zaman tek bir DefaultPreloadManager.Builder kullanın.
  • OutOfMemoryError hatasını önlemek için add() ve remove() çağrılarını etkin bir şekilde yöneterek kayan pencere kalıbını uygulayın.
  • Performans ve kaynak tüketimini dengeleyen akıllı, katmanlı bir önceden yükleme stratejisi oluşturmak için TargetPreloadStatusControl'ü kullanın.

3. bölümde ele alınacak konular: Önceden yüklenmiş medya ile önbelleğe alma

Verileri belleğe önceden yüklemek anında performans avantajı sağlar ancak bazı dezavantajları da olabilir. Uygulama kapatıldığında veya önceden yüklenen medya yöneticiden kaldırıldığında veriler silinir. Daha kalıcı bir optimizasyon düzeyi elde etmek için önceden yüklemeyi disk önbelleğe alma ile birleştirebiliriz. Bu özellik şu anda geliştirme aşamasındadır ve birkaç ay içinde kullanıma sunulacaktır.

Paylaşmak istediğiniz bir geri bildiriminiz var mı? Görüşlerinizi öğrenmekten memnuniyet duyarız.

Gelişmeleri takip edin ve videolarınızı daha hızlı oynatın. 🚀

Yazan:
Okumaya devam et