feat(exoplayer): let the read-ahead buffer depth be chosen instead of fixed at 50s
ExoPlayer's DefaultLoadControl was built with hard-coded durations picked from one memory tier, so read-ahead stopped at 50s on any device reporting 2GB or less free, with no way to raise it. On hardware where mpv cannot render at all that ceiling is the whole buffer budget. Playback Buffer offers Auto, Large and Extra Large. The durations are taken from jellyfin-androidtv and jellyfin-android so the same words mean the same thing across Jellyfin clients; Auto keeps the memory-tiered values that shipped. Named tiers rather than a duration because a duration would be a promise the load control cannot keep: prioritizeTimeOverSizeThresholds is disabled, so targetBufferBytes stops the loader even below minBufferMs and the byte cap binds first above roughly 23 Mbit/s. The tier crosses the method channel as a string and resolves in the core, where an unrecognised name falls back to Auto. LoadControlPolicy clamps the resulting pair: media3 validates the ordering with Guava Preconditions, an unconditional throw R8 does not elide, so a bad pair would be an IllegalArgumentException out of player construction rather than a bad buffer. The two play-start thresholds stay fixed even though the Jellyfin tiers move them. BufferingStallPolicy.MIN_BUFFER_AHEAD_MS is a const derived from BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS, so a runtime value there would make the stall watchdog indict a player that is obeying its own load control. That leaves the byte target as a second, often smaller ceiling, and nothing surfaced either. The resolved values now reach getStats, and the overlay's Buffer section gains a Cache Limit row reading "120s / 128MB" next to the buffered-ahead duration, so a tier that appears to do nothing on a high-bitrate file explains itself. close #1816
This commit is contained in:
@@ -120,6 +120,7 @@
|
||||
"darkTheme": "Tünd",
|
||||
"oledTheme": "OLED",
|
||||
"libraryDensity": "Kitabxana sıxlığı",
|
||||
"displayScale": "",
|
||||
"compact": "Sıx",
|
||||
"comfortable": "Rəhat",
|
||||
"tvCornerSpotlightBackdrop": "Künc işıqlandırma fonu",
|
||||
@@ -169,6 +170,11 @@
|
||||
"bufferSizeMB": "${size}MB",
|
||||
"bufferSizeAuto": "Avtomatik (Tövsiyə olunan)",
|
||||
"bufferSizeWarning": "${heap}MB yaddaş əlçatandır. ${size}MB bufer oynatmaya təsir edə bilər.",
|
||||
"playbackBuffer": "",
|
||||
"playbackBufferAuto": "",
|
||||
"playbackBufferLarge": "",
|
||||
"playbackBufferExtraLarge": "",
|
||||
"playbackBufferDescription": "",
|
||||
"defaultQualityTitle": "Defolt keyfiyyət",
|
||||
"musicQualityTitle": "Musiqi keyfiyyəti",
|
||||
"subtitleStyling": "Altyazı tənzimləmələri",
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"darkTheme": "Тъмна",
|
||||
"oledTheme": "OLED",
|
||||
"libraryDensity": "Плътност на библиотеката",
|
||||
"displayScale": "",
|
||||
"compact": "Компактна",
|
||||
"comfortable": "Удобна",
|
||||
"tvCornerSpotlightBackdrop": "Фон с акцент в ъгъла",
|
||||
@@ -169,6 +170,11 @@
|
||||
"bufferSizeMB": "${size} MB",
|
||||
"bufferSizeAuto": "Автоматично (препоръчително)",
|
||||
"bufferSizeWarning": "Налична памет: ${heap} MB. Буфер от ${size} MB може да повлияе на възпроизвеждането.",
|
||||
"playbackBuffer": "",
|
||||
"playbackBufferAuto": "",
|
||||
"playbackBufferLarge": "",
|
||||
"playbackBufferExtraLarge": "",
|
||||
"playbackBufferDescription": "",
|
||||
"defaultQualityTitle": "Качество по подразбиране",
|
||||
"musicQualityTitle": "Качество на музиката",
|
||||
"subtitleStyling": "Стил на субтитрите",
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"darkTheme": "Mørk",
|
||||
"oledTheme": "OLED",
|
||||
"libraryDensity": "Bibliotekstæthed",
|
||||
"displayScale": "",
|
||||
"compact": "Kompakt",
|
||||
"comfortable": "Komfortabel",
|
||||
"tvCornerSpotlightBackdrop": "Fremhævet baggrundsbillede i hjørnet",
|
||||
@@ -169,6 +170,11 @@
|
||||
"bufferSizeMB": "${size}MB",
|
||||
"bufferSizeAuto": "Automatisk (anbefalet)",
|
||||
"bufferSizeWarning": "${heap} MB hukommelse tilgængelig. En buffer på ${size} MB kan påvirke afspilningen.",
|
||||
"playbackBuffer": "",
|
||||
"playbackBufferAuto": "",
|
||||
"playbackBufferLarge": "",
|
||||
"playbackBufferExtraLarge": "",
|
||||
"playbackBufferDescription": "",
|
||||
"defaultQualityTitle": "Standardkvalitet",
|
||||
"musicQualityTitle": "Musikkvalitet",
|
||||
"subtitleStyling": "Undertekststil",
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"darkTheme": "Dunkel",
|
||||
"oledTheme": "OLED",
|
||||
"libraryDensity": "Darstellungsdichte der Mediathek",
|
||||
"displayScale": "",
|
||||
"compact": "Kompakt",
|
||||
"comfortable": "Großzügig",
|
||||
"tvCornerSpotlightBackdrop": "Backdrop in der Ecke",
|
||||
@@ -169,6 +170,11 @@
|
||||
"bufferSizeMB": "${size} MB",
|
||||
"bufferSizeAuto": "Automatisch (empfohlen)",
|
||||
"bufferSizeWarning": "${heap} MB Speicher verfügbar. Ein Puffer von ${size} MB kann die Wiedergabe beeinträchtigen.",
|
||||
"playbackBuffer": "",
|
||||
"playbackBufferAuto": "",
|
||||
"playbackBufferLarge": "",
|
||||
"playbackBufferExtraLarge": "",
|
||||
"playbackBufferDescription": "",
|
||||
"defaultQualityTitle": "Standardqualität",
|
||||
"musicQualityTitle": "Musikqualität",
|
||||
"subtitleStyling": "Untertitel-Stil",
|
||||
|
||||
@@ -170,6 +170,11 @@
|
||||
"bufferSizeMB": "${size}MB",
|
||||
"bufferSizeAuto": "Auto (Recommended)",
|
||||
"bufferSizeWarning": "${heap}MB memory available. A ${size}MB buffer may affect playback.",
|
||||
"playbackBuffer": "Playback Buffer",
|
||||
"playbackBufferAuto": "Auto (Recommended)",
|
||||
"playbackBufferLarge": "Large",
|
||||
"playbackBufferExtraLarge": "Extra Large",
|
||||
"playbackBufferDescription": "Buffer more against unstable connections. Also limited by Buffer Size.",
|
||||
"defaultQualityTitle": "Default Quality",
|
||||
"musicQualityTitle": "Music Quality",
|
||||
"subtitleStyling": "Subtitle Styling",
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"darkTheme": "Oscuro",
|
||||
"oledTheme": "OLED",
|
||||
"libraryDensity": "Densidad de la biblioteca",
|
||||
"displayScale": "",
|
||||
"compact": "Compacto",
|
||||
"comfortable": "Cómodo",
|
||||
"tvCornerSpotlightBackdrop": "Imagen destacada en la esquina",
|
||||
@@ -169,6 +170,11 @@
|
||||
"bufferSizeMB": "${size} MB",
|
||||
"bufferSizeAuto": "Automático (recomendado)",
|
||||
"bufferSizeWarning": "${heap} MB de memoria disponible. Un búfer de ${size} MB puede afectar a la reproducción.",
|
||||
"playbackBuffer": "",
|
||||
"playbackBufferAuto": "",
|
||||
"playbackBufferLarge": "",
|
||||
"playbackBufferExtraLarge": "",
|
||||
"playbackBufferDescription": "",
|
||||
"defaultQualityTitle": "Calidad predeterminada",
|
||||
"musicQualityTitle": "Calidad de música",
|
||||
"subtitleStyling": "Estilo de subtítulos",
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"darkTheme": "Sombre",
|
||||
"oledTheme": "OLED",
|
||||
"libraryDensity": "Densité des bibliothèques",
|
||||
"displayScale": "",
|
||||
"compact": "Compact",
|
||||
"comfortable": "Confortable",
|
||||
"tvCornerSpotlightBackdrop": "Illustration en vedette dans le coin",
|
||||
@@ -169,6 +170,11 @@
|
||||
"bufferSizeMB": "${size} Mo",
|
||||
"bufferSizeAuto": "Automatique (recommandé)",
|
||||
"bufferSizeWarning": "${heap} Mo de mémoire disponible. Un tampon de ${size} Mo peut affecter la lecture.",
|
||||
"playbackBuffer": "",
|
||||
"playbackBufferAuto": "",
|
||||
"playbackBufferLarge": "",
|
||||
"playbackBufferExtraLarge": "",
|
||||
"playbackBufferDescription": "",
|
||||
"defaultQualityTitle": "Qualité par défaut",
|
||||
"musicQualityTitle": "Qualité de la musique",
|
||||
"subtitleStyling": "Style des sous-titres",
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"darkTheme": "Sötét",
|
||||
"oledTheme": "OLED",
|
||||
"libraryDensity": "Könyvtársűrűség",
|
||||
"displayScale": "",
|
||||
"compact": "Kompakt",
|
||||
"comfortable": "Kényelmes",
|
||||
"tvCornerSpotlightBackdrop": "Sarokban megjelenő kiemelt háttérkép",
|
||||
@@ -169,6 +170,11 @@
|
||||
"bufferSizeMB": "${size} MB",
|
||||
"bufferSizeAuto": "Automatikus (ajánlott)",
|
||||
"bufferSizeWarning": "${heap} MB memória érhető el. A(z) ${size} MB méretű puffer befolyásolhatja a lejátszást.",
|
||||
"playbackBuffer": "",
|
||||
"playbackBufferAuto": "",
|
||||
"playbackBufferLarge": "",
|
||||
"playbackBufferExtraLarge": "",
|
||||
"playbackBufferDescription": "",
|
||||
"defaultQualityTitle": "Alapértelmezett minőség",
|
||||
"musicQualityTitle": "Zene minősége",
|
||||
"subtitleStyling": "Feliratok stílusa",
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"darkTheme": "Scuro",
|
||||
"oledTheme": "OLED",
|
||||
"libraryDensity": "Densità della libreria",
|
||||
"displayScale": "",
|
||||
"compact": "Compatta",
|
||||
"comfortable": "Comoda",
|
||||
"tvCornerSpotlightBackdrop": "Sfondo in evidenza nell'angolo",
|
||||
@@ -169,6 +170,11 @@
|
||||
"bufferSizeMB": "${size}MB",
|
||||
"bufferSizeAuto": "Automatica (consigliata)",
|
||||
"bufferSizeWarning": "${heap}MB di memoria disponibile. Un buffer di ${size}MB può influire sulla riproduzione.",
|
||||
"playbackBuffer": "",
|
||||
"playbackBufferAuto": "",
|
||||
"playbackBufferLarge": "",
|
||||
"playbackBufferExtraLarge": "",
|
||||
"playbackBufferDescription": "",
|
||||
"defaultQualityTitle": "Qualità predefinita",
|
||||
"musicQualityTitle": "Qualità musicale",
|
||||
"subtitleStyling": "Stile sottotitoli",
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"darkTheme": "ダーク",
|
||||
"oledTheme": "OLED",
|
||||
"libraryDensity": "ライブラリの密度",
|
||||
"displayScale": "",
|
||||
"compact": "コンパクト",
|
||||
"comfortable": "ゆったり",
|
||||
"tvCornerSpotlightBackdrop": "画面隅の注目コンテンツ背景",
|
||||
@@ -169,6 +170,11 @@
|
||||
"bufferSizeMB": "${size}MB",
|
||||
"bufferSizeAuto": "自動(推奨)",
|
||||
"bufferSizeWarning": "${heap}MBのメモリが利用可能です。${size}MBのバッファは再生に影響する可能性があります。",
|
||||
"playbackBuffer": "",
|
||||
"playbackBufferAuto": "",
|
||||
"playbackBufferLarge": "",
|
||||
"playbackBufferExtraLarge": "",
|
||||
"playbackBufferDescription": "",
|
||||
"defaultQualityTitle": "デフォルト画質",
|
||||
"musicQualityTitle": "音楽の音質",
|
||||
"subtitleStyling": "字幕スタイル",
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"darkTheme": "Қараңғы",
|
||||
"oledTheme": "OLED",
|
||||
"libraryDensity": "Кітапхана тығыздығы",
|
||||
"displayScale": "",
|
||||
"compact": "Тығыз",
|
||||
"comfortable": "Ыңғайлы",
|
||||
"tvCornerSpotlightBackdrop": "Бұрыштық жарық фоны",
|
||||
@@ -169,6 +170,11 @@
|
||||
"bufferSizeMB": "${size} МБ",
|
||||
"bufferSizeAuto": "Автоматты (Ұсынылатын)",
|
||||
"bufferSizeWarning": "${heap} МБ ЖҰД қолжетімді. ${size} МБ буфер ойнатуға әсер етуі мүмкін.",
|
||||
"playbackBuffer": "",
|
||||
"playbackBufferAuto": "",
|
||||
"playbackBufferLarge": "",
|
||||
"playbackBufferExtraLarge": "",
|
||||
"playbackBufferDescription": "",
|
||||
"defaultQualityTitle": "Әдепкі сапа",
|
||||
"musicQualityTitle": "Музыка сапасы",
|
||||
"subtitleStyling": "Субтитр баптаулары",
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"darkTheme": "다크",
|
||||
"oledTheme": "OLED",
|
||||
"libraryDensity": "라이브러리 표시 밀도",
|
||||
"displayScale": "",
|
||||
"compact": "조밀하게",
|
||||
"comfortable": "여유롭게",
|
||||
"tvCornerSpotlightBackdrop": "모서리 스포트라이트 배경",
|
||||
@@ -169,6 +170,11 @@
|
||||
"bufferSizeMB": "${size}MB",
|
||||
"bufferSizeAuto": "자동 (권장)",
|
||||
"bufferSizeWarning": "${heap}MB 메모리를 사용할 수 있습니다. ${size}MB 버퍼는 재생에 영향을 줄 수 있습니다.",
|
||||
"playbackBuffer": "",
|
||||
"playbackBufferAuto": "",
|
||||
"playbackBufferLarge": "",
|
||||
"playbackBufferExtraLarge": "",
|
||||
"playbackBufferDescription": "",
|
||||
"defaultQualityTitle": "기본 화질",
|
||||
"musicQualityTitle": "음악 음질",
|
||||
"subtitleStyling": "자막 스타일",
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"darkTheme": "Mørkt",
|
||||
"oledTheme": "OLED",
|
||||
"libraryDensity": "Innholdstetthet i biblioteket",
|
||||
"displayScale": "",
|
||||
"compact": "Kompakt",
|
||||
"comfortable": "Komfortabel",
|
||||
"tvCornerSpotlightBackdrop": "Fremhevet bakgrunn i hjørnet",
|
||||
@@ -169,6 +170,11 @@
|
||||
"bufferSizeMB": "${size} MB",
|
||||
"bufferSizeAuto": "Automatisk (anbefalt)",
|
||||
"bufferSizeWarning": "${heap} MB minne tilgjengelig. En buffer på ${size} MB kan påvirke avspillingen.",
|
||||
"playbackBuffer": "",
|
||||
"playbackBufferAuto": "",
|
||||
"playbackBufferLarge": "",
|
||||
"playbackBufferExtraLarge": "",
|
||||
"playbackBufferDescription": "",
|
||||
"defaultQualityTitle": "Standardkvalitet",
|
||||
"musicQualityTitle": "Musikkvalitet",
|
||||
"subtitleStyling": "Undertekststil",
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"darkTheme": "Donker",
|
||||
"oledTheme": "OLED",
|
||||
"libraryDensity": "Bibliotheekdichtheid",
|
||||
"displayScale": "",
|
||||
"compact": "Compact",
|
||||
"comfortable": "Comfortabel",
|
||||
"tvCornerSpotlightBackdrop": "Uitgelichte achtergrond in de hoek",
|
||||
@@ -169,6 +170,11 @@
|
||||
"bufferSizeMB": "${size}MB",
|
||||
"bufferSizeAuto": "Automatisch (aanbevolen)",
|
||||
"bufferSizeWarning": "${heap}MB geheugen beschikbaar. Een buffer van ${size}MB kan afspelen beïnvloeden.",
|
||||
"playbackBuffer": "",
|
||||
"playbackBufferAuto": "",
|
||||
"playbackBufferLarge": "",
|
||||
"playbackBufferExtraLarge": "",
|
||||
"playbackBufferDescription": "",
|
||||
"defaultQualityTitle": "Standaardkwaliteit",
|
||||
"musicQualityTitle": "Muziekkwaliteit",
|
||||
"subtitleStyling": "Ondertitelopmaak",
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"darkTheme": "Ciemny",
|
||||
"oledTheme": "OLED",
|
||||
"libraryDensity": "Gęstość biblioteki",
|
||||
"displayScale": "",
|
||||
"compact": "Kompaktowy",
|
||||
"comfortable": "Wygodny",
|
||||
"tvCornerSpotlightBackdrop": "Tło wyróżnionej pozycji w rogu",
|
||||
@@ -169,6 +170,11 @@
|
||||
"bufferSizeMB": "${size}MB",
|
||||
"bufferSizeAuto": "Automatyczny (zalecany)",
|
||||
"bufferSizeWarning": "Dostępna pamięć: ${heap}MB. Bufor ${size}MB może wpłynąć na odtwarzanie.",
|
||||
"playbackBuffer": "",
|
||||
"playbackBufferAuto": "",
|
||||
"playbackBufferLarge": "",
|
||||
"playbackBufferExtraLarge": "",
|
||||
"playbackBufferDescription": "",
|
||||
"defaultQualityTitle": "Domyślna jakość",
|
||||
"musicQualityTitle": "Jakość muzyki",
|
||||
"subtitleStyling": "Styl napisów",
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"darkTheme": "Escuro",
|
||||
"oledTheme": "OLED",
|
||||
"libraryDensity": "Densidade da Biblioteca",
|
||||
"displayScale": "",
|
||||
"compact": "Compacto",
|
||||
"comfortable": "Confortável",
|
||||
"tvCornerSpotlightBackdrop": "Imagem de destaque no canto",
|
||||
@@ -169,6 +170,11 @@
|
||||
"bufferSizeMB": "${size}MB",
|
||||
"bufferSizeAuto": "Automático (Recomendado)",
|
||||
"bufferSizeWarning": "${heap}MB de memória disponível. Um buffer de ${size}MB pode afetar a reprodução.",
|
||||
"playbackBuffer": "",
|
||||
"playbackBufferAuto": "",
|
||||
"playbackBufferLarge": "",
|
||||
"playbackBufferExtraLarge": "",
|
||||
"playbackBufferDescription": "",
|
||||
"defaultQualityTitle": "Qualidade padrão",
|
||||
"musicQualityTitle": "Qualidade da música",
|
||||
"subtitleStyling": "Estilo de Legendas",
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"darkTheme": "Тёмная",
|
||||
"oledTheme": "OLED",
|
||||
"libraryDensity": "Плотность библиотеки",
|
||||
"displayScale": "",
|
||||
"compact": "Компактный",
|
||||
"comfortable": "Комфортный",
|
||||
"tvCornerSpotlightBackdrop": "Фоновое изображение в углу",
|
||||
@@ -169,6 +170,11 @@
|
||||
"bufferSizeMB": "${size}МБ",
|
||||
"bufferSizeAuto": "Авто (Рекомендуется)",
|
||||
"bufferSizeWarning": "Доступно памяти: ${heap} МБ. Буфер размером ${size} МБ может повлиять на воспроизведение.",
|
||||
"playbackBuffer": "",
|
||||
"playbackBufferAuto": "",
|
||||
"playbackBufferLarge": "",
|
||||
"playbackBufferExtraLarge": "",
|
||||
"playbackBufferDescription": "",
|
||||
"defaultQualityTitle": "Качество по умолчанию",
|
||||
"musicQualityTitle": "Качество музыки",
|
||||
"subtitleStyling": "Стиль субтитров",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
/// To regenerate, run: `dart run slang`
|
||||
///
|
||||
/// Locales: 22
|
||||
/// Strings: 32738 (1488 per locale)
|
||||
/// Strings: 32743 (1488 per locale)
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint, unused_import
|
||||
|
||||
@@ -588,6 +588,21 @@ class Translations$settings$en {
|
||||
/// en: '${heap}MB memory available. A ${size}MB buffer may affect playback.'
|
||||
String bufferSizeWarning({required Object heap, required Object size}) => '${heap}MB memory available. A ${size}MB buffer may affect playback.';
|
||||
|
||||
/// en: 'Playback Buffer'
|
||||
String get playbackBuffer => 'Playback Buffer';
|
||||
|
||||
/// en: 'Auto (Recommended)'
|
||||
String get playbackBufferAuto => 'Auto (Recommended)';
|
||||
|
||||
/// en: 'Large'
|
||||
String get playbackBufferLarge => 'Large';
|
||||
|
||||
/// en: 'Extra Large'
|
||||
String get playbackBufferExtraLarge => 'Extra Large';
|
||||
|
||||
/// en: 'Buffer more against unstable connections. Also limited by Buffer Size.'
|
||||
String get playbackBufferDescription => 'Buffer more against unstable connections. Also limited by Buffer Size.';
|
||||
|
||||
/// en: 'Default Quality'
|
||||
String get defaultQualityTitle => 'Default Quality';
|
||||
|
||||
@@ -6261,6 +6276,11 @@ extension on Translations {
|
||||
'settings.bufferSizeMB' => ({required Object size}) => '${size}MB',
|
||||
'settings.bufferSizeAuto' => 'Auto (Recommended)',
|
||||
'settings.bufferSizeWarning' => ({required Object heap, required Object size}) => '${heap}MB memory available. A ${size}MB buffer may affect playback.',
|
||||
'settings.playbackBuffer' => 'Playback Buffer',
|
||||
'settings.playbackBufferAuto' => 'Auto (Recommended)',
|
||||
'settings.playbackBufferLarge' => 'Large',
|
||||
'settings.playbackBufferExtraLarge' => 'Extra Large',
|
||||
'settings.playbackBufferDescription' => 'Buffer more against unstable connections. Also limited by Buffer Size.',
|
||||
'settings.defaultQualityTitle' => 'Default Quality',
|
||||
'settings.musicQualityTitle' => 'Music Quality',
|
||||
'settings.subtitleStyling' => 'Subtitle Styling',
|
||||
@@ -6610,13 +6630,13 @@ extension on Translations {
|
||||
'mediaMenu.deleteMovieTitle' => 'Delete this movie?',
|
||||
'mediaMenu.deleteEpisodeConfirm' => 'Delete episode',
|
||||
'mediaMenu.deleteSeasonConfirm' => 'Delete season',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'mediaMenu.deleteShowConfirm' => 'Delete show',
|
||||
'mediaMenu.deleteMovieConfirm' => 'Delete movie',
|
||||
'mediaMenu.deleteAnyway' => 'Delete anyway',
|
||||
'mediaMenu.confirmDeleteTarget' => ({required Object title}) => 'Permanently delete ${title} from your server?',
|
||||
'mediaMenu.deleteMultipleWarning' => 'This includes all episodes and their files.',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'mediaMenu.deleteEpisodeCountWarning' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('en'))(n, one: 'This deletes all ${n} episode in it, and its file.', other: 'This deletes all ${n} episodes in it, and their files.', ),
|
||||
'mediaMenu.deleteMultiPartWarning' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('en'))(n, one: 'This item is stored as ${n} file, which will be deleted.', other: 'This item is stored across ${n} files, and all of them will be deleted.', ),
|
||||
'mediaMenu.deleteSharedFileHeading' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('en'))(n, one: '${n} other episode is stored in the same file and will be deleted too:', other: '${n} other episodes are stored in the same file and will be deleted too:', ),
|
||||
@@ -7124,13 +7144,13 @@ extension on Translations {
|
||||
'explore.badge.pendingApproval' => 'Pending approval',
|
||||
'explore.badge.processing' => 'Processing',
|
||||
'explore.badge.declined' => 'Declined',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'explore.badge.requestFailed' => 'Request failed',
|
||||
'explore.badge.requested4k' => '4K requested',
|
||||
'explore.badge.seasonsAvailable' => ({required Object available, required Object total}) => '${available}/${total} seasons',
|
||||
'explore.badge.nextEpisodeIn' => ({required Object episode, required Object duration}) => 'Ep ${episode} in ${duration}',
|
||||
'explore.badge.nextAiringIn' => ({required Object duration}) => 'Next in ${duration}',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'explore.badge.episodesShort' => ({required Object n}) => '${n} eps',
|
||||
'explore.badge.minutesPerEpisode' => ({required Object n}) => '${n} min/ep',
|
||||
'explore.badge.adult' => '18+',
|
||||
@@ -7638,13 +7658,13 @@ extension on Translations {
|
||||
'performanceOverlay.dvRpus' => 'DV RPUs',
|
||||
'performanceOverlay.dvRpuAverage' => 'DV RPU Avg',
|
||||
'performanceOverlay.dvSampleAverage' => 'DV Sample Avg',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'performanceOverlay.maxLuma' => 'Max Luma',
|
||||
'performanceOverlay.minLuma' => 'Min Luma',
|
||||
'performanceOverlay.maxCll' => 'MaxCLL',
|
||||
'performanceOverlay.maxFall' => 'MaxFALL',
|
||||
'performanceOverlay.cacheUsed' => 'Cache Used',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'performanceOverlay.cacheLimit' => 'Cache Limit',
|
||||
'performanceOverlay.speed' => 'Speed',
|
||||
'performanceOverlay.player' => 'Player',
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"darkTheme": "Mörkt",
|
||||
"oledTheme": "OLED",
|
||||
"libraryDensity": "Biblioteksdensitet",
|
||||
"displayScale": "",
|
||||
"compact": "Kompakt",
|
||||
"comfortable": "Luftig",
|
||||
"tvCornerSpotlightBackdrop": "Bakgrundsbild för utvalt innehåll i hörnet",
|
||||
@@ -169,6 +170,11 @@
|
||||
"bufferSizeMB": "${size} MB",
|
||||
"bufferSizeAuto": "Automatiskt (rekommenderas)",
|
||||
"bufferSizeWarning": "${heap} MB minne är tillgängligt. En buffert på ${size} MB kan påverka uppspelningen.",
|
||||
"playbackBuffer": "",
|
||||
"playbackBufferAuto": "",
|
||||
"playbackBufferLarge": "",
|
||||
"playbackBufferExtraLarge": "",
|
||||
"playbackBufferDescription": "",
|
||||
"defaultQualityTitle": "Standardkvalitet",
|
||||
"musicQualityTitle": "Musikkvalitet",
|
||||
"subtitleStyling": "Utseende för undertexter",
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"darkTheme": "Koyu",
|
||||
"oledTheme": "OLED",
|
||||
"libraryDensity": "Kitaplık Yoğunluğu",
|
||||
"displayScale": "",
|
||||
"compact": "Sıkışık",
|
||||
"comfortable": "Rahat",
|
||||
"tvCornerSpotlightBackdrop": "Köşe Öne Çıkan Arka Plan",
|
||||
@@ -169,6 +170,11 @@
|
||||
"bufferSizeMB": "${size}MB",
|
||||
"bufferSizeAuto": "Otomatik (Önerilen)",
|
||||
"bufferSizeWarning": "${heap}MB bellek mevcut. ${size}MB arabellek oynatmayı etkileyebilir.",
|
||||
"playbackBuffer": "",
|
||||
"playbackBufferAuto": "",
|
||||
"playbackBufferLarge": "",
|
||||
"playbackBufferExtraLarge": "",
|
||||
"playbackBufferDescription": "",
|
||||
"defaultQualityTitle": "Varsayılan Kalite",
|
||||
"musicQualityTitle": "Müzik Kalitesi",
|
||||
"subtitleStyling": "Altyazı Biçimlendirmesi",
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"darkTheme": "Toʻq",
|
||||
"oledTheme": "OLED",
|
||||
"libraryDensity": "Kutubxona zichligi",
|
||||
"displayScale": "",
|
||||
"compact": "Ixcham",
|
||||
"comfortable": "Qulay",
|
||||
"tvCornerSpotlightBackdrop": "Burchak yoritish foni",
|
||||
@@ -169,6 +170,11 @@
|
||||
"bufferSizeMB": "${size} MB",
|
||||
"bufferSizeAuto": "Avtomatik (Tavsiya etilgan)",
|
||||
"bufferSizeWarning": "${heap} MB Xotira mavjud. ${size} MB bufer ijroga taʼsir qilishi mumkin.",
|
||||
"playbackBuffer": "",
|
||||
"playbackBufferAuto": "",
|
||||
"playbackBufferLarge": "",
|
||||
"playbackBufferExtraLarge": "",
|
||||
"playbackBufferDescription": "",
|
||||
"defaultQualityTitle": "Standart sifat",
|
||||
"musicQualityTitle": "Musiqa sifati",
|
||||
"subtitleStyling": "Subtitr sozlamalari",
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"darkTheme": "深色",
|
||||
"oledTheme": "OLED 純黑",
|
||||
"libraryDensity": "媒體庫版面配置密度",
|
||||
"displayScale": "",
|
||||
"compact": "緊湊",
|
||||
"comfortable": "舒適",
|
||||
"tvCornerSpotlightBackdrop": "右上角焦點背景圖",
|
||||
@@ -169,6 +170,11 @@
|
||||
"bufferSizeMB": "${size} MB",
|
||||
"bufferSizeAuto": "自動(推薦)",
|
||||
"bufferSizeWarning": "可用記憶體為 ${heap} MB。設定 ${size} MB 緩衝可能影響播放穩定性。",
|
||||
"playbackBuffer": "",
|
||||
"playbackBufferAuto": "",
|
||||
"playbackBufferLarge": "",
|
||||
"playbackBufferExtraLarge": "",
|
||||
"playbackBufferDescription": "",
|
||||
"defaultQualityTitle": "預設畫質",
|
||||
"musicQualityTitle": "音樂品質",
|
||||
"subtitleStyling": "字幕樣式",
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"darkTheme": "深色",
|
||||
"oledTheme": "OLED",
|
||||
"libraryDensity": "媒体库密度",
|
||||
"displayScale": "",
|
||||
"compact": "紧凑",
|
||||
"comfortable": "舒适",
|
||||
"tvCornerSpotlightBackdrop": "右上角聚焦背景图",
|
||||
@@ -169,6 +170,11 @@
|
||||
"bufferSizeMB": "${size}MB",
|
||||
"bufferSizeAuto": "自动(推荐)",
|
||||
"bufferSizeWarning": "可用内存 ${heap}MB。${size}MB 缓冲可能影响播放。",
|
||||
"playbackBuffer": "",
|
||||
"playbackBufferAuto": "",
|
||||
"playbackBufferLarge": "",
|
||||
"playbackBufferExtraLarge": "",
|
||||
"playbackBufferDescription": "",
|
||||
"defaultQualityTitle": "默认画质",
|
||||
"musicQualityTitle": "音乐音质",
|
||||
"subtitleStyling": "字幕样式",
|
||||
|
||||
@@ -13,6 +13,7 @@ class PlayerAndroid extends PlayerBase {
|
||||
|
||||
int? _bufferSizeBytes;
|
||||
bool _bufferSizeIsAuto = false;
|
||||
String _bufferTier = 'auto';
|
||||
bool _tunnelingEnabled = true;
|
||||
String _dvConversionMode = 'auto';
|
||||
bool _audioNormalizationEnabled = false;
|
||||
@@ -115,6 +116,7 @@ class PlayerAndroid extends PlayerBase {
|
||||
final result = await invoke<bool>('initialize', {
|
||||
'bufferSizeBytes': _bufferSizeBytes,
|
||||
'bufferSizeAuto': _bufferSizeIsAuto,
|
||||
'bufferTier': _bufferTier,
|
||||
'tunnelingEnabled': _tunnelingEnabled,
|
||||
'dvConversionMode': _dvConversionMode,
|
||||
'audioPassthroughEnabled': _audioPassthroughEnabled,
|
||||
@@ -331,6 +333,12 @@ class PlayerAndroid extends PlayerBase {
|
||||
case 'demuxer-max-bytes-auto':
|
||||
_bufferSizeIsAuto = value != 'no';
|
||||
break;
|
||||
// Not an mpv property. mpv read-ahead is owned by the mpv.conf editor; this tier is
|
||||
// a named ExoPlayer read-ahead depth rather than a duration because the byte cap can
|
||||
// bind first (#1816).
|
||||
case 'exo-buffer-tier':
|
||||
_bufferTier = value;
|
||||
break;
|
||||
case 'tunneled-playback':
|
||||
_tunnelingEnabled = value != 'no';
|
||||
break;
|
||||
|
||||
@@ -91,6 +91,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
if (PlatformDetector.isAppleTV()) _atmosDiagnosticsTile(),
|
||||
if (exoActive) _dvConversionModeTile(),
|
||||
_bufferSizeTile(),
|
||||
if (exoActive) _playbackBufferTile(),
|
||||
_defaultQualityTile(),
|
||||
_musicQualityTile(),
|
||||
],
|
||||
@@ -433,6 +434,22 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _playbackBufferTile() => SettingSelectionTile<PlaybackBufferTier>(
|
||||
pref: SettingsService.playbackBufferTier,
|
||||
icon: Symbols.hourglass_top_rounded,
|
||||
title: t.settings.playbackBuffer,
|
||||
subtitleBuilder: (tier) => '${_playbackBufferLabel(tier)} · ${t.settings.playbackBufferDescription}',
|
||||
options: PlaybackBufferTier.values
|
||||
.map((tier) => DialogOption(value: tier, title: _playbackBufferLabel(tier)))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
String _playbackBufferLabel(PlaybackBufferTier tier) => switch (tier) {
|
||||
PlaybackBufferTier.auto => t.settings.playbackBufferAuto,
|
||||
PlaybackBufferTier.large => t.settings.playbackBufferLarge,
|
||||
PlaybackBufferTier.extraLarge => t.settings.playbackBufferExtraLarge,
|
||||
};
|
||||
|
||||
Widget _defaultQualityTile() => SettingSelectionTile<TranscodeQualityPreset>(
|
||||
pref: SettingsService.defaultQualityPreset,
|
||||
icon: Symbols.high_quality_rounded,
|
||||
|
||||
@@ -1193,6 +1193,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
_exitFullscreenOnPlayerClose = settingsService.read(SettingsService.exitFullscreenOnPlayerClose);
|
||||
_rewindOnResume = settingsService.read(SettingsService.rewindOnResume);
|
||||
final bufferSizeMB = settingsService.read(SettingsService.bufferSize);
|
||||
final playbackBufferTier = settingsService.read(SettingsService.playbackBufferTier);
|
||||
final enableHardwareDecoding = settingsService.read(SettingsService.enableHardwareDecoding);
|
||||
final debugLoggingEnabled = settingsService.read(SettingsService.enableDebugLogging);
|
||||
final useExoPlayer = settingsService.read(SettingsService.useExoPlayer);
|
||||
@@ -1285,6 +1286,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
if (Platform.isAndroid && useExoPlayer) {
|
||||
final tunneledPlayback = settingsService.read(SettingsService.tunneledPlayback);
|
||||
await currentPlayer.setProperty('tunneled-playback', tunneledPlayback ? 'yes' : 'no');
|
||||
await currentPlayer.setProperty('exo-buffer-tier', playbackBufferTier.nativeValue);
|
||||
}
|
||||
if ((Platform.isAndroid && useExoPlayer) || Platform.isIOS || Platform.isMacOS) {
|
||||
final dvConversionMode = settingsService.read(SettingsService.dvConversionMode);
|
||||
|
||||
@@ -83,6 +83,16 @@ extension DvConversionModePreferenceNativeValue on DvConversionModePreference {
|
||||
};
|
||||
}
|
||||
|
||||
enum PlaybackBufferTier { auto, large, extraLarge }
|
||||
|
||||
extension PlaybackBufferTierNativeValue on PlaybackBufferTier {
|
||||
String get nativeValue => switch (this) {
|
||||
PlaybackBufferTier.auto => 'auto',
|
||||
PlaybackBufferTier.large => 'large',
|
||||
PlaybackBufferTier.extraLarge => 'extra_large',
|
||||
};
|
||||
}
|
||||
|
||||
const String _bufferSizeMigratedKey = 'buffer_size_migrated_to_auto';
|
||||
const String _legacyUseSeasonPosterKey = 'use_season_poster';
|
||||
const String _legacyMpvConfigEntriesKey = 'mpv_config_entries';
|
||||
@@ -504,6 +514,11 @@ class SettingsService extends BaseSharedPreferencesService {
|
||||
static const exitFullscreenOnPlayerClose = BoolPref('exit_fullscreen_on_player_close');
|
||||
|
||||
static const bufferSize = _BufferSizePref();
|
||||
static const playbackBufferTier = EnumPref<PlaybackBufferTier>(
|
||||
'playback_buffer_tier',
|
||||
values: PlaybackBufferTier.values,
|
||||
defaultValue: PlaybackBufferTier.auto,
|
||||
);
|
||||
static const libraryDensity = _LibraryDensityPref();
|
||||
static const automotiveUiScale = _AutomotiveUiScalePref();
|
||||
static const tvCornerSpotlightBackdrop = BoolPref('tv_corner_spotlight_backdrop');
|
||||
@@ -932,6 +947,7 @@ class SettingsService extends BaseSharedPreferencesService {
|
||||
themeMode,
|
||||
videoPlayerNavigationEnabled,
|
||||
bufferSize,
|
||||
playbackBufferTier,
|
||||
libraryDensity,
|
||||
automotiveUiScale,
|
||||
tvCornerSpotlightBackdrop,
|
||||
|
||||
@@ -104,6 +104,8 @@ class _PlayerPerformanceOverlayState extends State<PlayerPerformanceOverlay> {
|
||||
_metric(t.fileInfo.duration, _stats.cacheDurationFormatted),
|
||||
if (isMpv) _metric(t.performanceOverlay.cacheUsed, _stats.cacheUsedFormatted),
|
||||
if (isMpv) _metric(t.performanceOverlay.cacheLimit, _stats.cacheLimitFormatted),
|
||||
if (!isMpv && _stats.hasValidBufferLimits)
|
||||
_metric(t.performanceOverlay.cacheLimit, _stats.bufferLimitsFormatted),
|
||||
if (isMpv) _metric(t.performanceOverlay.speed, _stats.cacheSpeedFormatted),
|
||||
]),
|
||||
_buildSection(Symbols.apps_rounded, t.performanceOverlay.app, [
|
||||
|
||||
@@ -53,6 +53,8 @@ class PerformanceStats {
|
||||
final int? cacheLimit;
|
||||
final double? cacheSpeed;
|
||||
final double? cacheDuration;
|
||||
final int? bufferTargetBytes;
|
||||
final int? bufferMaxMs;
|
||||
|
||||
// DV conversion
|
||||
final bool dvConversionActive;
|
||||
@@ -106,6 +108,8 @@ class PerformanceStats {
|
||||
this.cacheLimit,
|
||||
this.cacheSpeed,
|
||||
this.cacheDuration,
|
||||
this.bufferTargetBytes,
|
||||
this.bufferMaxMs,
|
||||
this.dvConversionActive = false,
|
||||
this.dvConversionMode = '',
|
||||
this.dvConvertedRpus,
|
||||
@@ -157,6 +161,8 @@ class PerformanceStats {
|
||||
cacheLimit = null,
|
||||
cacheSpeed = null,
|
||||
cacheDuration = null,
|
||||
bufferTargetBytes = null,
|
||||
bufferMaxMs = null,
|
||||
dvConversionActive = false,
|
||||
dvConversionMode = '',
|
||||
dvConvertedRpus = null,
|
||||
@@ -183,6 +189,17 @@ class PerformanceStats {
|
||||
return '${mbps.toStringAsFixed(1)} Mbps';
|
||||
}
|
||||
|
||||
/// Both ExoPlayer read-ahead ceilings: the duration target and the hard byte cap.
|
||||
/// The smaller one binds, so showing both explains a duration setting that
|
||||
/// appears to have no effect on high-bitrate media.
|
||||
String get bufferLimitsFormatted {
|
||||
if (bufferMaxMs == null || bufferMaxMs! <= 0) return 'N/A';
|
||||
final duration = '${bufferMaxMs! ~/ 1000}s';
|
||||
if (bufferTargetBytes == null) return duration;
|
||||
final targetBufferMb = bufferTargetBytes! ~/ (1024 * 1024);
|
||||
return '$duration / ${targetBufferMb}MB';
|
||||
}
|
||||
|
||||
/// Format audio bitrate in kbps.
|
||||
String get audioBitrateFormatted {
|
||||
if (audioBitrate == null || audioBitrate == 0) return 'N/A';
|
||||
@@ -377,6 +394,8 @@ class PerformanceStats {
|
||||
return videoBitrate != null && videoBitrate! > 0;
|
||||
}
|
||||
|
||||
bool get hasValidBufferLimits => bufferMaxMs != null && bufferMaxMs! > 0;
|
||||
|
||||
/// Check if audio bitrate is valid (not null, not negative, not zero).
|
||||
bool get hasValidAudioBitrate {
|
||||
return audioBitrate != null && audioBitrate! > 0;
|
||||
|
||||
@@ -211,6 +211,8 @@ class PerformanceStatsService {
|
||||
frameDropCount: statsMap['videoDroppedFrames'] as int?,
|
||||
// Buffer metrics - convert ms to seconds for duration
|
||||
cacheDuration: ((statsMap['totalBufferedDurationMs'] as int?) ?? 0) / 1000.0,
|
||||
bufferTargetBytes: statsMap['bufferTargetBytes'] as int?,
|
||||
bufferMaxMs: statsMap['bufferMaxMs'] as int?,
|
||||
// DV conversion
|
||||
dvConversionActive: statsMap['dvConversionActive'] == true,
|
||||
dvConversionMode: statsMap['dvConversionMode'] as String? ?? '',
|
||||
|
||||
Reference in New Issue
Block a user