mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-01-19 17:48:07 +00:00
Compare commits
1 Commits
fix/text-u
...
renovate/r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ffd76e3cf |
@@ -449,7 +449,7 @@ export default function page() {
|
||||
async (data: { nativeEvent: MpvOnProgressEventPayload }) => {
|
||||
if (isSeeking.get() || isPlaybackStopped) return;
|
||||
|
||||
const { position, cacheSeconds } = data.nativeEvent;
|
||||
const { position } = data.nativeEvent;
|
||||
// MPV reports position in seconds, convert to ms
|
||||
const currentTime = position * 1000;
|
||||
|
||||
@@ -459,12 +459,6 @@ export default function page() {
|
||||
|
||||
progress.set(currentTime);
|
||||
|
||||
// Update cache progress (current position + buffered seconds ahead)
|
||||
if (cacheSeconds !== undefined && cacheSeconds > 0) {
|
||||
const cacheEnd = currentTime + cacheSeconds * 1000;
|
||||
cacheProgress.set(cacheEnd);
|
||||
}
|
||||
|
||||
// Update URL immediately after seeking, or every 30 seconds during normal playback
|
||||
const now = Date.now();
|
||||
const shouldUpdateUrl = wasJustSeeking.get();
|
||||
|
||||
4
bun.lock
4
bun.lock
@@ -79,7 +79,7 @@
|
||||
"react-native-reanimated": "~4.1.1",
|
||||
"react-native-reanimated-carousel": "4.0.3",
|
||||
"react-native-safe-area-context": "~5.6.0",
|
||||
"react-native-screens": "~4.18.0",
|
||||
"react-native-screens": "~4.19.0",
|
||||
"react-native-svg": "15.12.1",
|
||||
"react-native-text-ticker": "^1.15.0",
|
||||
"react-native-track-player": "github:lovegaoshi/react-native-track-player#APM",
|
||||
@@ -1688,7 +1688,7 @@
|
||||
|
||||
"react-native-safe-area-context": ["react-native-safe-area-context@5.6.2", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg=="],
|
||||
|
||||
"react-native-screens": ["react-native-screens@4.18.0", "", { "dependencies": { "react-freeze": "^1.0.0", "warn-once": "^0.1.0" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-mRTLWL7Uc1p/RFNveEIIrhP22oxHduC2ZnLr/2iHwBeYpGXR0rJZ7Bgc0ktxQSHRjWTPT70qc/7yd4r9960PBQ=="],
|
||||
"react-native-screens": ["react-native-screens@4.19.0", "", { "dependencies": { "react-freeze": "^1.0.0", "warn-once": "^0.1.0" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-qSDAO3AL5bti0Ri7KZRSVmWlhDr8MV86N5GruiKVQfEL7Zx2nUi3Dl62lqHUAD/LnDvOPuDDsMHCfIpYSv3hPQ=="],
|
||||
|
||||
"react-native-svg": ["react-native-svg@15.12.1", "", { "dependencies": { "css-select": "^5.1.0", "css-tree": "^1.1.3", "warn-once": "0.1.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-vCuZJDf8a5aNC2dlMovEv4Z0jjEUET53lm/iILFnFewa15b4atjVxU6Wirm6O9y6dEsdjDZVD7Q3QM4T1wlI8g=="],
|
||||
|
||||
|
||||
@@ -144,10 +144,7 @@ export const Button: React.FC<PropsWithChildren<ButtonProps>> = ({
|
||||
${colorClasses}
|
||||
${className}`}
|
||||
>
|
||||
<Text
|
||||
className={`${textColorClass} text-xl font-bold`}
|
||||
ellipsizeMode='tail'
|
||||
>
|
||||
<Text className={`${textColorClass} text-xl font-bold`}>
|
||||
{children}
|
||||
</Text>
|
||||
</View>
|
||||
@@ -189,7 +186,6 @@ export const Button: React.FC<PropsWithChildren<ButtonProps>> = ({
|
||||
${iconRight ? "mr-2" : ""}
|
||||
${iconLeft ? "ml-2" : ""}
|
||||
`}
|
||||
ellipsizeMode='tail'
|
||||
>
|
||||
{children}
|
||||
</Text>
|
||||
|
||||
@@ -17,7 +17,7 @@ const JellyfinServerDiscovery: React.FC<Props> = ({ onServerSelect }) => {
|
||||
return (
|
||||
<View className='mt-2'>
|
||||
<Button onPress={startDiscovery} color='black'>
|
||||
<Text maxFontSizeMultiplier={1.2} className='text-white text-center'>
|
||||
<Text className='text-white text-center'>
|
||||
{isSearching
|
||||
? t("server.searching")
|
||||
: t("server.search_for_local_servers")}
|
||||
|
||||
@@ -532,24 +532,18 @@ 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" }]}
|
||||
maxFontSizeMultiplier={1.2}
|
||||
>
|
||||
<Animated.Text style={[animatedTextStyle, { fontWeight: "bold" }]}>
|
||||
{runtimeTicksToMinutes(
|
||||
(item?.RunTimeTicks || 0) -
|
||||
(item?.UserData?.PlaybackPositionTicks || 0),
|
||||
)}
|
||||
{(item?.UserData?.PlaybackPositionTicks || 0) > 0 && " left"}
|
||||
</Animated.Text>
|
||||
<Animated.Text style={animatedTextStyle} maxFontSizeMultiplier={1.2}>
|
||||
<Animated.Text style={animatedTextStyle}>
|
||||
<Ionicons name='play-circle' size={24} />
|
||||
</Animated.Text>
|
||||
{client && (
|
||||
<Animated.Text
|
||||
style={animatedTextStyle}
|
||||
maxFontSizeMultiplier={1.2}
|
||||
>
|
||||
<Animated.Text style={animatedTextStyle}>
|
||||
<Feather name='cast' size={22} />
|
||||
<CastButton tintColor='transparent' />
|
||||
</Animated.Text>
|
||||
|
||||
@@ -198,13 +198,10 @@ 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" }]}
|
||||
maxFontSizeMultiplier={1.2}
|
||||
>
|
||||
<Animated.Text style={[animatedTextStyle, { fontWeight: "bold" }]}>
|
||||
{runtimeTicksToMinutes(item?.RunTimeTicks)}
|
||||
</Animated.Text>
|
||||
<Animated.Text style={animatedTextStyle} maxFontSizeMultiplier={1.2}>
|
||||
<Animated.Text style={animatedTextStyle}>
|
||||
<Ionicons name='play-circle' size={24} />
|
||||
</Animated.Text>
|
||||
</View>
|
||||
|
||||
@@ -13,7 +13,6 @@ export function ThemedText({
|
||||
}: ThemedTextProps) {
|
||||
return (
|
||||
<Text
|
||||
maxFontSizeMultiplier={1.3}
|
||||
style={[
|
||||
{ color: "white" },
|
||||
type === "default" ? styles.default : undefined,
|
||||
|
||||
@@ -736,7 +736,6 @@ 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,
|
||||
@@ -802,7 +801,6 @@ 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})`,
|
||||
|
||||
@@ -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}
|
||||
`}
|
||||
maxFontSizeMultiplier={1.2}
|
||||
allowFontScaling={false}
|
||||
style={[
|
||||
style,
|
||||
{
|
||||
@@ -45,7 +45,7 @@ export function Input(props: InputProps) {
|
||||
<TextInput
|
||||
ref={inputRef}
|
||||
className='p-4 rounded-xl bg-neutral-900'
|
||||
maxFontSizeMultiplier={1.2}
|
||||
allowFontScaling={false}
|
||||
style={[{ color: "white" }, style]}
|
||||
placeholderTextColor={"#9CA3AF"}
|
||||
clearButtonMode='while-editing'
|
||||
|
||||
@@ -19,9 +19,7 @@ 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' ellipsizeMode='tail'>
|
||||
{title}
|
||||
</Text>
|
||||
<Text className='text-lg font-bold text-neutral-100'>{title}</Text>
|
||||
{shouldShowAction && (
|
||||
<TouchableOpacity
|
||||
onPress={onPressAction}
|
||||
@@ -31,7 +29,6 @@ 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,
|
||||
}}
|
||||
|
||||
@@ -4,7 +4,7 @@ export function Text(props: TextProps) {
|
||||
if (Platform.isTV)
|
||||
return (
|
||||
<RNText
|
||||
maxFontSizeMultiplier={1.3}
|
||||
allowFontScaling={false}
|
||||
style={[{ color: "white" }, style]}
|
||||
{...otherProps}
|
||||
/>
|
||||
@@ -12,7 +12,7 @@ export function Text(props: TextProps) {
|
||||
|
||||
return (
|
||||
<RNText
|
||||
maxFontSizeMultiplier={1.3}
|
||||
allowFontScaling={false}
|
||||
style={[{ color: "white" }, style]}
|
||||
{...otherProps}
|
||||
/>
|
||||
|
||||
@@ -175,16 +175,10 @@ export const Favorites = () => {
|
||||
contentFit='contain'
|
||||
source={heart}
|
||||
/>
|
||||
<Text
|
||||
maxFontSizeMultiplier={1.2}
|
||||
className='text-xl font-semibold text-white mb-2'
|
||||
>
|
||||
<Text className='text-xl font-semibold text-white mb-2'>
|
||||
{t("favorites.noDataTitle")}
|
||||
</Text>
|
||||
<Text
|
||||
maxFontSizeMultiplier={1.2}
|
||||
className='text-base text-white/70 text-center max-w-xs px-4'
|
||||
>
|
||||
<Text className='text-base text-white/70 text-center max-w-xs px-4'>
|
||||
{t("favorites.noData")}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -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 maxFontSizeMultiplier={1.2} className='text-xs text-red-600 mb-2'>
|
||||
<Text 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 maxFontSizeMultiplier={1.2} className='text-white'>
|
||||
<Text className='text-white'>
|
||||
{isEnabled ? t("Watchlist On") : t("Watchlist Off")}
|
||||
</Text>
|
||||
|
||||
|
||||
@@ -19,9 +19,7 @@ const SkipButton: React.FC<SkipButtonProps> = ({
|
||||
onPress={onPress}
|
||||
className='bg-black/60 rounded-md px-3 py-3 border border-neutral-900'
|
||||
>
|
||||
<Text maxFontSizeMultiplier={1.2} className='text-white font-bold'>
|
||||
{buttonText}
|
||||
</Text>
|
||||
<Text className='text-white font-bold'>{buttonText}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -102,7 +102,6 @@ const SliderScrubber: React.FC<SliderScrubberProps> = ({
|
||||
contentFit='cover'
|
||||
/>
|
||||
<Text
|
||||
maxFontSizeMultiplier={1.2}
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 5,
|
||||
@@ -127,16 +126,10 @@ const SliderScrubber: React.FC<SliderScrubberProps> = ({
|
||||
maximumValue={max}
|
||||
/>
|
||||
<View className='flex flex-row items-center justify-between mt-0.5'>
|
||||
<Text
|
||||
maxFontSizeMultiplier={1.2}
|
||||
className='text-[12px] text-neutral-400'
|
||||
>
|
||||
<Text className='text-[12px] text-neutral-400'>
|
||||
{formatTimeString(currentTime, "ms")}
|
||||
</Text>
|
||||
<Text
|
||||
maxFontSizeMultiplier={1.2}
|
||||
className='text-[12px] text-neutral-400'
|
||||
>
|
||||
<Text className='text-[12px] text-neutral-400'>
|
||||
-{formatTimeString(remainingTime, "ms")}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -195,7 +195,6 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
|
||||
<View style={styles.infoBox}>
|
||||
{playMethod && (
|
||||
<Text
|
||||
maxFontSizeMultiplier={1.2}
|
||||
style={[
|
||||
styles.infoText,
|
||||
{ color: getPlayMethodColor(playMethod) },
|
||||
@@ -205,31 +204,28 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
|
||||
</Text>
|
||||
)}
|
||||
{transcodeReasons && transcodeReasons.length > 0 && (
|
||||
<Text
|
||||
maxFontSizeMultiplier={1.2}
|
||||
style={[styles.infoText, styles.reasonText]}
|
||||
>
|
||||
<Text style={[styles.infoText, styles.reasonText]}>
|
||||
{transcodeReasons.map(formatTranscodeReason).join(", ")}
|
||||
</Text>
|
||||
)}
|
||||
{info?.videoWidth && info?.videoHeight && (
|
||||
<Text maxFontSizeMultiplier={1.2} style={styles.infoText}>
|
||||
<Text style={styles.infoText}>
|
||||
{info.videoWidth}x{info.videoHeight}
|
||||
</Text>
|
||||
)}
|
||||
{info?.videoCodec && (
|
||||
<Text maxFontSizeMultiplier={1.2} style={styles.infoText}>
|
||||
<Text style={styles.infoText}>
|
||||
Video: {formatCodec(info.videoCodec)}
|
||||
{info.fps ? ` @ ${formatFps(info.fps)} fps` : ""}
|
||||
</Text>
|
||||
)}
|
||||
{info?.audioCodec && (
|
||||
<Text maxFontSizeMultiplier={1.2} style={styles.infoText}>
|
||||
<Text style={styles.infoText}>
|
||||
Audio: {formatCodec(info.audioCodec)}
|
||||
</Text>
|
||||
)}
|
||||
{(info?.videoBitrate || info?.audioBitrate) && (
|
||||
<Text maxFontSizeMultiplier={1.2} style={styles.infoText}>
|
||||
<Text style={styles.infoText}>
|
||||
Bitrate:{" "}
|
||||
{info.videoBitrate
|
||||
? formatBitrate(info.videoBitrate)
|
||||
@@ -239,22 +235,17 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
|
||||
</Text>
|
||||
)}
|
||||
{info?.cacheSeconds !== undefined && (
|
||||
<Text maxFontSizeMultiplier={1.2} style={styles.infoText}>
|
||||
<Text style={styles.infoText}>
|
||||
Buffer: {info.cacheSeconds.toFixed(1)}s
|
||||
</Text>
|
||||
)}
|
||||
{info?.droppedFrames !== undefined && info.droppedFrames > 0 && (
|
||||
<Text
|
||||
maxFontSizeMultiplier={1.2}
|
||||
style={[styles.infoText, styles.warningText]}
|
||||
>
|
||||
<Text style={[styles.infoText, styles.warningText]}>
|
||||
Dropped: {info.droppedFrames} frames
|
||||
</Text>
|
||||
)}
|
||||
{!info && !playMethod && (
|
||||
<Text maxFontSizeMultiplier={1.2} style={styles.infoText}>
|
||||
Loading...
|
||||
</Text>
|
||||
<Text style={styles.infoText}>Loading...</Text>
|
||||
)}
|
||||
</View>
|
||||
</Animated.View>
|
||||
|
||||
Binary file not shown.
@@ -1,13 +1,10 @@
|
||||
package expo.modules.mpvplayer
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.AssetManager
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.view.Surface
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
|
||||
/**
|
||||
* MPV renderer that wraps libmpv for video playback.
|
||||
@@ -29,7 +26,7 @@ class MPVLayerRenderer(private val context: Context) : MPVLib.EventObserver {
|
||||
}
|
||||
|
||||
interface Delegate {
|
||||
fun onPositionChanged(position: Double, duration: Double, cacheSeconds: Double)
|
||||
fun onPositionChanged(position: Double, duration: Double)
|
||||
fun onPauseChanged(isPaused: Boolean)
|
||||
fun onLoadingChanged(isLoading: Boolean)
|
||||
fun onReadyToSeek()
|
||||
@@ -49,7 +46,6 @@ class MPVLayerRenderer(private val context: Context) : MPVLib.EventObserver {
|
||||
// Cached state
|
||||
private var cachedPosition: Double = 0.0
|
||||
private var cachedDuration: Double = 0.0
|
||||
private var cachedCacheSeconds: Double = 0.0
|
||||
private var _isPaused: Boolean = true
|
||||
private var _isLoading: Boolean = false
|
||||
private var _playbackSpeed: Double = 1.0
|
||||
@@ -105,52 +101,6 @@ class MPVLayerRenderer(private val context: Context) : MPVLib.EventObserver {
|
||||
MPVLib.create(context)
|
||||
MPVLib.addObserver(this)
|
||||
|
||||
/**
|
||||
* Create mpv config directory and copy font files to ensure SubRip subtitles load properly on Android.
|
||||
*
|
||||
* Technical Background:
|
||||
* ====================
|
||||
* On Android, mpv requires access to a font file to render text-based subtitles, particularly SubRip (.srt)
|
||||
* format subtitles. Without an available font in the config directory, mpv will fail to display subtitles
|
||||
* even when subtitle tracks are properly detected and loaded.
|
||||
*
|
||||
* Why This Is Necessary:
|
||||
* =====================
|
||||
* 1. Android's font system is isolated from native libraries like mpv. While Android has system fonts,
|
||||
* mpv cannot access them directly due to sandboxing and library isolation.
|
||||
*
|
||||
* 2. SubRip subtitles require a font to render text overlay on video. When no font is available in the
|
||||
* configured directory, mpv either:
|
||||
* - Fails silently (subtitles don't appear)
|
||||
* - Falls back to a default font that may not support the required character set
|
||||
* - Crashes or produces rendering errors
|
||||
*
|
||||
* 3. By placing a font file (font.ttf) in mpv's config directory and setting that directory via
|
||||
* MPVLib.setOptionString("config-dir", ...), we ensure mpv has a known, accessible font source.
|
||||
*
|
||||
* Reference:
|
||||
* =========
|
||||
* This workaround is documented in the mpv-android project:
|
||||
* https://github.com/mpv-android/mpv-android/issues/96
|
||||
*
|
||||
* The issue discusses that without a font in the config directory, SubRip subtitles fail to load
|
||||
* properly on Android, and the solution is to copy a font file to a known location that mpv can access.
|
||||
*/
|
||||
// Create mpv config directory and copy font files
|
||||
val mpvDir = File(context.getExternalFilesDir(null) ?: context.filesDir, "mpv")
|
||||
//Log.i(TAG, "mpv config dir: $mpvDir")
|
||||
if (!mpvDir.exists()) mpvDir.mkdirs()
|
||||
// This needs to be named `subfont.ttf` else it won't work
|
||||
arrayOf("subfont.ttf").forEach { fileName ->
|
||||
val file = File(mpvDir, fileName)
|
||||
if (file.exists()) return@forEach
|
||||
context.assets
|
||||
.open(fileName, AssetManager.ACCESS_STREAMING)
|
||||
.copyTo(FileOutputStream(file))
|
||||
}
|
||||
MPVLib.setOptionString("config", "yes")
|
||||
MPVLib.setOptionString("config-dir", mpvDir.path)
|
||||
|
||||
// Configure mpv options before initialization (based on Findroid)
|
||||
MPVLib.setOptionString("vo", "gpu")
|
||||
MPVLib.setOptionString("gpu-context", "android")
|
||||
@@ -174,7 +124,7 @@ class MPVLayerRenderer(private val context: Context) : MPVLib.EventObserver {
|
||||
MPVLib.setOptionString("hr-seek-framedrop", "yes")
|
||||
|
||||
// Subtitle settings
|
||||
MPVLib.setOptionString("sub-scale-with-window", "no")
|
||||
MPVLib.setOptionString("sub-scale-with-window", "yes")
|
||||
MPVLib.setOptionString("sub-use-margins", "no")
|
||||
MPVLib.setOptionString("subs-match-os-language", "yes")
|
||||
MPVLib.setOptionString("subs-fallback", "yes")
|
||||
@@ -333,7 +283,6 @@ class MPVLayerRenderer(private val context: Context) : MPVLib.EventObserver {
|
||||
MPVLib.observeProperty("pause", MPV_FORMAT_FLAG)
|
||||
MPVLib.observeProperty("track-list/count", MPV_FORMAT_INT64)
|
||||
MPVLib.observeProperty("paused-for-cache", MPV_FORMAT_FLAG)
|
||||
MPVLib.observeProperty("demuxer-cache-duration", MPV_FORMAT_DOUBLE)
|
||||
// Video dimensions for PiP aspect ratio
|
||||
MPVLib.observeProperty("video-params/w", MPV_FORMAT_INT64)
|
||||
MPVLib.observeProperty("video-params/h", MPV_FORMAT_INT64)
|
||||
@@ -612,7 +561,7 @@ class MPVLayerRenderer(private val context: Context) : MPVLib.EventObserver {
|
||||
when (property) {
|
||||
"duration" -> {
|
||||
cachedDuration = value
|
||||
mainHandler.post { delegate?.onPositionChanged(cachedPosition, cachedDuration, cachedCacheSeconds) }
|
||||
mainHandler.post { delegate?.onPositionChanged(cachedPosition, cachedDuration) }
|
||||
}
|
||||
"time-pos" -> {
|
||||
cachedPosition = value
|
||||
@@ -621,12 +570,9 @@ class MPVLayerRenderer(private val context: Context) : MPVLib.EventObserver {
|
||||
val shouldUpdate = _isSeeking || (now - lastProgressUpdateTime >= 1000)
|
||||
if (shouldUpdate) {
|
||||
lastProgressUpdateTime = now
|
||||
mainHandler.post { delegate?.onPositionChanged(cachedPosition, cachedDuration, cachedCacheSeconds) }
|
||||
mainHandler.post { delegate?.onPositionChanged(cachedPosition, cachedDuration) }
|
||||
}
|
||||
}
|
||||
"demuxer-cache-duration" -> {
|
||||
cachedCacheSeconds = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -307,7 +307,7 @@ class MpvPlayerView(context: Context, appContext: AppContext) : ExpoView(context
|
||||
|
||||
// MARK: - MPVLayerRenderer.Delegate
|
||||
|
||||
override fun onPositionChanged(position: Double, duration: Double, cacheSeconds: Double) {
|
||||
override fun onPositionChanged(position: Double, duration: Double) {
|
||||
cachedPosition = position
|
||||
cachedDuration = duration
|
||||
|
||||
@@ -319,8 +319,7 @@ class MpvPlayerView(context: Context, appContext: AppContext) : ExpoView(context
|
||||
onProgress(mapOf(
|
||||
"position" to position,
|
||||
"duration" to duration,
|
||||
"progress" to if (duration > 0) position / duration else 0.0,
|
||||
"cacheSeconds" to cacheSeconds
|
||||
"progress" to if (duration > 0) position / duration else 0.0
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import CoreVideo
|
||||
import AVFoundation
|
||||
|
||||
protocol MPVLayerRendererDelegate: AnyObject {
|
||||
func renderer(_ renderer: MPVLayerRenderer, didUpdatePosition position: Double, duration: Double, cacheSeconds: Double)
|
||||
func renderer(_ renderer: MPVLayerRenderer, didUpdatePosition position: Double, duration: Double)
|
||||
func renderer(_ renderer: MPVLayerRenderer, didChangePause isPaused: Bool)
|
||||
func renderer(_ renderer: MPVLayerRenderer, didChangeLoading isLoading: Bool)
|
||||
func renderer(_ renderer: MPVLayerRenderer, didBecomeReadyToSeek: Bool)
|
||||
@@ -44,7 +44,6 @@ final class MPVLayerRenderer {
|
||||
// Thread-safe state for playback
|
||||
private var _cachedDuration: Double = 0
|
||||
private var _cachedPosition: Double = 0
|
||||
private var _cachedCacheSeconds: Double = 0
|
||||
private var _isPaused: Bool = true
|
||||
private var _playbackSpeed: Double = 1.0
|
||||
private var _isLoading: Bool = false
|
||||
@@ -76,10 +75,6 @@ final class MPVLayerRenderer {
|
||||
get { stateQueue.sync { _cachedPosition } }
|
||||
set { stateQueue.async(flags: .barrier) { self._cachedPosition = newValue } }
|
||||
}
|
||||
private var cachedCacheSeconds: Double {
|
||||
get { stateQueue.sync { _cachedCacheSeconds } }
|
||||
set { stateQueue.async(flags: .barrier) { self._cachedCacheSeconds = newValue } }
|
||||
}
|
||||
private var isPaused: Bool {
|
||||
get { stateQueue.sync { _isPaused } }
|
||||
set { stateQueue.async(flags: .barrier) { self._isPaused = newValue } }
|
||||
@@ -169,7 +164,6 @@ final class MPVLayerRenderer {
|
||||
|
||||
// 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
|
||||
@@ -346,8 +340,7 @@ final class MPVLayerRenderer {
|
||||
("time-pos", MPV_FORMAT_DOUBLE),
|
||||
("pause", MPV_FORMAT_FLAG),
|
||||
("track-list/count", MPV_FORMAT_INT64),
|
||||
("paused-for-cache", MPV_FORMAT_FLAG),
|
||||
("demuxer-cache-duration", MPV_FORMAT_DOUBLE)
|
||||
("paused-for-cache", MPV_FORMAT_FLAG)
|
||||
]
|
||||
for (name, format) in properties {
|
||||
mpv_observe_property(handle, 0, name, format)
|
||||
@@ -491,7 +484,7 @@ final class MPVLayerRenderer {
|
||||
cachedDuration = value
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self else { return }
|
||||
self.delegate?.renderer(self, didUpdatePosition: self.cachedPosition, duration: self.cachedDuration, cacheSeconds: self.cachedCacheSeconds)
|
||||
self.delegate?.renderer(self, didUpdatePosition: self.cachedPosition, duration: self.cachedDuration)
|
||||
}
|
||||
}
|
||||
case "time-pos":
|
||||
@@ -506,16 +499,10 @@ final class MPVLayerRenderer {
|
||||
lastProgressUpdateTime = now
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self else { return }
|
||||
self.delegate?.renderer(self, didUpdatePosition: self.cachedPosition, duration: self.cachedDuration, cacheSeconds: self.cachedCacheSeconds)
|
||||
self.delegate?.renderer(self, didUpdatePosition: self.cachedPosition, duration: self.cachedDuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
case "demuxer-cache-duration":
|
||||
var value = Double(0)
|
||||
let status = getProperty(handle: handle, name: name, format: MPV_FORMAT_DOUBLE, value: &value)
|
||||
if status >= 0 {
|
||||
cachedCacheSeconds = value
|
||||
}
|
||||
case "pause":
|
||||
var flag: Int32 = 0
|
||||
let status = getProperty(handle: handle, name: name, format: MPV_FORMAT_FLAG, value: &flag)
|
||||
|
||||
@@ -298,7 +298,7 @@ class MpvPlayerView: ExpoView {
|
||||
// MARK: - MPVLayerRendererDelegate
|
||||
|
||||
extension MpvPlayerView: MPVLayerRendererDelegate {
|
||||
func renderer(_: MPVLayerRenderer, didUpdatePosition position: Double, duration: Double, cacheSeconds: Double) {
|
||||
func renderer(_: MPVLayerRenderer, didUpdatePosition position: Double, duration: Double) {
|
||||
cachedPosition = position
|
||||
cachedDuration = duration
|
||||
|
||||
@@ -313,7 +313,6 @@ extension MpvPlayerView: MPVLayerRendererDelegate {
|
||||
"position": position,
|
||||
"duration": duration,
|
||||
"progress": duration > 0 ? position / duration : 0,
|
||||
"cacheSeconds": cacheSeconds,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@ export type OnProgressEventPayload = {
|
||||
position: number;
|
||||
duration: number;
|
||||
progress: number;
|
||||
/** Seconds of video buffered ahead of current position */
|
||||
cacheSeconds: number;
|
||||
};
|
||||
|
||||
export type OnErrorEventPayload = {
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
"react-native-reanimated": "~4.1.1",
|
||||
"react-native-reanimated-carousel": "4.0.3",
|
||||
"react-native-safe-area-context": "~5.6.0",
|
||||
"react-native-screens": "~4.18.0",
|
||||
"react-native-screens": "~4.19.0",
|
||||
"react-native-svg": "15.12.1",
|
||||
"react-native-text-ticker": "^1.15.0",
|
||||
"react-native-track-player": "github:lovegaoshi/react-native-track-player#APM",
|
||||
|
||||
Reference in New Issue
Block a user