mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-04-22 00:34:43 +01:00
Fixed trick play for VLC
This commit is contained in:
@@ -240,14 +240,18 @@ export default function page() {
|
|||||||
|
|
||||||
const { currentTime } = data.nativeEvent;
|
const { currentTime } = data.nativeEvent;
|
||||||
|
|
||||||
|
if (isBuffering) {
|
||||||
|
setIsBuffering(false);
|
||||||
|
}
|
||||||
|
|
||||||
progress.value = currentTime;
|
progress.value = currentTime;
|
||||||
const currentTimeInTicks = msToTicks(currentTime);
|
const currentTimeInTicks = msToTicks(currentTime);
|
||||||
|
|
||||||
console.log("onProgress ~", {
|
// console.log("onProgress ~", {
|
||||||
currentTime,
|
// currentTime,
|
||||||
currentTimeInTicks,
|
// currentTimeInTicks,
|
||||||
isPlaying,
|
// isPlaying,
|
||||||
});
|
// });
|
||||||
|
|
||||||
await getPlaystateApi(api).onPlaybackProgress({
|
await getPlaystateApi(api).onPlaybackProgress({
|
||||||
itemId: item.Id,
|
itemId: item.Id,
|
||||||
@@ -259,7 +263,6 @@ export default function page() {
|
|||||||
playMethod: stream?.url.includes("m3u8") ? "Transcode" : "DirectStream",
|
playMethod: stream?.url.includes("m3u8") ? "Transcode" : "DirectStream",
|
||||||
playSessionId: stream.sessionId,
|
playSessionId: stream.sessionId,
|
||||||
});
|
});
|
||||||
console.log("Progress", currentTime);
|
|
||||||
},
|
},
|
||||||
[item?.Id, isPlaying, api, isPlaybackStopped]
|
[item?.Id, isPlaying, api, isPlaybackStopped]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { getDefaultPlaySettings } from "@/utils/jellyfin/getDefaultPlaySettings"
|
|||||||
import { writeToLog } from "@/utils/log";
|
import { writeToLog } from "@/utils/log";
|
||||||
import {
|
import {
|
||||||
formatTimeString,
|
formatTimeString,
|
||||||
|
msToTicks,
|
||||||
secondsToMs,
|
secondsToMs,
|
||||||
ticksToMs,
|
ticksToMs,
|
||||||
ticksToSeconds,
|
ticksToSeconds,
|
||||||
@@ -234,8 +235,16 @@ export const Controls: React.FC<Props> = ({
|
|||||||
[isVlc]
|
[isVlc]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [time, setTime] = useState({ minutes: 0, seconds: 0 });
|
||||||
|
|
||||||
const handleSliderChange = (value: number) => {
|
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(() => {
|
const handleSliderStart = useCallback(() => {
|
||||||
@@ -351,7 +360,6 @@ export const Controls: React.FC<Props> = ({
|
|||||||
const uniqueExternalSubs = externalSubs.filter(
|
const uniqueExternalSubs = externalSubs.filter(
|
||||||
(sub) => !embeddedSubNames.has(sub.name)
|
(sub) => !embeddedSubNames.has(sub.name)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Combine embedded and unique external subs
|
// Combine embedded and unique external subs
|
||||||
return [...embeddedSubs, ...uniqueExternalSubs] as (
|
return [...embeddedSubs, ...uniqueExternalSubs] as (
|
||||||
| EmbeddedSubtitle
|
| EmbeddedSubtitle
|
||||||
@@ -359,6 +367,11 @@ export const Controls: React.FC<Props> = ({
|
|||||||
)[];
|
)[];
|
||||||
}, [item, isVideoLoaded, subtitleTracks, mediaSource]);
|
}, [item, isVideoLoaded, subtitleTracks, mediaSource]);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
|
||||||
|
// }, [allSubtitleTracks, setSubtitleTrack]);
|
||||||
|
const [subtitleTrackSet, setSubtitleTrackSet] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
@@ -715,6 +728,21 @@ export const Controls: React.FC<Props> = ({
|
|||||||
source={{ uri: url }}
|
source={{ uri: url }}
|
||||||
contentFit="cover"
|
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>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// hooks/useTrickplay.ts
|
// hooks/useTrickplay.ts
|
||||||
|
|
||||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||||
|
import { ticksToMs } from "@/utils/time";
|
||||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { useCallback, useMemo, useRef, useState } from "react";
|
import { useCallback, useMemo, useRef, useState } from "react";
|
||||||
@@ -57,6 +58,7 @@ export const useTrickplay = (item: BaseItemDto, enabled = true) => {
|
|||||||
: null;
|
: null;
|
||||||
}, [item, enabled]);
|
}, [item, enabled]);
|
||||||
|
|
||||||
|
// Takes in ticks.
|
||||||
const calculateTrickplayUrl = useCallback(
|
const calculateTrickplayUrl = useCallback(
|
||||||
(progress: number) => {
|
(progress: number) => {
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
@@ -74,28 +76,33 @@ export const useTrickplay = (item: BaseItemDto, enabled = true) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { data, resolution } = trickplayInfo;
|
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");
|
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 tileSize = TileWidth * TileHeight;
|
||||||
const rows = TileHeight;
|
const tileOffset = currentTile % tileSize;
|
||||||
const imagesPerTile = cols * rows;
|
const index = Math.floor(currentTile / tileSize);
|
||||||
const imageIndex = Math.floor(currentSecond / (Interval / 1000));
|
|
||||||
const tileIndex = Math.floor(imageIndex / imagesPerTile);
|
|
||||||
|
|
||||||
const positionInTile = imageIndex % imagesPerTile;
|
const tileOffsetX = tileOffset % TileWidth;
|
||||||
const rowInTile = Math.floor(positionInTile / cols);
|
const tileOffsetY = Math.floor(tileOffset / TileWidth);
|
||||||
const colInTile = positionInTile % cols;
|
|
||||||
|
|
||||||
const newTrickPlayUrl = {
|
const newTrickPlayUrl = {
|
||||||
x: rowInTile,
|
x: tileOffsetX,
|
||||||
y: colInTile,
|
y: tileOffsetY,
|
||||||
url: `${api.basePath}/Videos/${item.Id}/Trickplay/${resolution}/${tileIndex}.jpg?api_key=${api.accessToken}`,
|
url: `${api.basePath}/Videos/${item.Id}/Trickplay/${resolution}/${index}.jpg?api_key=${api.accessToken}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
setTrickPlayUrl(newTrickPlayUrl);
|
setTrickPlayUrl(newTrickPlayUrl);
|
||||||
|
|||||||
@@ -16,17 +16,17 @@ public class VlcPlayerModule: Module {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prop("muted") { (view: VlcPlayerView, muted: Bool) in
|
// Prop("muted") { (view: VlcPlayerView, muted: Bool) in
|
||||||
// view.setMuted(muted)
|
// view.setMuted(muted)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// Prop("volume") { (view: VlcPlayerView, volume: Int) in
|
// Prop("volume") { (view: VlcPlayerView, volume: Int) in
|
||||||
// view.setVolume(volume)
|
// view.setVolume(volume)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// Prop("videoAspectRatio") { (view: VlcPlayerView, ratio: String) in
|
// Prop("videoAspectRatio") { (view: VlcPlayerView, ratio: String) in
|
||||||
// view.setVideoAspectRatio(ratio)
|
// view.setVideoAspectRatio(ratio)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
Events(
|
Events(
|
||||||
"onPlaybackStateChanged",
|
"onPlaybackStateChanged",
|
||||||
@@ -69,13 +69,13 @@ public class VlcPlayerModule: Module {
|
|||||||
return view.getSubtitleTracks()
|
return view.getSubtitleTracks()
|
||||||
}
|
}
|
||||||
|
|
||||||
// AsyncFunction("setVideoCropGeometry") { (view: VlcPlayerView, geometry: String?) in
|
// AsyncFunction("setVideoCropGeometry") { (view: VlcPlayerView, geometry: String?) in
|
||||||
// view.setVideoCropGeometry(geometry)
|
// view.setVideoCropGeometry(geometry)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// AsyncFunction("getVideoCropGeometry") { (view: VlcPlayerView) -> String? in
|
// AsyncFunction("getVideoCropGeometry") { (view: VlcPlayerView) -> String? in
|
||||||
// return view.getVideoCropGeometry()
|
// return view.getVideoCropGeometry()
|
||||||
// }
|
// }
|
||||||
|
|
||||||
AsyncFunction("setSubtitleURL") {
|
AsyncFunction("setSubtitleURL") {
|
||||||
(view: VlcPlayerView, url: String, name: String) in
|
(view: VlcPlayerView, url: String, name: String) in
|
||||||
|
|||||||
Reference in New Issue
Block a user