Compare commits

..

1 Commits

Author SHA1 Message Date
renovate[bot]
65be3d4059 chore(deps): Update dependency @react-navigation/material-top-tabs to v7.4.12 2026-01-15 14:25:10 +00:00
8 changed files with 23 additions and 50 deletions

View File

@@ -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();

View File

@@ -14,7 +14,7 @@
"@gorhom/bottom-sheet": "5.2.8",
"@jellyfin/sdk": "^0.13.0",
"@react-native-community/netinfo": "^11.4.1",
"@react-navigation/material-top-tabs": "7.4.9",
"@react-navigation/material-top-tabs": "7.4.12",
"@react-navigation/native": "^7.0.14",
"@shopify/flash-list": "2.0.2",
"@tanstack/query-sync-storage-persister": "^5.90.18",
@@ -74,7 +74,7 @@
"react-native-ios-context-menu": "^3.2.1",
"react-native-ios-utilities": "5.2.0",
"react-native-mmkv": "4.1.1",
"react-native-nitro-modules": "0.33.1",
"react-native-nitro-modules": "0.32.1",
"react-native-pager-view": "^6.9.1",
"react-native-reanimated": "~4.1.1",
"react-native-reanimated-carousel": "4.0.3",
@@ -566,9 +566,9 @@
"@react-navigation/core": ["@react-navigation/core@7.13.0", "", { "dependencies": { "@react-navigation/routers": "^7.5.1", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "query-string": "^7.1.3", "react-is": "^19.1.0", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": ">= 18.2.0" } }, "sha512-Fc/SO23HnlGnkou/z8JQUzwEMvhxuUhr4rdPTIZp/c8q1atq3k632Nfh8fEiGtk+MP1wtIvXdN2a5hBIWpLq3g=="],
"@react-navigation/elements": ["@react-navigation/elements@2.9.2", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", "@react-navigation/native": "^7.1.25", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-J1GltOAGowNLznEphV/kr4zs0U7mUBO1wVA2CqpkN8ePBsoxrAmsd+T5sEYUCXN9KgTDFvc6IfcDqrGSQngd/g=="],
"@react-navigation/elements": ["@react-navigation/elements@2.9.4", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", "@react-navigation/native": "^7.1.27", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-TMFh+QzwesEuSaKpvZk4BFC5105t5gJs9eq+jG7jtfdlMKyrcFwheu2/dy1zfrW4WYbVcoMxhzFqCU4mKwI4fw=="],
"@react-navigation/material-top-tabs": ["@react-navigation/material-top-tabs@7.4.9", "", { "dependencies": { "@react-navigation/elements": "^2.9.2", "color": "^4.2.3", "react-native-tab-view": "^4.2.0" }, "peerDependencies": { "@react-navigation/native": "^7.1.25", "react": ">= 18.2.0", "react-native": "*", "react-native-pager-view": ">= 6.0.0", "react-native-safe-area-context": ">= 4.0.0" } }, "sha512-oYpdTfa2D1Tn0HJER9dRCR260agKGgYe+ydSHt3RIsJ9sLg8hU7ntKYWo1FnEC/Nsv1/N1u/tRst7ZpQRjjl4A=="],
"@react-navigation/material-top-tabs": ["@react-navigation/material-top-tabs@7.4.12", "", { "dependencies": { "@react-navigation/elements": "^2.9.4", "color": "^4.2.3", "react-native-tab-view": "^4.2.2" }, "peerDependencies": { "@react-navigation/native": "^7.1.27", "react": ">= 18.2.0", "react-native": "*", "react-native-pager-view": ">= 6.0.0", "react-native-safe-area-context": ">= 4.0.0" } }, "sha512-SrftYrhyx/bnpl51EP62yyymT+AyOtSSP9XIc0vLVPf88e7W0dWJI1SgGdWoYnvFPT5O7QN2xmB/Il39deDhzQ=="],
"@react-navigation/native": ["@react-navigation/native@7.1.19", "", { "dependencies": { "@react-navigation/core": "^7.13.0", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*" } }, "sha512-fM7q8di4Q8sp2WUhiUWOe7bEDRyRhbzsKQOd5N2k+lHeCx3UncsRYuw4Q/KN0EovM3wWKqMMmhy/YWuEO04kgw=="],
@@ -1678,7 +1678,7 @@
"react-native-mmkv": ["react-native-mmkv@4.1.1", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": "*" } }, "sha512-nYFjM27l7zVhIiyAqWEFRagGASecb13JMIlzAuOeakRRz9GMJ49hCQntUBE2aeuZRE4u4ehSqTOomB0mTF56Ew=="],
"react-native-nitro-modules": ["react-native-nitro-modules@0.33.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-Kdo8qiqlkGAEs7fq29i0yiZs0Gf7ucmMiFsH8PH4uzsnSGEt2CQRBJGnQKKMl9vJYL8e7rzA0TZKRwO/L8G/Sg=="],
"react-native-nitro-modules": ["react-native-nitro-modules@0.32.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-V+Vy76e4fxRxgVGu5Uh3cBPvuFQW8fM1OUKk1mqEA/JawjhX+hxHtBhpfuvNjV0BnV/uXCIg8/eK+rTpB6tqFg=="],
"react-native-pager-view": ["react-native-pager-view@6.9.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-uUT0MMMbNtoSbxe9pRvdJJKEi9snjuJ3fXlZhG8F2vVMOBJVt/AFtqMPUHu9yMflmqOr08PewKzj9EPl/Yj+Gw=="],
@@ -1692,7 +1692,7 @@
"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=="],
"react-native-tab-view": ["react-native-tab-view@4.2.0", "", { "dependencies": { "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*", "react-native-pager-view": ">= 6.0.0" } }, "sha512-TUbh7Yr0tE/99t1pJQLbQ+4/Px67xkT7/r3AhfV+93Q3WoUira0Lx7yuKUP2C118doqxub8NCLERwcqsHr29nQ=="],
"react-native-tab-view": ["react-native-tab-view@4.2.2", "", { "dependencies": { "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*", "react-native-pager-view": ">= 6.0.0" } }, "sha512-NXtrG6OchvbGjsvbySJGVocXxo4Y2vA17ph4rAaWtA2jh+AasD8OyikKBRg2SmllEfeQ+GEhcKe8kulHv8BhTg=="],
"react-native-text-ticker": ["react-native-text-ticker@1.15.0", "", {}, "sha512-d/uK+PIOhsYMy1r8h825iq/nADiHsabz3WMbRJSnkpQYn+K9aykUAXRRhu8ZbTAzk4CgnUWajJEFxS5ZDygsdg=="],

View File

@@ -26,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()
@@ -46,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
@@ -284,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)
@@ -563,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
@@ -572,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
}
}
}

View File

@@ -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
))
}

View File

@@ -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 } }
@@ -167,16 +162,16 @@ 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
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"))
@@ -345,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)
@@ -490,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":
@@ -505,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)

View File

@@ -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,
])
}
}

View File

@@ -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 = {

View File

@@ -34,7 +34,7 @@
"@gorhom/bottom-sheet": "5.2.8",
"@jellyfin/sdk": "^0.13.0",
"@react-native-community/netinfo": "^11.4.1",
"@react-navigation/material-top-tabs": "7.4.9",
"@react-navigation/material-top-tabs": "7.4.12",
"@react-navigation/native": "^7.0.14",
"@shopify/flash-list": "2.0.2",
"@tanstack/query-sync-storage-persister": "^5.90.18",
@@ -94,7 +94,7 @@
"react-native-ios-context-menu": "^3.2.1",
"react-native-ios-utilities": "5.2.0",
"react-native-mmkv": "4.1.1",
"react-native-nitro-modules": "0.33.1",
"react-native-nitro-modules": "0.32.1",
"react-native-pager-view": "^6.9.1",
"react-native-reanimated": "~4.1.1",
"react-native-reanimated-carousel": "4.0.3",