feat(subtitles): optionally anchor text subtitles to the screen bottom
Adds an "Anchor to Screen" toggle under Subtitle Styling (Android + ExoPlayer only, default off). When enabled, the text SubtitleView is sized to the full container instead of the letterboxed video rect, so SRT/VTT/mov_text cues render in the black bars below widescreen video and font size and the position setting become relative to the physical screen height. Bitmap (PGS/VOB) and ASS/libass rendering are unchanged; mpv already places plaintext subtitles in the margins by default. close #1730
This commit is contained in:
@@ -215,6 +215,7 @@ class ExoPlayerCore(private val activity: Activity) :
|
||||
@Volatile private var assVideoLatencyFrames = 0
|
||||
private var subtitlePositionPercent: Int = 100
|
||||
private var subtitleFontSize: Float = 55f
|
||||
private var subtitleAnchorToScreen: Boolean = false
|
||||
private var lastSubtitleCues: List<Cue> = emptyList()
|
||||
|
||||
// Tracks whether a text track was selected on the previous onTracksChanged so we
|
||||
@@ -1757,7 +1758,8 @@ class ExoPlayerCore(private val activity: Activity) :
|
||||
videoHeight,
|
||||
pixelRatio,
|
||||
resizeMode,
|
||||
videoZoomScale
|
||||
videoZoomScale,
|
||||
subtitleAnchorToScreen
|
||||
)
|
||||
val bitmapDimensions = SubtitleViewLayout.bitmapDimensions(
|
||||
containerWidth,
|
||||
@@ -3911,7 +3913,8 @@ class ExoPlayerCore(private val activity: Activity) :
|
||||
bgOpacity: Int,
|
||||
subtitlePosition: Int = 100,
|
||||
bold: Boolean = false,
|
||||
italic: Boolean = false
|
||||
italic: Boolean = false,
|
||||
anchorToScreen: Boolean = false
|
||||
) {
|
||||
activity.runOnUiThread {
|
||||
// 1. Non-ASS subtitles: CaptionStyleCompat on SubtitleView
|
||||
@@ -3959,6 +3962,18 @@ class ExoPlayerCore(private val activity: Activity) :
|
||||
subtitlePositionPercent = clampedPosition
|
||||
subtitleFontSize = fontSize
|
||||
|
||||
// Anchor-to-screen (#1730): resize the text SubtitleView to the full
|
||||
// container so default-placed cues land in the letterbox bars.
|
||||
val anchorChanged = subtitleAnchorToScreen != anchorToScreen
|
||||
subtitleAnchorToScreen = anchorToScreen
|
||||
if (anchorChanged) {
|
||||
lastVideoSize?.let { vs ->
|
||||
if (vs.width > 0 && vs.height > 0) {
|
||||
updateSubtitleViewSize(vs.width, vs.height, vs.pixelWidthHeightRatio)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cue-level positioning handles default VTT/SRT placement, whose line
|
||||
// numbers bypass SubtitleView bottom padding. Authored VTT line positions
|
||||
// are preserved in applySubtitlePosition().
|
||||
@@ -3980,7 +3995,7 @@ class ExoPlayerCore(private val activity: Activity) :
|
||||
Log.w(TAG, "Failed to set ASS font scale: ${e.message}")
|
||||
}
|
||||
|
||||
Log.d(TAG, "setSubtitleStyle: fontSize=$fontSize, textColor=$textColor, borderSize=$borderSize, bgOpacity=$bgOpacity, position=$subtitlePosition, bold=$bold, italic=$italic, assScale=$scale")
|
||||
Log.d(TAG, "setSubtitleStyle: fontSize=$fontSize, textColor=$textColor, borderSize=$borderSize, bgOpacity=$bgOpacity, position=$subtitlePosition, bold=$bold, italic=$italic, anchorToScreen=$anchorToScreen, assScale=$scale")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1069,6 +1069,7 @@ class ExoPlayerPlugin :
|
||||
val subtitlePosition = call.argument<Number>("subtitlePosition")?.toInt() ?: 100
|
||||
val bold = call.argument<Boolean>("bold") ?: false
|
||||
val italic = call.argument<Boolean>("italic") ?: false
|
||||
val anchorToScreen = call.argument<Boolean>("anchorToScreen") ?: false
|
||||
|
||||
if (usingMpvFallback) {
|
||||
// MPV fallback handles styling via setProperty, no-op here
|
||||
@@ -1076,7 +1077,7 @@ class ExoPlayerPlugin :
|
||||
return
|
||||
}
|
||||
|
||||
playerCore?.setSubtitleStyle(fontSize, textColor, borderSize, borderColor, bgColor, bgOpacity, subtitlePosition, bold, italic)
|
||||
playerCore?.setSubtitleStyle(fontSize, textColor, borderSize, borderColor, bgColor, bgOpacity, subtitlePosition, bold, italic, anchorToScreen)
|
||||
result.success(null)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,12 +13,18 @@ internal object SubtitleViewLayout {
|
||||
videoHeight: Int,
|
||||
pixelRatio: Float,
|
||||
resizeMode: Int,
|
||||
zoomScale: Float
|
||||
zoomScale: Float,
|
||||
anchorToScreen: Boolean = false
|
||||
): SubtitleViewDimensions? {
|
||||
val videoAspect = videoAspect(videoWidth, videoHeight, pixelRatio) ?: return null
|
||||
if (containerWidth <= 0 || containerHeight <= 0) return null
|
||||
|
||||
if (resizeMode != AspectRatioFrameLayout.RESIZE_MODE_FIT) {
|
||||
// Anchor-to-screen (#1730): size the text view to the full container so
|
||||
// media3's fractional text size and bottom-anchored cue placement compute
|
||||
// against the physical screen, letting subtitles render in the letterbox
|
||||
// bars instead of inside the video rect. Same geometry the non-FIT modes
|
||||
// below already use.
|
||||
if (anchorToScreen || resizeMode != AspectRatioFrameLayout.RESIZE_MODE_FIT) {
|
||||
return SubtitleViewDimensions(containerWidth, containerHeight)
|
||||
}
|
||||
|
||||
|
||||
@@ -45,9 +45,49 @@ class SubtitleViewLayoutTest {
|
||||
assertAspectCloseTo16By9(bitmap!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun anchorToScreenSizesTextToContainerAndKeepsBitmapOnVideoRect() {
|
||||
val text = textDimensions(resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT, anchorToScreen = true)
|
||||
val bitmap = bitmapDimensions(resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT)
|
||||
|
||||
assertEquals(SubtitleViewDimensions(2424, 1080), text)
|
||||
assertEquals(SubtitleViewDimensions(1920, 1080), bitmap)
|
||||
assertAspectCloseTo16By9(bitmap!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun anchorToScreenReachesBelowLetterboxedWideVideo() {
|
||||
// 2.37:1 video on a 16:9 screen — the #1730 use case. Without the anchor
|
||||
// the text view stops at the bottom letterbox bar; with it the view spans
|
||||
// the physical screen height.
|
||||
val unanchored = SubtitleViewLayout.textDimensions(
|
||||
containerWidth = 1920,
|
||||
containerHeight = 1080,
|
||||
videoWidth = 2560,
|
||||
videoHeight = 1080,
|
||||
pixelRatio = 1f,
|
||||
resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT,
|
||||
zoomScale = 1f
|
||||
)
|
||||
val anchored = SubtitleViewLayout.textDimensions(
|
||||
containerWidth = 1920,
|
||||
containerHeight = 1080,
|
||||
videoWidth = 2560,
|
||||
videoHeight = 1080,
|
||||
pixelRatio = 1f,
|
||||
resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT,
|
||||
zoomScale = 1f,
|
||||
anchorToScreen = true
|
||||
)
|
||||
|
||||
assertEquals(SubtitleViewDimensions(1920, 810), unanchored)
|
||||
assertEquals(SubtitleViewDimensions(1920, 1080), anchored)
|
||||
}
|
||||
|
||||
private fun textDimensions(
|
||||
resizeMode: Int,
|
||||
zoomScale: Float = 1f
|
||||
zoomScale: Float = 1f,
|
||||
anchorToScreen: Boolean = false
|
||||
): SubtitleViewDimensions? = SubtitleViewLayout.textDimensions(
|
||||
containerWidth = 2424,
|
||||
containerHeight = 1080,
|
||||
@@ -55,7 +95,8 @@ class SubtitleViewLayoutTest {
|
||||
videoHeight = 1080,
|
||||
pixelRatio = 1f,
|
||||
resizeMode = resizeMode,
|
||||
zoomScale = zoomScale
|
||||
zoomScale = zoomScale,
|
||||
anchorToScreen = anchorToScreen
|
||||
)
|
||||
|
||||
private fun bitmapDimensions(
|
||||
|
||||
@@ -744,6 +744,8 @@
|
||||
"overrideStrip": "Formatlaşdırmanı sil",
|
||||
"positionTop": "Yuxarı",
|
||||
"positionBottom": "Aşağı",
|
||||
"anchorToScreen": "",
|
||||
"anchorToScreenDescription": "",
|
||||
"bold": "Qalın",
|
||||
"italic": "Kursiv",
|
||||
"renderResolution": "Emal imkanı (Resolution)",
|
||||
|
||||
@@ -744,6 +744,8 @@
|
||||
"overrideStrip": "Премахване на стиловете",
|
||||
"positionTop": "Горе",
|
||||
"positionBottom": "Долу",
|
||||
"anchorToScreen": "",
|
||||
"anchorToScreenDescription": "",
|
||||
"bold": "Получер",
|
||||
"italic": "Курсив",
|
||||
"renderResolution": "Резолюция на изобразяване",
|
||||
|
||||
@@ -744,6 +744,8 @@
|
||||
"overrideStrip": "Fjern formatering",
|
||||
"positionTop": "Øverst",
|
||||
"positionBottom": "Nederst",
|
||||
"anchorToScreen": "",
|
||||
"anchorToScreenDescription": "",
|
||||
"bold": "Fed",
|
||||
"italic": "Kursiv",
|
||||
"renderResolution": "Gengivelsesopløsning",
|
||||
|
||||
@@ -744,6 +744,8 @@
|
||||
"overrideStrip": "Formatierung entfernen",
|
||||
"positionTop": "Oben",
|
||||
"positionBottom": "Unten",
|
||||
"anchorToScreen": "",
|
||||
"anchorToScreenDescription": "",
|
||||
"bold": "Fett",
|
||||
"italic": "Kursiv",
|
||||
"renderResolution": "Render-Auflösung",
|
||||
|
||||
@@ -744,6 +744,8 @@
|
||||
"overrideStrip": "Remove styling",
|
||||
"positionTop": "Top",
|
||||
"positionBottom": "Bottom",
|
||||
"anchorToScreen": "Anchor to Screen",
|
||||
"anchorToScreenDescription": "Show text subtitles in the black bars below widescreen video",
|
||||
"bold": "Bold",
|
||||
"italic": "Italic",
|
||||
"renderResolution": "Render Resolution",
|
||||
|
||||
@@ -744,6 +744,8 @@
|
||||
"overrideStrip": "Quitar estilos",
|
||||
"positionTop": "Arriba",
|
||||
"positionBottom": "Abajo",
|
||||
"anchorToScreen": "",
|
||||
"anchorToScreenDescription": "",
|
||||
"bold": "Negrita",
|
||||
"italic": "Cursiva",
|
||||
"renderResolution": "Resolución de renderizado",
|
||||
|
||||
@@ -744,6 +744,8 @@
|
||||
"overrideStrip": "Supprimer le style",
|
||||
"positionTop": "Haut",
|
||||
"positionBottom": "Bas",
|
||||
"anchorToScreen": "",
|
||||
"anchorToScreenDescription": "",
|
||||
"bold": "Gras",
|
||||
"italic": "Italique",
|
||||
"renderResolution": "Résolution de rendu",
|
||||
|
||||
@@ -744,6 +744,8 @@
|
||||
"overrideStrip": "Stílus eltávolítása",
|
||||
"positionTop": "Fent",
|
||||
"positionBottom": "Lent",
|
||||
"anchorToScreen": "",
|
||||
"anchorToScreenDescription": "",
|
||||
"bold": "Félkövér",
|
||||
"italic": "Dőlt",
|
||||
"renderResolution": "Renderelési felbontás",
|
||||
|
||||
@@ -744,6 +744,8 @@
|
||||
"overrideStrip": "Rimuovi stile",
|
||||
"positionTop": "In alto",
|
||||
"positionBottom": "In basso",
|
||||
"anchorToScreen": "",
|
||||
"anchorToScreenDescription": "",
|
||||
"bold": "Grassetto",
|
||||
"italic": "Corsivo",
|
||||
"renderResolution": "Risoluzione di rendering",
|
||||
|
||||
@@ -740,6 +740,8 @@
|
||||
"overrideStrip": "スタイルを削除",
|
||||
"positionTop": "上",
|
||||
"positionBottom": "下",
|
||||
"anchorToScreen": "",
|
||||
"anchorToScreenDescription": "",
|
||||
"bold": "太字",
|
||||
"italic": "斜体",
|
||||
"renderResolution": "レンダリング解像度",
|
||||
|
||||
@@ -744,6 +744,8 @@
|
||||
"overrideStrip": "Форматтауды жою",
|
||||
"positionTop": "Жоғары",
|
||||
"positionBottom": "Төмен",
|
||||
"anchorToScreen": "",
|
||||
"anchorToScreenDescription": "",
|
||||
"bold": "Қалың",
|
||||
"italic": "Көлбеу",
|
||||
"renderResolution": "Рендеринг ажыратымдылығы",
|
||||
|
||||
@@ -740,6 +740,8 @@
|
||||
"overrideStrip": "스타일 제거",
|
||||
"positionTop": "위",
|
||||
"positionBottom": "아래",
|
||||
"anchorToScreen": "",
|
||||
"anchorToScreenDescription": "",
|
||||
"bold": "굵게",
|
||||
"italic": "기울임꼴",
|
||||
"renderResolution": "렌더링 해상도",
|
||||
|
||||
@@ -744,6 +744,8 @@
|
||||
"overrideStrip": "Fjern formatering",
|
||||
"positionTop": "Øverst",
|
||||
"positionBottom": "Nederst",
|
||||
"anchorToScreen": "",
|
||||
"anchorToScreenDescription": "",
|
||||
"bold": "Fet",
|
||||
"italic": "Kursiv",
|
||||
"renderResolution": "Gjengivelsesoppløsning",
|
||||
|
||||
@@ -744,6 +744,8 @@
|
||||
"overrideStrip": "Opmaak verwijderen",
|
||||
"positionTop": "Bovenaan",
|
||||
"positionBottom": "Onderaan",
|
||||
"anchorToScreen": "",
|
||||
"anchorToScreenDescription": "",
|
||||
"bold": "Vet",
|
||||
"italic": "Cursief",
|
||||
"renderResolution": "Renderresolutie",
|
||||
|
||||
@@ -752,6 +752,8 @@
|
||||
"overrideStrip": "Usuń style",
|
||||
"positionTop": "Góra",
|
||||
"positionBottom": "Dół",
|
||||
"anchorToScreen": "",
|
||||
"anchorToScreenDescription": "",
|
||||
"bold": "Pogrubienie",
|
||||
"italic": "Kursywa",
|
||||
"renderResolution": "Rozdzielczość renderowania",
|
||||
|
||||
@@ -744,6 +744,8 @@
|
||||
"overrideStrip": "Remover estilo",
|
||||
"positionTop": "Superior",
|
||||
"positionBottom": "Inferior",
|
||||
"anchorToScreen": "",
|
||||
"anchorToScreenDescription": "",
|
||||
"bold": "Negrito",
|
||||
"italic": "Itálico",
|
||||
"renderResolution": "Resolução de renderização",
|
||||
|
||||
@@ -752,6 +752,8 @@
|
||||
"overrideStrip": "Удалить стили",
|
||||
"positionTop": "Сверху",
|
||||
"positionBottom": "Снизу",
|
||||
"anchorToScreen": "",
|
||||
"anchorToScreenDescription": "",
|
||||
"bold": "Жирный",
|
||||
"italic": "Курсив",
|
||||
"renderResolution": "Разрешение отрисовки",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
/// To regenerate, run: `dart run slang`
|
||||
///
|
||||
/// Locales: 22
|
||||
/// Strings: 38352 (1743 per locale)
|
||||
/// Strings: 38354 (1743 per locale)
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint, unused_import
|
||||
|
||||
@@ -2212,6 +2212,12 @@ class Translations$subtitlingStyling$en {
|
||||
/// en: 'Bottom'
|
||||
String get positionBottom => 'Bottom';
|
||||
|
||||
/// en: 'Anchor to Screen'
|
||||
String get anchorToScreen => 'Anchor to Screen';
|
||||
|
||||
/// en: 'Show text subtitles in the black bars below widescreen video'
|
||||
String get anchorToScreenDescription => 'Show text subtitles in the black bars below widescreen video';
|
||||
|
||||
/// en: 'Bold'
|
||||
String get bold => 'Bold';
|
||||
|
||||
@@ -6762,6 +6768,8 @@ extension on Translations {
|
||||
'subtitlingStyling.overrideStrip' => 'Remove styling',
|
||||
'subtitlingStyling.positionTop' => 'Top',
|
||||
'subtitlingStyling.positionBottom' => 'Bottom',
|
||||
'subtitlingStyling.anchorToScreen' => 'Anchor to Screen',
|
||||
'subtitlingStyling.anchorToScreenDescription' => 'Show text subtitles in the black bars below widescreen video',
|
||||
'subtitlingStyling.bold' => 'Bold',
|
||||
'subtitlingStyling.italic' => 'Italic',
|
||||
'subtitlingStyling.renderResolution' => 'Render Resolution',
|
||||
@@ -7091,10 +7099,10 @@ extension on Translations {
|
||||
'explore.stats.favorited' => ({required Object n}) => '${n} favorites',
|
||||
'explore.stats.dropRate' => ({required Object percent}) => '${percent} dropped it',
|
||||
'explore.stats.comments' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('en'))(n, one: '${n} comment', other: '${n} comments', ),
|
||||
'explore.stats.votes' => ({required Object n}) => '${n} votes',
|
||||
'explore.stats.watching' => ({required Object n}) => '${n} watching it',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'explore.stats.votes' => ({required Object n}) => '${n} votes',
|
||||
'explore.stats.watching' => ({required Object n}) => '${n} watching it',
|
||||
'explore.stats.completed' => ({required Object n}) => '${n} completed',
|
||||
'explore.stats.onHold' => ({required Object n}) => '${n} on hold',
|
||||
'explore.stats.dropped' => ({required Object n}) => '${n} dropped',
|
||||
@@ -7605,10 +7613,10 @@ extension on Translations {
|
||||
'externalPlayer.useExternalPlayerDescription' => 'Open videos in another app',
|
||||
'externalPlayer.selectPlayer' => 'Select Player',
|
||||
'externalPlayer.customPlayers' => 'Custom Players',
|
||||
'externalPlayer.systemDefault' => 'System Default',
|
||||
'externalPlayer.addCustomPlayer' => 'Add Custom Player',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'externalPlayer.systemDefault' => 'System Default',
|
||||
'externalPlayer.addCustomPlayer' => 'Add Custom Player',
|
||||
'externalPlayer.playerName' => 'Player Name',
|
||||
'externalPlayer.playerNameHint' => 'My Player',
|
||||
'externalPlayer.playerCommand' => 'Command',
|
||||
|
||||
@@ -744,6 +744,8 @@
|
||||
"overrideStrip": "Ta bort formatering",
|
||||
"positionTop": "Överst",
|
||||
"positionBottom": "Nederst",
|
||||
"anchorToScreen": "",
|
||||
"anchorToScreenDescription": "",
|
||||
"bold": "Fet",
|
||||
"italic": "Kursiv",
|
||||
"renderResolution": "Renderingsupplösning",
|
||||
|
||||
@@ -744,6 +744,8 @@
|
||||
"overrideStrip": "Biçimlendirmeyi kaldır",
|
||||
"positionTop": "Üst",
|
||||
"positionBottom": "Alt",
|
||||
"anchorToScreen": "",
|
||||
"anchorToScreenDescription": "",
|
||||
"bold": "Kalın",
|
||||
"italic": "İtalik",
|
||||
"renderResolution": "İşleme Çözünürlüğü",
|
||||
|
||||
@@ -744,6 +744,8 @@
|
||||
"overrideStrip": "Formatlashni olib tashlash",
|
||||
"positionTop": "Yuqori",
|
||||
"positionBottom": "Pastki",
|
||||
"anchorToScreen": "",
|
||||
"anchorToScreenDescription": "",
|
||||
"bold": "Qalin",
|
||||
"italic": "Qiya",
|
||||
"renderResolution": "Renderlash oʻlchamlari",
|
||||
|
||||
@@ -740,6 +740,8 @@
|
||||
"overrideStrip": "移除樣式",
|
||||
"positionTop": "頂部",
|
||||
"positionBottom": "底部",
|
||||
"anchorToScreen": "",
|
||||
"anchorToScreenDescription": "",
|
||||
"bold": "粗體",
|
||||
"italic": "斜體",
|
||||
"renderResolution": "渲染解析度",
|
||||
|
||||
@@ -740,6 +740,8 @@
|
||||
"overrideStrip": "移除样式",
|
||||
"positionTop": "顶部",
|
||||
"positionBottom": "底部",
|
||||
"anchorToScreen": "",
|
||||
"anchorToScreenDescription": "",
|
||||
"bold": "粗体",
|
||||
"italic": "斜体",
|
||||
"renderResolution": "渲染分辨率",
|
||||
|
||||
@@ -542,6 +542,7 @@ class PlayerAndroid extends PlayerBase {
|
||||
int subtitlePosition = 100,
|
||||
bool bold = false,
|
||||
bool italic = false,
|
||||
bool anchorToScreen = false,
|
||||
}) async {
|
||||
if (disposed || !initialized) return;
|
||||
await invoke('setSubtitleStyle', {
|
||||
@@ -554,6 +555,7 @@ class PlayerAndroid extends PlayerBase {
|
||||
'subtitlePosition': subtitlePosition,
|
||||
'bold': bold,
|
||||
'italic': italic,
|
||||
'anchorToScreen': anchorToScreen,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -314,6 +314,7 @@ abstract class Player {
|
||||
int subtitlePosition = 100,
|
||||
bool bold = false,
|
||||
bool italic = false,
|
||||
bool anchorToScreen = false,
|
||||
});
|
||||
|
||||
/// Apply the box-fit mode to the native video layer
|
||||
|
||||
@@ -1097,6 +1097,7 @@ abstract class PlayerBase with PlayerStreamControllersMixin implements Player {
|
||||
int subtitlePosition = 100,
|
||||
bool bold = false,
|
||||
bool italic = false,
|
||||
bool anchorToScreen = false,
|
||||
}) async {}
|
||||
|
||||
@override
|
||||
|
||||
@@ -42,6 +42,9 @@ class SubtitleStylingScreen extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Anchor-to-screen is an ExoPlayer text-subtitle knob; mpv already places
|
||||
// plaintext subtitles in the letterbox margins by default.
|
||||
final exoActive = Platform.isAndroid && SettingsService.instance.read(SettingsService.useExoPlayer);
|
||||
return SettingsPage(
|
||||
title: Text(t.screens.subtitleStyling),
|
||||
children: [
|
||||
@@ -108,6 +111,13 @@ class SubtitleStylingScreen extends StatelessWidget {
|
||||
min: 0,
|
||||
max: 100,
|
||||
),
|
||||
if (exoActive)
|
||||
SettingSwitchTile(
|
||||
pref: SettingsService.subtitleAnchorToScreen,
|
||||
icon: Symbols.fit_screen_rounded,
|
||||
title: t.subtitlingStyling.anchorToScreen,
|
||||
subtitle: t.subtitlingStyling.anchorToScreenDescription,
|
||||
),
|
||||
SettingSwitchTile(
|
||||
pref: SettingsService.subtitleBold,
|
||||
icon: Symbols.format_bold_rounded,
|
||||
|
||||
@@ -432,6 +432,7 @@ extension _VideoPlayerOpenMethods on VideoPlayerScreenState {
|
||||
subtitlePosition: settingsService.read(SettingsService.subtitlePosition),
|
||||
bold: settingsService.read(SettingsService.subtitleBold),
|
||||
italic: settingsService.read(SettingsService.subtitleItalic),
|
||||
anchorToScreen: settingsService.read(SettingsService.subtitleAnchorToScreen),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -377,6 +377,12 @@ class SettingsService extends BaseSharedPreferencesService {
|
||||
);
|
||||
static const subtitleBold = BoolPref('subtitle_bold');
|
||||
static const subtitleItalic = BoolPref('subtitle_italic');
|
||||
|
||||
/// Render text subtitles (SRT/VTT/mov_text) anchored to the physical screen
|
||||
/// instead of the video rect, so they land in the letterbox bars of
|
||||
/// widescreen video (#1730). ExoPlayer backend only; mpv already places
|
||||
/// plaintext subtitles in the margins by default (sub-use-margins=yes).
|
||||
static const subtitleAnchorToScreen = BoolPref('subtitle_anchor_to_screen');
|
||||
static const cleanedOldImageCache = BoolPref('cleaned_old_image_cache');
|
||||
static const rememberTrackSelections = BoolPref('remember_track_selections', defaultValue: true);
|
||||
|
||||
@@ -935,6 +941,7 @@ class SettingsService extends BaseSharedPreferencesService {
|
||||
maxVolume,
|
||||
downmixCenterBoost,
|
||||
subtitlePosition,
|
||||
subtitleAnchorToScreen,
|
||||
defaultPlaybackSpeed,
|
||||
defaultBoxFitMode,
|
||||
themeMode,
|
||||
|
||||
@@ -368,6 +368,7 @@ class FakePlayer implements Player {
|
||||
int subtitlePosition = 100,
|
||||
bool bold = false,
|
||||
bool italic = false,
|
||||
bool anchorToScreen = false,
|
||||
}) async {}
|
||||
|
||||
@override
|
||||
|
||||
Reference in New Issue
Block a user