Kullanıcı, kareleri önizlemek ve oynatma konumu bulmak için arama çubuğu tutma yerini zaman çizelgesi üzerinde sürüklediğinde kullanıcı arayüzü, kısa aralıklarla sık sık Player.seekTo çağrıları gönderir. Player.seekTo ve ExoPlayer.setSeekParameters ile standart arama, her yeni aramanın devam eden aramaları iptal ettiği ve kod çözücüyü sıfırladığı bağımsız atlamalar için tasarlanmıştır.
Sürekli sürükleme hareketi sırasında bu davranış, kullanıcı hareketi durdurana kadar önizleme karelerinin oluşturulmasını engelleyebilir.
ExoPlayer.setScrubbingModeEnabled, oynatıcıyı etkileşimli ileri geri oynatma için yapılandırır. Böylece video önizleme kareleri, kullanıcı sürükledikçe duyarlı bir şekilde oluşturulur.
Önceden oluşturulmuş kullanıcı arayüzü bileşenlerini kullanma
Media3'ün önceden oluşturulmuş kullanıcı arayüzü modülleri, doğrudan bir ExoPlayer örneğine bağlandığında (ForwardingPlayer içinde sarmalanmamış veya bir MediaController üzerinden erişilmemiş) yerleşik ileri geri oynatma modu desteği içerir:
- Jetpack Compose (
media3-ui-compose-material3): HemPlayercomposable hem de bağımsızProgressSlider, varsayılan olarak ileri geri oynatma modunu kullanır. Kullanıcı kaydırma çubuğunu sürüklediğindeProgressSlider, sürükleme işleminin başındasetScrubbingModeEnabled(true)işlevini çağırır, sürükleme sırasındaseekTogüncellemelerini gönderir ve sürükleme işlemi sona erdiğinde veya composable, oluşturma işleminden ayrıldığındasetScrubbingModeEnabled(false)işlevini çağırır. Daha fazla bilgi için Material3 Compose sayfasına bakın. - Görünümler (
media3-ui):PlayerViewvePlayerControlView, varsayılan olarak devre dışı bırakılan ve yalnızca kullanıcı zaman çubuğunu bıraktığında bir kez arama yapan ileri geri oynatma modu entegrasyonunu içerir. XML'dePlayerViewüzerindeapp:time_bar_scrubbing_enabled="true"ayarını yaparak veya koddasetTimeBarScrubbingEnabled(true)yöntemini çağırarak sürekli ileri geri oynatmayı etkinleştirin.
ProgressSlider veya PlayerView kullansanız bile ExoPlayer örneğinde ScrubbingModeParameters öğesini özelleştirebilirsiniz (ör. kaydırma çubuğu genişliğinden kesirli bir arama toleransı ayarlama).
İleri geri oynatma modunu açma ve kapatma zamanı
Özel bir sarma çubuğu veya ilerleme noktası kontrolü uyguluyorsanız ileri geri oynatma modunu yalnızca etkileşimli bir kullanıcı ileri geri oynatma hareketi süresince etkinleştirin:
- Kullanıcı ilerleme noktası sürüklemeye başladığında ileri geri oynatma modunu etkinleştirin (
setScrubbingModeEnabled(true)) ve sürükleme konumu değiştikçeseekToyöntemini çağırın. - Kullanıcı hareketi tamamladığında veya iptal ettiğinde ileri geri oynatma modunu kapatın (
setScrubbingModeEnabled(false)). Örneğin, işaretçi bırakıldığında, hareket iptal edildiğinde veya kullanıcı arayüzü bileşeniniz sürükleme sırasında ayrıldığında ya da silindiğinde bu özelliği devre dışı bırakın. - Normal oynatma ve tekli arama sırasında ileri geri oynatma modunu kapalı tutun. Örneğin, ileri sarma veya geri sarma düğmelerine basma, sarma çubuğuna sürüklemeden dokunma veya programatik olarak arama yapma.
Sürükleme hareketi dışında ileri geri oynatma modunu etkin bırakmanın istenmeyen yan etkileri vardır:
- Oynatma engellenir: Sarma modu etkinken ExoPlayer, oynatmanın engellenme nedenini
Player.PLAYBACK_SUPPRESSION_REASON_SCRUBBINGolarak ayarlar. Bu nedenlePlayer.isPlaying,falsedeğerini döndürür.setScrubbingModeEnabled(false)yöntemini çağırdığınızdaplayWhenReady,trueise normal oynatma otomatik olarak devam eder. - Görsel olmayan parçalar devre dışı bırakılır: Ses ve meta veri parçaları, ileri geri oynatma sırasında varsayılan olarak devre dışı bırakılır.
- Güç ve kod çözücü kullanımı artar: İleri geri oynatma modu, video codec'inin çalışma hızını artırır. Bu mod etkin bırakılırsa ek donanım ve pil kaynakları tüketilir.
Aşağıdaki örnekte, özel kaydırma çubuğu hareketi geri çağırmaları ExoPlayer öğesine bağlanmaktadır:
Kotlin
fun onScrubStart(exoPlayer: ExoPlayer, positionMs: Long) { exoPlayer.setScrubbingModeEnabled(true) exoPlayer.seekTo(positionMs) } fun onScrubMove(exoPlayer: ExoPlayer, positionMs: Long) { exoPlayer.seekTo(positionMs) } fun onScrubStop(exoPlayer: ExoPlayer, positionMs: Long, canceled: Boolean) { if (!canceled) { exoPlayer.seekTo(positionMs) } exoPlayer.setScrubbingModeEnabled(false) }
Java
public static void onScrubStart(ExoPlayer exoPlayer, long positionMs) { exoPlayer.setScrubbingModeEnabled(true); exoPlayer.seekTo(positionMs); } public static void onScrubMove(ExoPlayer exoPlayer, long positionMs) { exoPlayer.seekTo(positionMs); } public static void onScrubStop(ExoPlayer exoPlayer, long positionMs, boolean canceled) { if (!canceled) { exoPlayer.seekTo(positionMs); } exoPlayer.setScrubbingModeEnabled(false); }
Sarma modunun ileri geri oynatmayı optimize etme şekli
ScrubbingModeParameters.DEFAULT ile ExoPlayer, ileri geri oynatma modu etkinken çeşitli optimizasyonlar uygular:
- Hızlı aramaları birleştirir: ExoPlayer, her işaretçi hareketinde etkin bir aramayı önceden almak yerine, yalnızca sıraya alınan en son arama konumunu tutarken mevcut hedef kareyi çözmeyi ve oluşturmayı tamamlar. Yerine yenisi gelen ara aramalar bırakılır ve
AnalyticsListener.onDroppedSeeksWhileScrubbing'e bildirilir. - Ses ve meta veri parçalarını devre dışı bırakır: Ses patlamalarını ve gereksiz oluşturucu sıfırlamalarını önlemek için
C.TRACK_TYPE_AUDIOveC.TRACK_TYPE_METADATAparçalarını geçici olarak devre dışı bırakır. - Codec işleme kapasitesini artırır: Video
MediaCodecçalışma hızını artırır ve dinamik planlamanın etkinleştirilmesini sağlayarak kodu çözülmüş çıkış arabelleklerinin daha hızlı işlenmesini sağlar. - Kod çözücü sıfırlama işlemlerini azaltır: Uyumlu akışlarda bir ana kareye giderken gereksiz
MediaCodectemizleme işlemlerini önler, aynı resim grubu (GOP) içinde ileri doğru giderken ana kareye sıfırlama işlemini atlar ve Android 14 (API düzeyi 34) ve sonraki sürümlerdeMediaCodec.BUFFER_FLAG_DECODE_ONLYkullanarak kod çözücünün bir ana kare ile hedef kare arasındaki ara kareler için çıkış arabellekleri oluşturmayı atlamasını sağlar.
Temizleme parametrelerini özelleştirme
Hangi parça türlerinin devre dışı bırakılacağı veya codec çalışma hızlarının artırılıp artırılmayacağı gibi ileri geri oynatma davranışını ayarlamak için ScrubbingModeParameters.Builder kullanın. Yapılandırmayı ExoPlayer.setScrubbingModeParameters veya ExoPlayer.Builder.setScrubbingModeParameters ile uygulayın. Varsayılan optimizasyonların etkin kalması için exoPlayer.scrubbingModeParameters.buildUpon() (veya ScrubbingModeParameters.DEFAULT.buildUpon()) ile derleyin.
Kaydırıcı genişliğinden kesirli bir arama toleransı ayarlayın
Varsayılan olarak ScrubbingModeParameters, kesirli arama toleranslarını ayarlanmamış olarak bırakır (null). Bu nedenle, ileri geri oynatma sırasında aramalar oynatıcının SeekParameters değerini (varsayılan olarak SeekParameters.EXACT) kullanır. Tam arama, önceki ana kareden hedef zaman damgasına kadar her kareyi çözmelidir. Önceki kare anahtarından bu yana çok sayıda kare olduğunda (ör. geniş kare anahtarı aralıkları veya yüksek kare hızları olan akışlarda) bu kod çözme işlemi, sürükleme sırasında fark edilir bir gecikmeye neden olabilir.
Dokunmatik ekranlarda, kullanıcının parmağı kaydırma çubuğu boyunca 1dp veya 2dp hareketi güvenilir bir şekilde ayırt edemez. widthDp genişliğindeki bir kaydırma çubuğu için her iki yönde 2dp (toplam 4dp) gibi küçük bir dokunma toleransı, medya süresinin 2.0 / widthDp kadarına karşılık gelir. Bu kesrin kapsadığı medya süresi, videonun uzunluğuna bağlıdır. setFractionalSeekTolerance'ye, kullanıcı arayüzü kaydırıcınızın genişliğine bölünmüş dp tabanlı bir tolerans (ör. 2dp veya hız ile hassasiyeti dengelemek için 1dp ile 4dp arasında ayarlanmış) iletmenizi öneririz:
- Uzun içeriklerde (ör.
400dpkaydırma çubuğunda 1 saatlik bir video,2dp18 saniyeyi kapsar) ExoPlayer, ara karelerin kodunu çözmek yerine doğrudan dokunma toleransı dahilindeki yakındaki bir kareye geçebilir. - Kısa kliplerde (örneğin, aynı
400dpkaydırıcıda 10 saniyelik bir klip, burada2dp50 milisaniyeyi kapsar), aynı kesir, aramaları otomatik olarak hedef kareye yakın tutar.
Hedef dp toleransınızı ekran yoğunluğunu kullanarak piksele dönüştürün. Böylece, tolerans, kaydırıcının ölçülen piksel genişliğiyle eşleşir. Kaydırıcının genişliği veya ekran yoğunluğu her değiştiğinde (ör. ilk düzende veya ekran döndürüldüğünde) toleransı güncelleyin.
Jetpack Compose'da dp toleransını LocalDensity ile piksele dönüştürün, özel kaydırma çubuğunuzu Modifier.onSizeChanged ile ölçün ve oynatıcı, genişlik veya yoğunluk değiştiğinde scrubbingModeParameters değerini güncelleyin. ExoPlayer örneği, composable'dan daha uzun süre etkin kalırsa imha sırasında toleransı temizleyin:
private val SCRUBBING_TOLERANCE = 2.dp @Composable fun CustomScrubbingSlider(exoPlayer: ExoPlayer?, modifier: Modifier = Modifier) { var sliderWidthPx by remember { mutableIntStateOf(0) } val tolerancePx = with(LocalDensity.current) { SCRUBBING_TOLERANCE.toPx() } LaunchedEffect(exoPlayer, sliderWidthPx, tolerancePx) { if (exoPlayer != null && sliderWidthPx > 0) { val tolerance = (tolerancePx.toDouble() / sliderWidthPx).coerceIn(0.0, 1.0) exoPlayer.setFractionalSeekTolerance(tolerance) } } DisposableEffect(exoPlayer) { onDispose { exoPlayer?.setFractionalSeekTolerance(null) } } CustomSlider( player = exoPlayer, modifier = modifier.onSizeChanged { size -> sliderWidthPx = size.width }, ) } private fun ExoPlayer.setFractionalSeekTolerance(tolerance: Double?) { scrubbingModeParameters = scrubbingModeParameters .buildUpon() .setFractionalSeekTolerance( /* toleranceBefore= */ tolerance, /* toleranceAfter= */ tolerance, ) .build() }
Görünüm tabanlı bir kullanıcı arayüzünde, DisplayMetrics.density kullanarak dp toleransını piksele dönüştürün, kaydırma çubuğu görünümünüz veya PlayerView zaten düzenlenmişse mevcut genişliği uygulayın ve genişliği değiştiğinde scrubbingModeParameters değerini güncellemek için View.addOnLayoutChangeListener kullanın. Kullanıcı arayüzünü ayırırken dinleyiciyi kaldırın ve ExoPlayer örneği görünümden daha uzun süre kalırsa toleransı temizleyin:
Kotlin
private const val SCRUBBING_TOLERANCE_DP = 2.0 fun configureViewFractionalSeekTolerance( exoPlayer: ExoPlayer, scrubberView: View, ): View.OnLayoutChangeListener { updateFractionalSeekTolerance(exoPlayer, scrubberView, scrubberView.width) val listener = View.OnLayoutChangeListener { v, left, _, right, _, oldLeft, _, oldRight, _ -> val widthPx = right - left if (widthPx != oldRight - oldLeft) { updateFractionalSeekTolerance(exoPlayer, v, widthPx) } } scrubberView.addOnLayoutChangeListener(listener) return listener } fun clearViewFractionalSeekTolerance( exoPlayer: ExoPlayer, scrubberView: View, listener: View.OnLayoutChangeListener, ) { scrubberView.removeOnLayoutChangeListener(listener) setFractionalSeekTolerance(exoPlayer, tolerance = null) } private fun updateFractionalSeekTolerance( exoPlayer: ExoPlayer, scrubberView: View, widthPx: Int, ) { val density = scrubberView.resources.displayMetrics.density if (widthPx > 0 && density > 0f) { val tolerancePx = SCRUBBING_TOLERANCE_DP * density setFractionalSeekTolerance(exoPlayer, (tolerancePx / widthPx).coerceIn(0.0, 1.0)) } } private fun setFractionalSeekTolerance(exoPlayer: ExoPlayer, tolerance: Double?) { exoPlayer.scrubbingModeParameters = exoPlayer.scrubbingModeParameters .buildUpon() .setFractionalSeekTolerance( /* toleranceBefore= */ tolerance, /* toleranceAfter= */ tolerance, ) .build() }
Java
private static final double SCRUBBING_TOLERANCE_DP = 2.0; public static View.OnLayoutChangeListener configureViewFractionalSeekTolerance( ExoPlayer exoPlayer, View scrubberView) { updateFractionalSeekTolerance(exoPlayer, scrubberView, scrubberView.getWidth()); View.OnLayoutChangeListener listener = (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { int widthPx = right - left; if (widthPx != oldRight - oldLeft) { updateFractionalSeekTolerance(exoPlayer, v, widthPx); } }; scrubberView.addOnLayoutChangeListener(listener); return listener; } public static void clearViewFractionalSeekTolerance( ExoPlayer exoPlayer, View scrubberView, View.OnLayoutChangeListener listener) { scrubberView.removeOnLayoutChangeListener(listener); setFractionalSeekTolerance(exoPlayer, /* tolerance= */ null); } private static void updateFractionalSeekTolerance( ExoPlayer exoPlayer, View scrubberView, int widthPx) { float density = scrubberView.getResources().getDisplayMetrics().density; if (widthPx > 0 && density > 0f) { double tolerancePx = SCRUBBING_TOLERANCE_DP * density; setFractionalSeekTolerance(exoPlayer, Math.min(1.0, tolerancePx / widthPx)); } } private static void setFractionalSeekTolerance(ExoPlayer exoPlayer, @Nullable Double tolerance) { exoPlayer.setScrubbingModeParameters( exoPlayer .getScrubbingModeParameters() .buildUpon() .setFractionalSeekTolerance( /* toleranceBefore= */ tolerance, /* toleranceAfter= */ tolerance) .build()); }
dp tabanlı bir tolerans kullandığınızda, onScrubStop içindeki son seekTo çağrısı boyunca ileri geri oynatma modunu etkin tutmak, kullanıcı parmağını kaldırdığında görsel bir kare atlamasını önler. Uygulamanız, son bırakma konumunun tam bir zaman damgasına denk gelmesini gerektiriyorsa son seekTo çağrınızdan önce setScrubbingModeEnabled(false) çağrısı yapın. Böylece son arama, oynatıcının standart SeekParameters değerini kullanır.
Medya ve ağ ile ilgili dikkat edilmesi gerekenler
Sarma modu, yerel arama planlamasını ve donanım kod çözme işlemini optimize eder. Bu nedenle, yerel dosyalar, indirilen medya, önbelleğe alınmış akışlar veya sık ana karelerle (kısa GOP'ler) kodlanmış arabelleğe alınmış uyarlanabilir akışlarda en etkilidir. Önbelleğe alınmamış, yüksek gecikmeli ağ bağlantılarında, arabelleğe alınmamış konumlara sık sık yapılan aramalar ağ gidiş-dönüş süreleriyle sınırlı kalır.