Fixed trick play for VLC

This commit is contained in:
Alex Kim
2024-10-28 21:12:42 +11:00
parent 9ca71dc7fc
commit db20fffeb5
4 changed files with 75 additions and 37 deletions

View File

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

View File

@@ -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<Props> = ({
[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<Props> = ({
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<Props> = ({
)[];
}, [item, isVideoLoaded, subtitleTracks, mediaSource]);
// useEffect(() => {
// }, [allSubtitleTracks, setSubtitleTrack]);
const [subtitleTrackSet, setSubtitleTrackSet] = useState(false);
return (
<View
style={[
@@ -715,6 +728,21 @@ export const Controls: React.FC<Props> = ({
source={{ uri: url }}
contentFit="cover"
/>
<Text
style={{
position: "absolute",
bottom: 5,
left: 5,
color: "white",
backgroundColor: "rgba(0, 0, 0, 0.5)",
padding: 5,
borderRadius: 5,
}}
>
{`${time.minutes}:${
time.seconds < 10 ? `0${time.seconds}` : time.seconds
}`}
</Text>
</View>
);
}}

View File

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

View File

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