Compare commits

...

2 Commits

Author SHA1 Message Date
Lance Chant
9c0de94247 fix: text ui scaling
Made text UI scaling follow OS level scailing to a limit to stop
overlapping issues

Signed-off-by: Lance Chant <13349722+lancechant@users.noreply.github.com>
2026-01-19 14:59:11 +02:00
Fredrik Burmester
358e00d8b7 fix(player): resolve tvOS freeze on player exit by reordering mpv options
Some checks are pending
🏗️ Build Apps / 🤖 Build Android APK (Phone) (push) Waiting to run
🏗️ Build Apps / 🤖 Build Android APK (TV) (push) Waiting to run
🏗️ Build Apps / 🍎 Build iOS IPA (Phone) (push) Waiting to run
🔒 Lockfile Consistency Check / 🔍 Check bun.lock and package.json consistency (push) Waiting to run
🛡️ CodeQL Analysis / 🔎 Analyze with CodeQL (actions) (push) Waiting to run
🛡️ CodeQL Analysis / 🔎 Analyze with CodeQL (javascript-typescript) (push) Waiting to run
🏷️🔀Merge Conflict Labeler / 🏷️ Labeling Merge Conflicts (push) Waiting to run
🚦 Security & Quality Gate / 📝 Validate PR Title (push) Waiting to run
🚦 Security & Quality Gate / 🔍 Vulnerable Dependencies (push) Waiting to run
🚦 Security & Quality Gate / 🚑 Expo Doctor Check (push) Waiting to run
🚦 Security & Quality Gate / 🔍 Lint & Test (check) (push) Waiting to run
🚦 Security & Quality Gate / 🔍 Lint & Test (format) (push) Waiting to run
🚦 Security & Quality Gate / 🔍 Lint & Test (lint) (push) Waiting to run
🚦 Security & Quality Gate / 🔍 Lint & Test (typecheck) (push) Waiting to run
🏗️ Build Apps / 🍎 Build iOS IPA (Phone - Unsigned) (push) Waiting to run
2026-01-19 08:41:52 +01:00
15 changed files with 75 additions and 31 deletions

View File

@@ -144,7 +144,10 @@ export const Button: React.FC<PropsWithChildren<ButtonProps>> = ({
${colorClasses}
${className}`}
>
<Text className={`${textColorClass} text-xl font-bold`}>
<Text
className={`${textColorClass} text-xl font-bold`}
ellipsizeMode='tail'
>
{children}
</Text>
</View>
@@ -186,6 +189,7 @@ export const Button: React.FC<PropsWithChildren<ButtonProps>> = ({
${iconRight ? "mr-2" : ""}
${iconLeft ? "ml-2" : ""}
`}
ellipsizeMode='tail'
>
{children}
</Text>

View File

@@ -17,7 +17,7 @@ const JellyfinServerDiscovery: React.FC<Props> = ({ onServerSelect }) => {
return (
<View className='mt-2'>
<Button onPress={startDiscovery} color='black'>
<Text className='text-white text-center'>
<Text maxFontSizeMultiplier={1.2} className='text-white text-center'>
{isSearching
? t("server.searching")
: t("server.search_for_local_servers")}

View File

@@ -532,18 +532,24 @@ export const PlayButton: React.FC<Props> = ({
className='flex flex-row items-center justify-center bg-transparent rounded-full z-20 h-12 w-full '
>
<View className='flex flex-row items-center space-x-2'>
<Animated.Text style={[animatedTextStyle, { fontWeight: "bold" }]}>
<Animated.Text
style={[animatedTextStyle, { fontWeight: "bold" }]}
maxFontSizeMultiplier={1.2}
>
{runtimeTicksToMinutes(
(item?.RunTimeTicks || 0) -
(item?.UserData?.PlaybackPositionTicks || 0),
)}
{(item?.UserData?.PlaybackPositionTicks || 0) > 0 && " left"}
</Animated.Text>
<Animated.Text style={animatedTextStyle}>
<Animated.Text style={animatedTextStyle} maxFontSizeMultiplier={1.2}>
<Ionicons name='play-circle' size={24} />
</Animated.Text>
{client && (
<Animated.Text style={animatedTextStyle}>
<Animated.Text
style={animatedTextStyle}
maxFontSizeMultiplier={1.2}
>
<Feather name='cast' size={22} />
<CastButton tintColor='transparent' />
</Animated.Text>

View File

@@ -198,10 +198,13 @@ export const PlayButton: React.FC<Props> = ({
className='flex flex-row items-center justify-center bg-transparent rounded-xl z-20 h-12 w-full '
>
<View className='flex flex-row items-center space-x-2'>
<Animated.Text style={[animatedTextStyle, { fontWeight: "bold" }]}>
<Animated.Text
style={[animatedTextStyle, { fontWeight: "bold" }]}
maxFontSizeMultiplier={1.2}
>
{runtimeTicksToMinutes(item?.RunTimeTicks)}
</Animated.Text>
<Animated.Text style={animatedTextStyle}>
<Animated.Text style={animatedTextStyle} maxFontSizeMultiplier={1.2}>
<Ionicons name='play-circle' size={24} />
</Animated.Text>
</View>

View File

@@ -13,6 +13,7 @@ export function ThemedText({
}: ThemedTextProps) {
return (
<Text
maxFontSizeMultiplier={1.3}
style={[
{ color: "white" },
type === "default" ? styles.default : undefined,

View File

@@ -736,6 +736,7 @@ export const AppleTVCarousel: React.FC<AppleTVCarouselProps> = ({
>
<TouchableOpacity onPress={() => navigateToItem(item)}>
<Animated.Text
maxFontSizeMultiplier={1.2}
style={{
color: `rgba(255, 255, 255, ${TEXT_OPACITY})`,
fontSize: GENRES_FONT_SIZE,
@@ -801,6 +802,7 @@ export const AppleTVCarousel: React.FC<AppleTVCarouselProps> = ({
>
<TouchableOpacity onPress={() => navigateToItem(item)}>
<Animated.Text
maxFontSizeMultiplier={1.2}
numberOfLines={OVERVIEW_MAX_LINES}
style={{
color: `rgba(255, 255, 255, ${TEXT_OPACITY * 0.85})`,

View File

@@ -27,7 +27,7 @@ export function Input(props: InputProps) {
${isFocused ? "bg-neutral-700 border-2 border-white" : "bg-neutral-900 border-2 border-transparent"}
text-white ${extraClassName}
`}
allowFontScaling={false}
maxFontSizeMultiplier={1.2}
style={[
style,
{
@@ -45,7 +45,7 @@ export function Input(props: InputProps) {
<TextInput
ref={inputRef}
className='p-4 rounded-xl bg-neutral-900'
allowFontScaling={false}
maxFontSizeMultiplier={1.2}
style={[{ color: "white" }, style]}
placeholderTextColor={"#9CA3AF"}
clearButtonMode='while-editing'

View File

@@ -19,7 +19,9 @@ export const SectionHeader: React.FC<Props> = ({
return (
<View className='px-4 flex flex-row items-center justify-between mb-2'>
<Text className='text-lg font-bold text-neutral-100'>{title}</Text>
<Text className='text-lg font-bold text-neutral-100' ellipsizeMode='tail'>
{title}
</Text>
{shouldShowAction && (
<TouchableOpacity
onPress={onPressAction}
@@ -29,6 +31,7 @@ export const SectionHeader: React.FC<Props> = ({
className='py-1 pl-3'
>
<Text
ellipsizeMode='tail'
style={{
color: actionDisabled ? "rgba(255,255,255,0.4)" : Colors.primary,
}}

View File

@@ -4,7 +4,7 @@ export function Text(props: TextProps) {
if (Platform.isTV)
return (
<RNText
allowFontScaling={false}
maxFontSizeMultiplier={1.3}
style={[{ color: "white" }, style]}
{...otherProps}
/>
@@ -12,7 +12,7 @@ export function Text(props: TextProps) {
return (
<RNText
allowFontScaling={false}
maxFontSizeMultiplier={1.3}
style={[{ color: "white" }, style]}
{...otherProps}
/>

View File

@@ -175,10 +175,16 @@ export const Favorites = () => {
contentFit='contain'
source={heart}
/>
<Text className='text-xl font-semibold text-white mb-2'>
<Text
maxFontSizeMultiplier={1.2}
className='text-xl font-semibold text-white mb-2'
>
{t("favorites.noDataTitle")}
</Text>
<Text className='text-base text-white/70 text-center max-w-xs px-4'>
<Text
maxFontSizeMultiplier={1.2}
className='text-base text-white/70 text-center max-w-xs px-4'
>
{t("favorites.noData")}
</Text>
</View>

View File

@@ -11,12 +11,12 @@ export const KefinTweaksSettings = () => {
return (
<View className=''>
<View className='flex flex-col rounded-xl overflow-hidden p-4 bg-neutral-900'>
<Text className='text-xs text-red-600 mb-2'>
<Text maxFontSizeMultiplier={1.2} className='text-xs text-red-600 mb-2'>
{t("home.settings.plugins.kefinTweaks.watchlist_enabler")}
</Text>
<View className='flex flex-row items-center justify-between mt-2'>
<Text className='text-white'>
<Text maxFontSizeMultiplier={1.2} className='text-white'>
{isEnabled ? t("Watchlist On") : t("Watchlist Off")}
</Text>

View File

@@ -19,7 +19,9 @@ const SkipButton: React.FC<SkipButtonProps> = ({
onPress={onPress}
className='bg-black/60 rounded-md px-3 py-3 border border-neutral-900'
>
<Text className='text-white font-bold'>{buttonText}</Text>
<Text maxFontSizeMultiplier={1.2} className='text-white font-bold'>
{buttonText}
</Text>
</TouchableOpacity>
</View>
);

View File

@@ -102,6 +102,7 @@ const SliderScrubber: React.FC<SliderScrubberProps> = ({
contentFit='cover'
/>
<Text
maxFontSizeMultiplier={1.2}
style={{
position: "absolute",
bottom: 5,
@@ -126,10 +127,16 @@ const SliderScrubber: React.FC<SliderScrubberProps> = ({
maximumValue={max}
/>
<View className='flex flex-row items-center justify-between mt-0.5'>
<Text className='text-[12px] text-neutral-400'>
<Text
maxFontSizeMultiplier={1.2}
className='text-[12px] text-neutral-400'
>
{formatTimeString(currentTime, "ms")}
</Text>
<Text className='text-[12px] text-neutral-400'>
<Text
maxFontSizeMultiplier={1.2}
className='text-[12px] text-neutral-400'
>
-{formatTimeString(remainingTime, "ms")}
</Text>
</View>

View File

@@ -195,6 +195,7 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
<View style={styles.infoBox}>
{playMethod && (
<Text
maxFontSizeMultiplier={1.2}
style={[
styles.infoText,
{ color: getPlayMethodColor(playMethod) },
@@ -204,28 +205,31 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
</Text>
)}
{transcodeReasons && transcodeReasons.length > 0 && (
<Text style={[styles.infoText, styles.reasonText]}>
<Text
maxFontSizeMultiplier={1.2}
style={[styles.infoText, styles.reasonText]}
>
{transcodeReasons.map(formatTranscodeReason).join(", ")}
</Text>
)}
{info?.videoWidth && info?.videoHeight && (
<Text style={styles.infoText}>
<Text maxFontSizeMultiplier={1.2} style={styles.infoText}>
{info.videoWidth}x{info.videoHeight}
</Text>
)}
{info?.videoCodec && (
<Text style={styles.infoText}>
<Text maxFontSizeMultiplier={1.2} style={styles.infoText}>
Video: {formatCodec(info.videoCodec)}
{info.fps ? ` @ ${formatFps(info.fps)} fps` : ""}
</Text>
)}
{info?.audioCodec && (
<Text style={styles.infoText}>
<Text maxFontSizeMultiplier={1.2} style={styles.infoText}>
Audio: {formatCodec(info.audioCodec)}
</Text>
)}
{(info?.videoBitrate || info?.audioBitrate) && (
<Text style={styles.infoText}>
<Text maxFontSizeMultiplier={1.2} style={styles.infoText}>
Bitrate:{" "}
{info.videoBitrate
? formatBitrate(info.videoBitrate)
@@ -235,17 +239,22 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
</Text>
)}
{info?.cacheSeconds !== undefined && (
<Text style={styles.infoText}>
<Text maxFontSizeMultiplier={1.2} style={styles.infoText}>
Buffer: {info.cacheSeconds.toFixed(1)}s
</Text>
)}
{info?.droppedFrames !== undefined && info.droppedFrames > 0 && (
<Text style={[styles.infoText, styles.warningText]}>
<Text
maxFontSizeMultiplier={1.2}
style={[styles.infoText, styles.warningText]}
>
Dropped: {info.droppedFrames} frames
</Text>
)}
{!info && !playMethod && (
<Text style={styles.infoText}>Loading...</Text>
<Text maxFontSizeMultiplier={1.2} style={styles.infoText}>
Loading...
</Text>
)}
</View>
</Animated.View>

View File

@@ -167,16 +167,17 @@ final class MPVLayerRenderer {
// Use AVFoundation video output - required for PiP support
checkError(mpv_set_option_string(handle, "vo", "avfoundation"))
// Enable composite OSD mode - renders subtitles directly onto video frames using GPU
// This is better for PiP as subtitles are baked into the video
// NOTE: Must be set BEFORE the #if targetEnvironment check or tvOS will freeze on player exit
checkError(mpv_set_option_string(handle, "avfoundation-composite-osd", "yes"))
// Hardware decoding with VideoToolbox
// On simulator, use software decoding since VideoToolbox is not available
// On device, use VideoToolbox with software fallback enabled
#if targetEnvironment(simulator)
checkError(mpv_set_option_string(handle, "hwdec", "no"))
#else
// Only enable composite OSD mode on real device (OSD is not supported in simulator).
// This renders subtitles directly onto video frames using the GPU, which is better for PiP since subtitles are baked into the video.
checkError(mpv_set_option_string(handle, "avfoundation-composite-osd", "yes"))
checkError(mpv_set_option_string(handle, "hwdec", "videotoolbox"))
#endif
checkError(mpv_set_option_string(handle, "hwdec-codecs", "all"))