mirror of
https://github.com/Jellify-Music/App.git
synced 2026-01-22 03:00:03 -06:00
optimize player
This commit is contained in:
106
components/Player/helpers/scrubber.tsx
Normal file
106
components/Player/helpers/scrubber.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useProgress } from "react-native-track-player";
|
||||
import { ProgressMultiplier } from "../component.config";
|
||||
import { HorizontalSlider } from "@/components/Global/helpers/slider";
|
||||
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
||||
import { trigger } from "react-native-haptic-feedback";
|
||||
import { XStack, YStack } from "tamagui";
|
||||
import { useSafeAreaFrame } from "react-native-safe-area-context";
|
||||
import { usePlayerContext } from "../../../player/provider";
|
||||
import { RunTimeSeconds } from "@/components/Global/helpers/time-codes";
|
||||
|
||||
const scrubGesture = Gesture.Pan();
|
||||
|
||||
export default function Scrubber() : React.JSX.Element {
|
||||
|
||||
const {
|
||||
useSeekTo,
|
||||
} = usePlayerContext();
|
||||
|
||||
|
||||
const { width } = useSafeAreaFrame();
|
||||
|
||||
const progress = useProgress();
|
||||
|
||||
const [seeking, setSeeking] = useState<boolean>(false);
|
||||
|
||||
|
||||
|
||||
const [position, setPosition] = useState<number>(progress && progress.position ?
|
||||
Math.floor(progress.position * ProgressMultiplier)
|
||||
: 0
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!seeking)
|
||||
progress && progress.position
|
||||
? setPosition(
|
||||
Math.floor(
|
||||
progress.position * ProgressMultiplier
|
||||
)
|
||||
) : 0;
|
||||
}, [
|
||||
progress
|
||||
]);
|
||||
|
||||
return (
|
||||
<YStack>
|
||||
|
||||
<GestureDetector gesture={scrubGesture}>
|
||||
<HorizontalSlider
|
||||
value={position}
|
||||
max={
|
||||
progress && progress.duration > 0
|
||||
? progress.duration * ProgressMultiplier
|
||||
: 1
|
||||
}
|
||||
width={width / 1.1}
|
||||
props={{
|
||||
// If user swipes off of the slider we should seek to the spot
|
||||
onPressOut: (event) => {
|
||||
trigger("notificationSuccess")
|
||||
setSeeking(false);
|
||||
useSeekTo.mutate(Math.floor(position / ProgressMultiplier));
|
||||
},
|
||||
onSlideStart: (event, value) => {
|
||||
trigger("impactLight");
|
||||
setSeeking(true);
|
||||
setPosition(value)
|
||||
},
|
||||
onSlideMove: (event, value) => {
|
||||
trigger("clockTick")
|
||||
setSeeking(true);
|
||||
setPosition(value);
|
||||
},
|
||||
onSlideEnd: (event, value) => {
|
||||
trigger("notificationSuccess")
|
||||
setSeeking(false);
|
||||
setPosition(value)
|
||||
useSeekTo.mutate(Math.floor(value / ProgressMultiplier));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</GestureDetector>
|
||||
|
||||
<XStack marginHorizontal={20} marginTop={"$3"} marginBottom={"$2"}>
|
||||
<XStack flex={1} justifyContent="flex-start">
|
||||
<RunTimeSeconds>{Math.floor(position / ProgressMultiplier)}</RunTimeSeconds>
|
||||
</XStack>
|
||||
|
||||
<XStack flex={1} justifyContent="space-between">
|
||||
{ /** Track metadata can go here */}
|
||||
</XStack>
|
||||
|
||||
<XStack flex={1} justifyContent="flex-end">
|
||||
<RunTimeSeconds>
|
||||
{
|
||||
progress && progress.duration
|
||||
? Math.ceil(progress.duration)
|
||||
: 0
|
||||
}
|
||||
</RunTimeSeconds>
|
||||
</XStack>
|
||||
</XStack>
|
||||
</YStack>
|
||||
)
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
import { HorizontalSlider } from "../../../components/Global/helpers/slider";
|
||||
import { RunTimeSeconds } from "../../../components/Global/helpers/time-codes";
|
||||
import { StackParamList } from "../../../components/types";
|
||||
import { usePlayerContext } from "../../../player/provider";
|
||||
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
||||
import React, { useState, useEffect, useMemo } from "react";
|
||||
import React, { useState, useMemo } from "react";
|
||||
import { SafeAreaView, useSafeAreaFrame } from "react-native-safe-area-context";
|
||||
import { YStack, XStack, Spacer, getTokens } from "tamagui";
|
||||
import PlayPauseButton from "../helpers/buttons";
|
||||
@@ -13,15 +11,10 @@ import FavoriteButton from "../../Global/components/favorite-button";
|
||||
import BlurhashedImage from "../../Global/components/blurhashed-image";
|
||||
import TextTicker from "react-native-text-ticker";
|
||||
import { ProgressMultiplier, TextTickerConfig } from "../component.config";
|
||||
import { toUpper } from "lodash";
|
||||
import { trigger } from "react-native-haptic-feedback";
|
||||
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { useIsFocused } from "@react-navigation/native";
|
||||
import { useProgress } from "react-native-track-player";
|
||||
import { UPDATE_INTERVAL } from "../../../player/config";
|
||||
|
||||
const scrubGesture = Gesture.Pan();
|
||||
import Scrubber from "../helpers/scrubber";
|
||||
|
||||
export default function PlayerScreen({
|
||||
navigation
|
||||
@@ -42,34 +35,18 @@ export default function PlayerScreen({
|
||||
|
||||
const progress = useProgress(UPDATE_INTERVAL);
|
||||
|
||||
const [seeking, setSeeking] = useState<boolean>(false);
|
||||
|
||||
/**
|
||||
* TrackPlayer.getProgress() returns a high sig-fig number. We're going to apply
|
||||
* a multiplier so that the scrubber bar can take advantage of those extra numbers
|
||||
*/
|
||||
const [progressState, setProgressState] = useState<number>(
|
||||
progress && progress.position
|
||||
? Math.floor(progress.position * ProgressMultiplier)
|
||||
? Math.ceil(progress.position * ProgressMultiplier)
|
||||
: 0
|
||||
);
|
||||
|
||||
const freeze = !useIsFocused()
|
||||
|
||||
const { width } = useSafeAreaFrame();
|
||||
|
||||
useEffect(() => {
|
||||
if (!seeking)
|
||||
progress && progress.position
|
||||
? setProgressState(
|
||||
Math.floor(
|
||||
progress.position * ProgressMultiplier
|
||||
)
|
||||
) : 0;
|
||||
}, [
|
||||
progress
|
||||
]);
|
||||
|
||||
return (
|
||||
<SafeAreaView edges={["right", "left"]}>
|
||||
{ nowPlaying && (
|
||||
@@ -215,95 +192,7 @@ export default function PlayerScreen({
|
||||
|
||||
<XStack justifyContent="center" marginTop={"$3"}>
|
||||
{/* playback progress goes here */}
|
||||
{ useMemo(() => {
|
||||
|
||||
return (
|
||||
<GestureDetector gesture={scrubGesture}>
|
||||
<HorizontalSlider
|
||||
value={progressState}
|
||||
max={
|
||||
progress && progress.duration > 0
|
||||
? progress.duration * ProgressMultiplier
|
||||
: 1
|
||||
}
|
||||
width={width / 1.1}
|
||||
props={{
|
||||
// If user swipes off of the slider we should seek to the spot
|
||||
onPressOut: () => {
|
||||
setSeeking(false);
|
||||
|
||||
navigation.setOptions({
|
||||
gestureEnabled: true
|
||||
});
|
||||
|
||||
useSeekTo.mutate(Math.floor(progressState / ProgressMultiplier));
|
||||
},
|
||||
onSlideStart: () => {
|
||||
trigger("impactLight");
|
||||
setSeeking(true);
|
||||
|
||||
navigation.setOptions({
|
||||
gestureEnabled: false
|
||||
});
|
||||
},
|
||||
onSlideMove: (event, value) => {
|
||||
setSeeking(true);
|
||||
|
||||
navigation.setOptions({
|
||||
gestureEnabled: false
|
||||
});
|
||||
|
||||
setProgressState(value);
|
||||
},
|
||||
onSlideEnd: (event, value) => {
|
||||
setSeeking(false);
|
||||
|
||||
navigation.setOptions({
|
||||
gestureEnabled: true
|
||||
});
|
||||
|
||||
useSeekTo.mutate(Math.floor(value / ProgressMultiplier));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</GestureDetector>
|
||||
)}, [
|
||||
progressState
|
||||
]
|
||||
)}
|
||||
</XStack>
|
||||
|
||||
|
||||
<XStack marginHorizontal={20} marginTop={"$3"} marginBottom={"$2"}>
|
||||
<XStack flex={1} justifyContent="flex-start">
|
||||
{ useMemo(() => {
|
||||
return (
|
||||
<RunTimeSeconds>{Math.floor(progressState / ProgressMultiplier)}</RunTimeSeconds>
|
||||
)
|
||||
}, [
|
||||
progressState,
|
||||
])}
|
||||
</XStack>
|
||||
|
||||
<XStack flex={1} justifyContent="space-between">
|
||||
{ /** Track metadata can go here */}
|
||||
{ nowPlaying!.item.MediaSources && (
|
||||
<>
|
||||
<Text>{toUpper(nowPlaying!.item.MediaSources[0].Container ?? "")}</Text>
|
||||
<Text>{nowPlaying!.item.MediaSources[0].Bitrate?.toString() ?? ""}</Text>
|
||||
</>
|
||||
)}
|
||||
</XStack>
|
||||
|
||||
<XStack flex={1} justifyContent="flex-end">
|
||||
<RunTimeSeconds>
|
||||
{
|
||||
progress && progress.duration
|
||||
? Math.ceil(progress.duration)
|
||||
: 0
|
||||
}
|
||||
</RunTimeSeconds>
|
||||
</XStack>
|
||||
<Scrubber />
|
||||
</XStack>
|
||||
|
||||
{ useMemo(() => {
|
||||
@@ -317,11 +206,7 @@ export default function PlayerScreen({
|
||||
color={getTokens().color.amethyst.val}
|
||||
name="rewind-15"
|
||||
onPress={() => {
|
||||
|
||||
setSeeking(true);
|
||||
setProgressState(progressState - (15 * ProgressMultiplier));
|
||||
useSeekTo.mutate(progress!.position - 15);
|
||||
setSeeking(false);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -334,10 +219,7 @@ export default function PlayerScreen({
|
||||
if (progressState / ProgressMultiplier < 3)
|
||||
usePrevious.mutate()
|
||||
else {
|
||||
setSeeking(true);
|
||||
setProgressState(0);
|
||||
useSeekTo.mutate(0);
|
||||
setSeeking(false);
|
||||
}
|
||||
}}
|
||||
large
|
||||
@@ -357,10 +239,7 @@ export default function PlayerScreen({
|
||||
color={getTokens().color.amethyst.val}
|
||||
name="fast-forward-15"
|
||||
onPress={() => {
|
||||
setSeeking(true);
|
||||
setProgressState(progressState + (15 * ProgressMultiplier));
|
||||
useSeekTo.mutate(progress!.position + 15);
|
||||
setSeeking(false);
|
||||
}}
|
||||
/>
|
||||
</XStack>
|
||||
|
||||
Reference in New Issue
Block a user