Skip to content

Commit 40b5a52

Browse files
committed
refactor: simplify audio state handling
1 parent 70650c7 commit 40b5a52

4 files changed

Lines changed: 27 additions & 47 deletions

File tree

apps/mobile/src/components/ui/video/PlayerAction.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,25 @@ import { ThemedBlurView } from "@/src/components/common/ThemedBlurView"
66
import { PlatformActivityIndicator } from "@/src/components/ui/loading/PlatformActivityIndicator"
77
import { PauseCuteFiIcon } from "@/src/icons/pause_cute_fi"
88
import { PlayCuteFiIcon } from "@/src/icons/play_cute_fi"
9+
import type { SimpleMediaState } from "@/src/lib/player"
910

1011
import { NativePressable } from "../pressable/NativePressable"
1112

1213
interface PlayerActionProps {
13-
state: "play" | "pause" | "loading"
14+
/**
15+
* This is the state of the media instead of the play button.
16+
*
17+
* When the media is paused, the play button should be shown.
18+
*/
19+
mediaState: SimpleMediaState
1420
onPress: () => void
1521
className?: string
1622
iconSize?: number
1723
buttonClassName?: string
1824
}
1925

2026
export function PlayerAction({
21-
state,
27+
mediaState,
2228
onPress,
2329
className = "",
2430
iconSize = 24,
@@ -39,9 +45,13 @@ export function PlayerAction({
3945
intensity={30}
4046
experimentalBlurMethod="none"
4147
/>
42-
{state === "play" && <PlayCuteFiIcon color="white" width={iconSize} height={iconSize} />}
43-
{state === "pause" && <PauseCuteFiIcon color="white" width={iconSize} height={iconSize} />}
44-
{state === "loading" && <PlatformActivityIndicator />}
48+
{mediaState === "paused" && (
49+
<PlayCuteFiIcon color="white" width={iconSize} height={iconSize} />
50+
)}
51+
{mediaState === "playing" && (
52+
<PauseCuteFiIcon color="white" width={iconSize} height={iconSize} />
53+
)}
54+
{mediaState === "loading" && <PlatformActivityIndicator />}
4555
</View>
4656
</NativePressable>
4757
)

apps/mobile/src/components/ui/video/VideoPlayer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export function VideoPlayer({
7373
{status !== "readyToPlay" && <View className="absolute inset-0">{placeholder}</View>}
7474
<PlayerAction
7575
iconSize={32}
76-
state={status === "readyToPlay" ? "play" : "loading"}
76+
mediaState={status === "readyToPlay" ? "paused" : "loading"}
7777
onPress={handlePressPlay}
7878
/>
7979
</View>

apps/mobile/src/lib/player.ts

Lines changed: 6 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,27 @@
1-
import type { AttachmentsModel } from "@follow/database/schemas/types"
21
import { atom, useAtom } from "jotai"
32
import { useCallback, useEffect } from "react"
4-
import TrackPlayer, {
5-
State,
6-
useActiveTrack,
7-
useIsPlaying,
8-
usePlaybackState,
9-
} from "react-native-track-player"
3+
import TrackPlayer, { useActiveTrack, useIsPlaying } from "react-native-track-player"
104

11-
const LOADING_SUFFIX = "_loading"
12-
13-
export function usePlayingUrl() {
14-
const activeTrack = useActiveTrack()
15-
const playerState = usePlaybackState()
16-
const isPlaying = !!activeTrack?.url && playerState.state === State.Playing
17-
const isLoading = playerState.state === State.Buffering || playerState.state === State.Loading
18-
return isPlaying ? activeTrack?.url : isLoading ? `${activeTrack?.url}${LOADING_SUFFIX}` : null
19-
}
20-
21-
export function getAttachmentState(playingUrl?: string, attachment?: AttachmentsModel) {
22-
if (!playingUrl || !attachment || !attachment.mime_type?.startsWith("audio/")) {
23-
return null
24-
}
25-
const isPlaying = attachment.url === playingUrl
26-
const isLoading = playingUrl === `${attachment.url}${LOADING_SUFFIX}`
27-
return isPlaying ? "playing" : isLoading ? "loading" : null
28-
}
5+
export type SimpleMediaState = "playing" | "paused" | "loading"
296

307
/**
318
* Learn more https://rntp.dev/docs/guides/play-button
329
*/
33-
export function usePlayButtonState(audioUrl?: string): "play" | "pause" | "loading" {
10+
export function useAudioPlayState(audioUrl?: string): SimpleMediaState {
3411
const playState = useIsPlaying()
3512
const activeTrack = useActiveTrack()
3613
const playingUrl = activeTrack?.url
3714

3815
const isCurrentTrack = !audioUrl || playingUrl === audioUrl
3916
if (!playingUrl || !isCurrentTrack) {
40-
// If no track is playing or the current track is not the one we are checking, return 'play'
41-
return "play"
17+
// By default the audio should be in "paused" state
18+
return "paused"
4219
}
4320

4421
if (playState.bufferingDuringPlay === true) {
4522
return "loading"
4623
}
47-
return playState.playing ? "pause" : "play"
24+
return playState.playing ? "playing" : "paused"
4825
}
4926

5027
class Player {

apps/mobile/src/modules/entry-list/templates/EntryNormalItem.tsx

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { ItemPressable } from "@/src/components/ui/pressable/ItemPressable"
2222
import { PlayerAction } from "@/src/components/ui/video/PlayerAction"
2323
import { useNavigation } from "@/src/lib/navigation/hooks"
2424
import { isIOS } from "@/src/lib/platform"
25-
import { player, usePlayButtonState } from "@/src/lib/player"
25+
import { player, useAudioPlayState } from "@/src/lib/player"
2626
import { toast } from "@/src/lib/toast"
2727
import { EntryDetailScreen } from "@/src/screens/(stack)/entries/[entryId]/EntryDetailScreen"
2828

@@ -181,9 +181,7 @@ const ThumbnailImage = ({ entryId }: { entryId: string }) => {
181181
const blurhash = mediaModel?.blurhash
182182

183183
const audio = entry?.attachments?.find((attachment) => attachment.mime_type?.startsWith("audio/"))
184-
const audioState = usePlayButtonState(audio?.url)
185-
const isPlaying = audioState === "pause"
186-
const isLoading = audioState === "loading"
184+
const audioState = useAudioPlayState(audio?.url)
187185

188186
const video = mediaModel?.type === "video" ? mediaModel : null
189187
const videoViewRef = useRef<null | VideoView>(null)
@@ -205,7 +203,7 @@ const ThumbnailImage = ({ entryId }: { entryId: string }) => {
205203
return
206204
}
207205
if (!audio) return
208-
if (isLoading || isPlaying) {
206+
if (audioState !== "paused") {
209207
player.pause()
210208
return
211209
}
@@ -220,7 +218,7 @@ const ThumbnailImage = ({ entryId }: { entryId: string }) => {
220218
console.error("Error playing audio:", error)
221219
toast.error("Failed to play audio")
222220
}
223-
}, [audio, entry?.title, feed?.title, image, isLoading, isPlaying, video, videoPlayer])
221+
}, [audio, audioState, entry?.title, feed?.title, image, video, videoPlayer])
224222

225223
const [imageError, setImageError] = useState(audio && !image)
226224
const handleImageError = useCallback(() => {
@@ -272,12 +270,7 @@ const ThumbnailImage = ({ entryId }: { entryId: string }) => {
272270
{/* Show feed icon if no image but audio is present */}
273271
{imageError && <FeedIcon feed={feed} size={96} />}
274272

275-
{(video || audio) && (
276-
<PlayerAction
277-
state={isPlaying ? "pause" : isLoading ? "loading" : "play"}
278-
onPress={handlePressPlay}
279-
/>
280-
)}
273+
{(video || audio) && <PlayerAction mediaState={audioState} onPress={handlePressPlay} />}
281274
</View>
282275
)
283276
}

0 commit comments

Comments
 (0)