Add Seeking ability

clean up some backend player handlers

scaling fixes
This commit is contained in:
Violet Caulfield
2025-01-12 08:37:44 -06:00
parent bd99e17bf6
commit 83828faa84
6 changed files with 78 additions and 44 deletions
+1 -1
View File
@@ -63,7 +63,7 @@ export default function Artist(props: ArtistProps): React.JSX.Element {
<Card
caption={album.Name}
subCaption={album.ProductionYear?.toString()}
width={width / columns}
width={(width / 1.25) / columns}
cornered
itemId={album.Id!}
onPress={() => {
+12 -5
View File
@@ -21,7 +21,7 @@ import Icon from "../Global/helpers/icon";
export default function Player({ navigation }: { navigation : NavigationHelpers<ParamListBase, BottomTabNavigationEventMap> }): React.JSX.Element {
const { apiClient } = useApiClientContext();
const { nowPlaying, progress } = usePlayerContext();
const { nowPlaying, progress, useSeekTo } = usePlayerContext();
const { width } = useSafeAreaFrame();
@@ -63,8 +63,8 @@ export default function Player({ navigation }: { navigation : NavigationHelpers<
imageStyle={{
position: "relative",
alignSelf: "center",
width: width / 1.25,
height: width / 1.25,
width: width / 1.5,
height: width / 1.5,
borderRadius: 2
}}
/>
@@ -92,12 +92,19 @@ export default function Player({ navigation }: { navigation : NavigationHelpers<
</XStack>
</XStack>
<XStack justifyContent="center" margin={15}>
<XStack justifyContent="center" marginHorizontal={20}>
{/* playback progress goes here */}
<HorizontalSlider
value={progress!.position}
max={progress!.duration}
width={width / 1.25}
width={width / 1.5}
props={{
onValueChange: (value) => {
const position = value[0];
useSeekTo.mutate(position);
}
}}
/>
</XStack>
+2 -2
View File
@@ -50,8 +50,8 @@ export function Miniplayer({ navigation }: { navigation : NavigationHelpers<Para
}
imageStyle={{
position: "relative",
width: width / 6,
height: width / 6,
width: width / 8,
height: width / 8,
borderRadius: 2,
}}
/>
+17 -30
View File
@@ -3,14 +3,15 @@ import { JellifyTrack } from "../types/JellifyTrack";
import { PlaystateApi } from "@jellyfin/sdk/lib/generated-client/api/playstate-api";
import { convertSecondsToRunTimeTicks } from "@/helpers/runtimeticks";
export async function handlePlaybackState(sessionId: string, playstateApi: PlaystateApi, track: JellifyTrack, state: State) {
export async function handlePlaybackState(sessionId: string, playstateApi: PlaystateApi, track: JellifyTrack, state: State, progress: Progress) {
switch (state) {
case (State.Playing) : {
console.debug("Report playback started")
await playstateApi.reportPlaybackStart({
playbackStartInfo: {
SessionId: sessionId,
ItemId: track.ItemId
ItemId: track.ItemId,
PositionTicks: convertSecondsToRunTimeTicks(progress.position)
}
});
break;
@@ -23,9 +24,10 @@ export async function handlePlaybackState(sessionId: string, playstateApi: Plays
await playstateApi.reportPlaybackStopped({
playbackStopInfo: {
SessionId: sessionId,
ItemId: track.ItemId
ItemId: track.ItemId,
PositionTicks: convertSecondsToRunTimeTicks(progress.position)
}
})
});
break;
}
@@ -35,28 +37,6 @@ export async function handlePlaybackState(sessionId: string, playstateApi: Plays
}
}
export async function handlePlaybackStopped(sessionId: string, playstateApi: PlaystateApi, track: JellifyTrack) {
console.debug("Stopping playback for session");
await playstateApi.reportPlaybackStopped({
playbackStopInfo: {
SessionId: sessionId,
ItemId: track.ItemId
}
})
}
export async function handlePlaybackStarted(sessionId: string, playstateApi: PlaystateApi, track: JellifyTrack) {
console.debug("Starting playback for session");
await playstateApi.reportPlaybackStart({
playbackStartInfo: {
SessionId: sessionId,
ItemId: track.ItemId
}
})
}
export async function handlePlaybackProgressUpdated(sessionId: string, playstateApi: PlaystateApi, track: JellifyTrack, progress: Progress) {
if (Math.floor(progress.duration - progress.position) === 5) {
console.debug("Track finished, scrobbling...");
@@ -66,8 +46,15 @@ export async function handlePlaybackProgressUpdated(sessionId: string, playstate
ItemId: track.ItemId,
PositionTicks: convertSecondsToRunTimeTicks(track.duration!)
}
})
}
else
return;
});
} else {
console.debug("Reporting playback position");
await playstateApi.reportPlaybackProgress({
playbackProgressInfo: {
SessionId: sessionId,
ItemId: track.ItemId,
PositionTicks: convertSecondsToRunTimeTicks(progress.position)
}
});
};
}
+42 -6
View File
@@ -8,7 +8,7 @@ import _ from "lodash";
import { buildNewQueue } from "./helpers/queue";
import { useApiClientContext } from "../components/jellyfin-api-provider";
import { getPlaystateApi } from "@jellyfin/sdk/lib/utils/api";
import { handlePlaybackProgressUpdated, handlePlaybackStarted, handlePlaybackState, handlePlaybackStopped } from "./handlers";
import { handlePlaybackProgressUpdated, handlePlaybackState } from "./handlers";
import { useSetupPlayer } from "@/player/hooks";
import { UPDATE_INTERVAL } from "./config";
import { sleep } from "@/helpers/sleep";
@@ -17,7 +17,8 @@ import { QueueMutation } from "./interfaces";
import { mapDtoToTrack } from "@/helpers/mappings";
import { QueuingType } from "@/enums/queuing-type";
import { trigger } from "react-native-haptic-feedback";
import { pause } from "react-native-track-player/lib/src/trackPlayer";
import { pause, seekTo } from "react-native-track-player/lib/src/trackPlayer";
import { convertRunTimeTicksToSeconds } from "@/helpers/runtimeticks";
interface PlayerContext {
showPlayer: boolean;
@@ -27,6 +28,7 @@ interface PlayerContext {
nowPlaying: JellifyTrack | undefined;
queue: JellifyTrack[];
useTogglePlayback: UseMutationResult<void, Error, number | undefined, unknown>;
useSeekTo: UseMutationResult<void, Error, number, unknown>;
playNewQueue: UseMutationResult<void, Error, QueueMutation, unknown>;
playbackState: State | undefined;
progress: Progress | undefined;
@@ -86,7 +88,20 @@ const PlayerContextInitializer = () => {
else
await play(index);
}
})
});
const useSeekTo = useMutation({
mutationFn: async (position: number) => {
trigger('impactLight');
await seekTo(position);
await handlePlaybackProgressUpdated(sessionId, playStateApi, nowPlaying!, {
buffered: 0,
position,
duration: convertRunTimeTicksToSeconds(nowPlaying!.duration!)
});
}
});
const playNewQueue = useMutation({
mutationFn: async (mutation: QueueMutation) => {
@@ -104,6 +119,8 @@ const PlayerContextInitializer = () => {
//#region RNTP Setup
const isPlayerReady = useSetupPlayer().isSuccess;
const { state: playbackState } = usePlaybackState();
const progress = useProgress(UPDATE_INTERVAL);
useTrackPlayerEvents([
Event.PlaybackProgressUpdated,
@@ -113,7 +130,7 @@ const PlayerContextInitializer = () => {
switch (event.type) {
case (Event.PlaybackState) : {
handlePlaybackState(sessionId, playStateApi, await TrackPlayer.getActiveTrack() as JellifyTrack, event.state);
handlePlaybackState(sessionId, playStateApi, await TrackPlayer.getActiveTrack() as JellifyTrack, event.state, progress);
break;
}
case (Event.PlaybackProgressUpdated) : {
@@ -135,8 +152,6 @@ const PlayerContextInitializer = () => {
}
})
const { state: playbackState } = usePlaybackState();
const progress = useProgress(UPDATE_INTERVAL);
useEffect(() => {
if (!showMiniplayer)
@@ -164,6 +179,7 @@ const PlayerContextInitializer = () => {
nowPlaying,
queue,
useTogglePlayback,
useSeekTo,
playNewQueue,
playbackState,
progress,
@@ -197,6 +213,24 @@ export const PlayerContext = createContext<PlayerContext>({
failureReason: null,
submittedAt: 0
},
useSeekTo: {
mutate: () => {},
mutateAsync: async () => {},
data: undefined,
error: null,
variables: undefined,
isError: false,
isIdle: true,
isPaused: false,
isPending: false,
isSuccess: false,
status: "idle",
reset: () => {},
context: {},
failureCount: 0,
failureReason: null,
submittedAt: 0
},
playNewQueue: {
mutate: () => {},
mutateAsync: async () => {},
@@ -229,6 +263,7 @@ export const PlayerProvider: ({ children }: { children: ReactNode }) => React.JS
nowPlaying,
queue,
useTogglePlayback,
useSeekTo,
playNewQueue,
playbackState,
progress
@@ -242,6 +277,7 @@ export const PlayerProvider: ({ children }: { children: ReactNode }) => React.JS
nowPlaying,
queue,
useTogglePlayback,
useSeekTo,
playNewQueue,
playbackState,
progress
+4
View File
@@ -22,4 +22,8 @@ export async function PlaybackService() {
TrackPlayer.addEventListener(Event.RemotePrevious, async () => {
await TrackPlayer.skipToPrevious();
});
TrackPlayer.addEventListener(Event.RemoteSeek, async (event) => {
await TrackPlayer.seekTo(event.position);
});
}