mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-01-15 15:48:05 +00: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 }) => {
|
export const MediaToggles: React.FC<Props> = ({ ...props }) => {
|
||||||
const [settings, updateSettings] = useSettings();
|
const [settings, updateSettings] = useSettings();
|
||||||
|
|
||||||
|
if (!settings) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
<Text className="text-lg font-bold mb-2">Media</Text>
|
<Text className="text-lg font-bold mb-2">Media</Text>
|
||||||
@@ -119,6 +121,82 @@ export const MediaToggles: React.FC<Props> = ({ ...props }) => {
|
|||||||
</DropdownMenu.Content>
|
</DropdownMenu.Content>
|
||||||
</DropdownMenu.Root>
|
</DropdownMenu.Root>
|
||||||
</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">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>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export const useTrickplay = (
|
|||||||
const [api] = useAtom(apiAtom);
|
const [api] = useAtom(apiAtom);
|
||||||
const [trickPlayUrl, setTrickPlayUrl] = useState<TrickplayUrl | null>(null);
|
const [trickPlayUrl, setTrickPlayUrl] = useState<TrickplayUrl | null>(null);
|
||||||
const lastCalculationTime = useRef(0);
|
const lastCalculationTime = useRef(0);
|
||||||
const throttleDelay = 100; // 200ms throttle
|
const throttleDelay = 200; // 200ms throttle
|
||||||
|
|
||||||
const trickplayInfo = useMemo(() => {
|
const trickplayInfo = useMemo(() => {
|
||||||
if (!currentlyPlaying?.item.Id || !currentlyPlaying?.item.Trickplay) {
|
if (!currentlyPlaying?.item.Id || !currentlyPlaying?.item.Trickplay) {
|
||||||
@@ -62,7 +62,7 @@ export const useTrickplay = (
|
|||||||
}, [currentlyPlaying]);
|
}, [currentlyPlaying]);
|
||||||
|
|
||||||
const calculateTrickplayUrl = useCallback(
|
const calculateTrickplayUrl = useCallback(
|
||||||
(progress: SharedValue<number>) => {
|
(progress: number) => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (now - lastCalculationTime.current < throttleDelay) {
|
if (now - lastCalculationTime.current < throttleDelay) {
|
||||||
return null;
|
return null;
|
||||||
@@ -80,7 +80,7 @@ export const useTrickplay = (
|
|||||||
throw new Error("Invalid trickplay data");
|
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 cols = TileWidth;
|
||||||
const rows = TileHeight;
|
const rows = TileHeight;
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ type Settings = {
|
|||||||
defaultAudioLanguage: DefaultLanguageOption | null;
|
defaultAudioLanguage: DefaultLanguageOption | null;
|
||||||
showHomeTitles: boolean;
|
showHomeTitles: boolean;
|
||||||
defaultVideoOrientation: ScreenOrientation.OrientationLock;
|
defaultVideoOrientation: ScreenOrientation.OrientationLock;
|
||||||
|
forwardSkipTime: number;
|
||||||
|
rewindSkipTime: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -103,6 +105,8 @@ const loadSettings = async (): Promise<Settings> => {
|
|||||||
defaultSubtitleLanguage: null,
|
defaultSubtitleLanguage: null,
|
||||||
showHomeTitles: true,
|
showHomeTitles: true,
|
||||||
defaultVideoOrientation: ScreenOrientation.OrientationLock.DEFAULT,
|
defaultVideoOrientation: ScreenOrientation.OrientationLock.DEFAULT,
|
||||||
|
forwardSkipTime: 30,
|
||||||
|
rewindSkipTime: 10,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// seconds to ticks util
|
// seconds to ticks util
|
||||||
|
|
||||||
export function secondsToTicks(seconds: number): number {
|
export function secondsToTicks(seconds: number): number {
|
||||||
"worklet";
|
|
||||||
return seconds * 10000000;
|
return seconds * 10000000;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* @returns A string formatted as "Xh Ym" where X is hours and Y is minutes.
|
* @returns A string formatted as "Xh Ym" where X is hours and Y is minutes.
|
||||||
*/
|
*/
|
||||||
export const runtimeTicksToMinutes = (
|
export const runtimeTicksToMinutes = (
|
||||||
ticks: number | null | undefined,
|
ticks: number | null | undefined
|
||||||
): string => {
|
): string => {
|
||||||
if (!ticks) return "0h 0m";
|
if (!ticks) return "0h 0m";
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ export const runtimeTicksToMinutes = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const runtimeTicksToSeconds = (
|
export const runtimeTicksToSeconds = (
|
||||||
ticks: number | null | undefined,
|
ticks: number | null | undefined
|
||||||
): string => {
|
): string => {
|
||||||
if (!ticks) return "0h 0m";
|
if (!ticks) return "0h 0m";
|
||||||
|
|
||||||
@@ -34,3 +34,37 @@ export const runtimeTicksToSeconds = (
|
|||||||
if (hours > 0) return `${hours}h ${minutes}m ${seconds}s`;
|
if (hours > 0) return `${hours}h ${minutes}m ${seconds}s`;
|
||||||
else return `${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