From db20fffeb5c4ccffa566a5d4a71a321eea3907e2 Mon Sep 17 00:00:00 2001 From: Alex Kim Date: Mon, 28 Oct 2024 21:12:42 +1100 Subject: [PATCH] Fixed trick play for VLC --- app/(auth)/vlc-player.tsx | 15 +++++---- components/video-player/Controls.tsx | 32 ++++++++++++++++-- hooks/useTrickplay.ts | 35 ++++++++++++-------- modules/vlc-player/ios/VlcPlayerModule.swift | 30 ++++++++--------- 4 files changed, 75 insertions(+), 37 deletions(-) diff --git a/app/(auth)/vlc-player.tsx b/app/(auth)/vlc-player.tsx index 46cce362..4727b0a7 100644 --- a/app/(auth)/vlc-player.tsx +++ b/app/(auth)/vlc-player.tsx @@ -240,14 +240,18 @@ export default function page() { const { currentTime } = data.nativeEvent; + if (isBuffering) { + setIsBuffering(false); + } + progress.value = currentTime; const currentTimeInTicks = msToTicks(currentTime); - console.log("onProgress ~", { - currentTime, - currentTimeInTicks, - isPlaying, - }); + // console.log("onProgress ~", { + // currentTime, + // currentTimeInTicks, + // isPlaying, + // }); await getPlaystateApi(api).onPlaybackProgress({ itemId: item.Id, @@ -259,7 +263,6 @@ export default function page() { playMethod: stream?.url.includes("m3u8") ? "Transcode" : "DirectStream", playSessionId: stream.sessionId, }); - console.log("Progress", currentTime); }, [item?.Id, isPlaying, api, isPlaybackStopped] ); diff --git a/components/video-player/Controls.tsx b/components/video-player/Controls.tsx index ed4fd788..5710559b 100644 --- a/components/video-player/Controls.tsx +++ b/components/video-player/Controls.tsx @@ -12,6 +12,7 @@ import { getDefaultPlaySettings } from "@/utils/jellyfin/getDefaultPlaySettings" import { writeToLog } from "@/utils/log"; import { formatTimeString, + msToTicks, secondsToMs, ticksToMs, ticksToSeconds, @@ -234,8 +235,16 @@ export const Controls: React.FC = ({ [isVlc] ); + const [time, setTime] = useState({ minutes: 0, seconds: 0 }); + const handleSliderChange = (value: number) => { - calculateTrickplayUrl(value); + const progressInTicks = isVlc ? msToTicks(value) : value; + calculateTrickplayUrl(progressInTicks); + + const progressInSeconds = Math.floor(progressInTicks / 10000000); + const minutes = Math.floor(progressInSeconds / 60); + const seconds = progressInSeconds % 60; + setTime({ minutes, seconds }); }; const handleSliderStart = useCallback(() => { @@ -351,7 +360,6 @@ export const Controls: React.FC = ({ const uniqueExternalSubs = externalSubs.filter( (sub) => !embeddedSubNames.has(sub.name) ); - // Combine embedded and unique external subs return [...embeddedSubs, ...uniqueExternalSubs] as ( | EmbeddedSubtitle @@ -359,6 +367,11 @@ export const Controls: React.FC = ({ )[]; }, [item, isVideoLoaded, subtitleTracks, mediaSource]); + // useEffect(() => { + + // }, [allSubtitleTracks, setSubtitleTrack]); + const [subtitleTrackSet, setSubtitleTrackSet] = useState(false); + return ( = ({ source={{ uri: url }} contentFit="cover" /> + + {`${time.minutes}:${ + time.seconds < 10 ? `0${time.seconds}` : time.seconds + }`} + ); }} diff --git a/hooks/useTrickplay.ts b/hooks/useTrickplay.ts index 6033420b..960aee62 100644 --- a/hooks/useTrickplay.ts +++ b/hooks/useTrickplay.ts @@ -1,6 +1,7 @@ // hooks/useTrickplay.ts import { apiAtom } from "@/providers/JellyfinProvider"; +import { ticksToMs } from "@/utils/time"; import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client"; import { useAtom } from "jotai"; import { useCallback, useMemo, useRef, useState } from "react"; @@ -57,6 +58,7 @@ export const useTrickplay = (item: BaseItemDto, enabled = true) => { : null; }, [item, enabled]); + // Takes in ticks. const calculateTrickplayUrl = useCallback( (progress: number) => { if (!enabled) { @@ -74,28 +76,33 @@ export const useTrickplay = (item: BaseItemDto, enabled = true) => { } const { data, resolution } = trickplayInfo; - const { Interval, TileWidth, TileHeight } = data; + const { Interval, TileWidth, TileHeight, Width, Height } = data; - if (!Interval || !TileWidth || !TileHeight || !resolution) { + if ( + !Interval || + !TileWidth || + !TileHeight || + !resolution || + !Width || + !Height + ) { throw new Error("Invalid trickplay data"); } - const currentSecond = Math.max(0, Math.floor(progress / 10000000)); + const currentTimeMs = Math.max(0, ticksToMs(progress)); + const currentTile = Math.floor(currentTimeMs / Interval); - const cols = TileWidth; - const rows = TileHeight; - const imagesPerTile = cols * rows; - const imageIndex = Math.floor(currentSecond / (Interval / 1000)); - const tileIndex = Math.floor(imageIndex / imagesPerTile); + const tileSize = TileWidth * TileHeight; + const tileOffset = currentTile % tileSize; + const index = Math.floor(currentTile / tileSize); - const positionInTile = imageIndex % imagesPerTile; - const rowInTile = Math.floor(positionInTile / cols); - const colInTile = positionInTile % cols; + const tileOffsetX = tileOffset % TileWidth; + const tileOffsetY = Math.floor(tileOffset / TileWidth); const newTrickPlayUrl = { - x: rowInTile, - y: colInTile, - url: `${api.basePath}/Videos/${item.Id}/Trickplay/${resolution}/${tileIndex}.jpg?api_key=${api.accessToken}`, + x: tileOffsetX, + y: tileOffsetY, + url: `${api.basePath}/Videos/${item.Id}/Trickplay/${resolution}/${index}.jpg?api_key=${api.accessToken}`, }; setTrickPlayUrl(newTrickPlayUrl); diff --git a/modules/vlc-player/ios/VlcPlayerModule.swift b/modules/vlc-player/ios/VlcPlayerModule.swift index 992ecd0c..64d6cad5 100644 --- a/modules/vlc-player/ios/VlcPlayerModule.swift +++ b/modules/vlc-player/ios/VlcPlayerModule.swift @@ -16,17 +16,17 @@ public class VlcPlayerModule: Module { } } -// Prop("muted") { (view: VlcPlayerView, muted: Bool) in -// view.setMuted(muted) -// } + // Prop("muted") { (view: VlcPlayerView, muted: Bool) in + // view.setMuted(muted) + // } -// Prop("volume") { (view: VlcPlayerView, volume: Int) in -// view.setVolume(volume) -// } + // Prop("volume") { (view: VlcPlayerView, volume: Int) in + // view.setVolume(volume) + // } -// Prop("videoAspectRatio") { (view: VlcPlayerView, ratio: String) in -// view.setVideoAspectRatio(ratio) -// } + // Prop("videoAspectRatio") { (view: VlcPlayerView, ratio: String) in + // view.setVideoAspectRatio(ratio) + // } Events( "onPlaybackStateChanged", @@ -69,13 +69,13 @@ public class VlcPlayerModule: Module { return view.getSubtitleTracks() } -// AsyncFunction("setVideoCropGeometry") { (view: VlcPlayerView, geometry: String?) in -// view.setVideoCropGeometry(geometry) -// } + // AsyncFunction("setVideoCropGeometry") { (view: VlcPlayerView, geometry: String?) in + // view.setVideoCropGeometry(geometry) + // } -// AsyncFunction("getVideoCropGeometry") { (view: VlcPlayerView) -> String? in -// return view.getVideoCropGeometry() -// } + // AsyncFunction("getVideoCropGeometry") { (view: VlcPlayerView) -> String? in + // return view.getVideoCropGeometry() + // } AsyncFunction("setSubtitleURL") { (view: VlcPlayerView, url: String, name: String) in