mirror of
https://github.com/streamyfin/streamyfin.git
synced 2025-08-20 18:37:18 +02:00
feat: select skip/rewind time + refactor video player
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,8 @@ interface Props extends ViewProps {}
|
||||
export const MediaToggles: React.FC<Props> = ({ ...props }) => {
|
||||
const [settings, updateSettings] = useSettings();
|
||||
|
||||
if (!settings) return null;
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Text className="text-lg font-bold mb-2">Media</Text>
|
||||
@@ -119,6 +121,82 @@ export const MediaToggles: React.FC<Props> = ({ ...props }) => {
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</View>
|
||||
|
||||
<View
|
||||
className={`
|
||||
flex flex-row items-center space-x-2 justify-between bg-neutral-900 p-4
|
||||
`}
|
||||
>
|
||||
<View className="flex flex-col shrink">
|
||||
<Text className="font-semibold">Forward skip length</Text>
|
||||
<Text className="text-xs opacity-50">
|
||||
Choose length in seconds when skipping in video playback.
|
||||
</Text>
|
||||
</View>
|
||||
<View className="flex flex-row items-center">
|
||||
<TouchableOpacity
|
||||
onPress={() =>
|
||||
updateSettings({
|
||||
forwardSkipTime: Math.max(0, settings.forwardSkipTime - 5),
|
||||
})
|
||||
}
|
||||
className="w-8 h-8 bg-neutral-800 rounded-l-lg flex items-center justify-center"
|
||||
>
|
||||
<Text>-</Text>
|
||||
</TouchableOpacity>
|
||||
<Text className="bg-neutral-800 first-letter:px-3 py-2 flex items-center justify-center">
|
||||
{settings.forwardSkipTime}s
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
className="w-8 h-8 bg-neutral-800 rounded-r-lg flex items-center justify-center"
|
||||
onPress={() =>
|
||||
updateSettings({
|
||||
forwardSkipTime: Math.min(60, settings.forwardSkipTime + 5),
|
||||
})
|
||||
}
|
||||
>
|
||||
<Text>+</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View
|
||||
className={`
|
||||
flex flex-row items-center space-x-2 justify-between bg-neutral-900 p-4
|
||||
`}
|
||||
>
|
||||
<View className="flex flex-col shrink">
|
||||
<Text className="font-semibold">Rewind length</Text>
|
||||
<Text className="text-xs opacity-50">
|
||||
Choose length in seconds when skipping in video playback.
|
||||
</Text>
|
||||
</View>
|
||||
<View className="flex flex-row items-center">
|
||||
<TouchableOpacity
|
||||
onPress={() =>
|
||||
updateSettings({
|
||||
rewindSkipTime: Math.max(0, settings.rewindSkipTime - 5),
|
||||
})
|
||||
}
|
||||
className="w-8 h-8 bg-neutral-800 rounded-l-lg flex items-center justify-center"
|
||||
>
|
||||
<Text>-</Text>
|
||||
</TouchableOpacity>
|
||||
<Text className="bg-neutral-800 first-letter:px-3 py-2 flex items-center justify-center">
|
||||
{settings.rewindSkipTime}s
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
className="w-8 h-8 bg-neutral-800 rounded-r-lg flex items-center justify-center"
|
||||
onPress={() =>
|
||||
updateSettings({
|
||||
rewindSkipTime: Math.min(60, settings.rewindSkipTime + 5),
|
||||
})
|
||||
}
|
||||
>
|
||||
<Text>+</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -34,7 +34,7 @@ export const useTrickplay = (
|
||||
const [api] = useAtom(apiAtom);
|
||||
const [trickPlayUrl, setTrickPlayUrl] = useState<TrickplayUrl | null>(null);
|
||||
const lastCalculationTime = useRef(0);
|
||||
const throttleDelay = 100; // 200ms throttle
|
||||
const throttleDelay = 200; // 200ms throttle
|
||||
|
||||
const trickplayInfo = useMemo(() => {
|
||||
if (!currentlyPlaying?.item.Id || !currentlyPlaying?.item.Trickplay) {
|
||||
@@ -62,7 +62,7 @@ export const useTrickplay = (
|
||||
}, [currentlyPlaying]);
|
||||
|
||||
const calculateTrickplayUrl = useCallback(
|
||||
(progress: SharedValue<number>) => {
|
||||
(progress: number) => {
|
||||
const now = Date.now();
|
||||
if (now - lastCalculationTime.current < throttleDelay) {
|
||||
return null;
|
||||
@@ -80,7 +80,7 @@ export const useTrickplay = (
|
||||
throw new Error("Invalid trickplay data");
|
||||
}
|
||||
|
||||
const currentSecond = Math.max(0, Math.floor(progress.value / 10000000));
|
||||
const currentSecond = Math.max(0, Math.floor(progress / 10000000));
|
||||
|
||||
const cols = TileWidth;
|
||||
const rows = TileHeight;
|
||||
|
||||
@@ -70,6 +70,8 @@ type Settings = {
|
||||
defaultAudioLanguage: DefaultLanguageOption | null;
|
||||
showHomeTitles: boolean;
|
||||
defaultVideoOrientation: ScreenOrientation.OrientationLock;
|
||||
forwardSkipTime: number;
|
||||
rewindSkipTime: number;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -103,6 +105,8 @@ const loadSettings = async (): Promise<Settings> => {
|
||||
defaultSubtitleLanguage: null,
|
||||
showHomeTitles: true,
|
||||
defaultVideoOrientation: ScreenOrientation.OrientationLock.DEFAULT,
|
||||
forwardSkipTime: 30,
|
||||
rewindSkipTime: 10,
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// seconds to ticks util
|
||||
|
||||
export function secondsToTicks(seconds: number): number {
|
||||
"worklet";
|
||||
return seconds * 10000000;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* @returns A string formatted as "Xh Ym" where X is hours and Y is minutes.
|
||||
*/
|
||||
export const runtimeTicksToMinutes = (
|
||||
ticks: number | null | undefined,
|
||||
ticks: number | null | undefined
|
||||
): string => {
|
||||
if (!ticks) return "0h 0m";
|
||||
|
||||
@@ -20,7 +20,7 @@ export const runtimeTicksToMinutes = (
|
||||
};
|
||||
|
||||
export const runtimeTicksToSeconds = (
|
||||
ticks: number | null | undefined,
|
||||
ticks: number | null | undefined
|
||||
): string => {
|
||||
if (!ticks) return "0h 0m";
|
||||
|
||||
@@ -34,3 +34,37 @@ export const runtimeTicksToSeconds = (
|
||||
if (hours > 0) return `${hours}h ${minutes}m ${seconds}s`;
|
||||
else return `${minutes}m ${seconds}s`;
|
||||
};
|
||||
|
||||
export const formatTimeString = (
|
||||
t: number | null | undefined,
|
||||
tick = false
|
||||
): string => {
|
||||
if (t === null || t === undefined) return "0:00";
|
||||
|
||||
let seconds = t;
|
||||
if (tick) {
|
||||
seconds = Math.floor(t / 10000000); // Convert ticks to seconds
|
||||
}
|
||||
|
||||
if (seconds < 0) return "0:00";
|
||||
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
const remainingSeconds = Math.floor(seconds % 60);
|
||||
|
||||
if (hours > 0) {
|
||||
return `${hours}h ${minutes}m ${remainingSeconds}s`;
|
||||
} else {
|
||||
return `${minutes}m ${remainingSeconds}s`;
|
||||
}
|
||||
};
|
||||
|
||||
export const secondsToTicks = (seconds?: number | undefined) => {
|
||||
if (!seconds) return 0;
|
||||
return seconds * 10000000;
|
||||
};
|
||||
|
||||
export const ticksToSeconds = (ticks?: number | undefined) => {
|
||||
if (!ticks) return 0;
|
||||
return ticks / 10000000;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user