Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit 77fcf5c

Browse files
committed
fix audio replay issues
1 parent 39b1f10 commit 77fcf5c

2 files changed

Lines changed: 33 additions & 17 deletions

File tree

‎src/renderer/features/recordings/components/AudioPlayer.tsx‎

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { FastForward, Pause, Play, Rewind } from "@phosphor-icons/react";
22
import { Box, Button, Flex, Text } from "@radix-ui/themes";
33
import { useCallback, useEffect, useRef, useState } from "react";
44
import { useHotkeys } from "react-hotkeys-hook";
5+
import { useRecordingStore } from "../stores/recordingStore";
56

67
interface AudioPlayerProps {
78
recordingId: string;
@@ -15,13 +16,21 @@ function formatTime(seconds: number): string {
1516
}
1617

1718
export function AudioPlayer({ recordingId, duration }: AudioPlayerProps) {
18-
const [isPlaying, setIsPlaying] = useState(false);
19+
const currentlyPlayingId = useRecordingStore(
20+
(state) => state.currentlyPlayingId,
21+
);
22+
const setCurrentlyPlaying = useRecordingStore(
23+
(state) => state.setCurrentlyPlaying,
24+
);
25+
const isPlaying = currentlyPlayingId === recordingId;
26+
1927
const [currentTime, setCurrentTime] = useState(0);
2028
const [playbackRate, setPlaybackRate] = useState(1);
2129
const [isReady, setIsReady] = useState(false);
2230
const audioRef = useRef<HTMLAudioElement | null>(null);
2331
const audioUrlRef = useRef<string | null>(null);
2432

33+
// Load audio file once when component mounts
2534
useEffect(() => {
2635
let mounted = true;
2736
setIsReady(false);
@@ -37,7 +46,6 @@ export function AudioPlayer({ recordingId, duration }: AudioPlayerProps) {
3746

3847
const audio = new Audio(url);
3948

40-
// Wait for metadata to load
4149
await new Promise<void>((resolve) => {
4250
audio.addEventListener("loadedmetadata", () => resolve(), {
4351
once: true,
@@ -48,25 +56,18 @@ export function AudioPlayer({ recordingId, duration }: AudioPlayerProps) {
4856

4957
audioRef.current = audio;
5058

59+
// Only track time and ended - don't sync play/pause state from audio element
5160
audio.addEventListener("timeupdate", () => {
5261
if (mounted) setCurrentTime(audio.currentTime);
5362
});
5463

5564
audio.addEventListener("ended", () => {
5665
if (mounted) {
57-
setIsPlaying(false);
66+
setCurrentlyPlaying(null);
5867
setCurrentTime(0);
5968
}
6069
});
6170

62-
audio.addEventListener("pause", () => {
63-
if (mounted) setIsPlaying(false);
64-
});
65-
66-
audio.addEventListener("play", () => {
67-
if (mounted) setIsPlaying(true);
68-
});
69-
7071
setIsReady(true);
7172
} catch (error) {
7273
console.error("Failed to load audio:", error);
@@ -85,17 +86,27 @@ export function AudioPlayer({ recordingId, duration }: AudioPlayerProps) {
8586
URL.revokeObjectURL(audioUrlRef.current);
8687
}
8788
};
88-
}, [recordingId]);
89+
}, [recordingId, setCurrentlyPlaying]);
8990

90-
const togglePlayPause = useCallback(() => {
91-
if (!audioRef.current) return;
91+
// Sync audio element state with global playing state
92+
useEffect(() => {
93+
const audio = audioRef.current;
94+
if (!audio) return;
9295

9396
if (isPlaying) {
94-
audioRef.current.pause();
97+
audio.play().catch((err) => {
98+
console.error("Failed to play audio:", err);
99+
setCurrentlyPlaying(null);
100+
});
95101
} else {
96-
audioRef.current.play();
102+
audio.pause();
97103
}
98-
}, [isPlaying]);
104+
}, [isPlaying, setCurrentlyPlaying]);
105+
106+
const togglePlayPause = useCallback(() => {
107+
if (!isReady) return;
108+
setCurrentlyPlaying(isPlaying ? null : recordingId);
109+
}, [isPlaying, isReady, recordingId, setCurrentlyPlaying]);
99110

100111
const cyclePlaybackRate = useCallback(() => {
101112
if (!audioRef.current) return;

‎src/renderer/features/recordings/stores/recordingStore.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ interface RecordingState {
1616
availableDevices: MediaDeviceInfo[];
1717

1818
selectedRecordingId: string | null;
19+
currentlyPlayingId: string | null;
1920

2021
mediaRecorder: MediaRecorder | null;
2122
audioChunks: Blob[];
@@ -26,6 +27,7 @@ interface RecordingState {
2627
setSelectedMicId: (deviceId: string) => void;
2728
setAvailableDevices: (devices: MediaDeviceInfo[]) => void;
2829
setSelectedRecording: (recordingId: string | null) => void;
30+
setCurrentlyPlaying: (recordingId: string | null) => void;
2931
startRecording: () => Promise<void>;
3032
stopRecording: (
3133
saveRecording: (params: {
@@ -55,6 +57,7 @@ export const useRecordingStore = create<RecordingState>((set, get) => ({
5557
selectedMicId: "",
5658
availableDevices: [],
5759
selectedRecordingId: null,
60+
currentlyPlayingId: null,
5861
mediaRecorder: null,
5962
audioChunks: [],
6063
audioContext: null,
@@ -65,6 +68,8 @@ export const useRecordingStore = create<RecordingState>((set, get) => ({
6568
setAvailableDevices: (devices) => set({ availableDevices: devices }),
6669
setSelectedRecording: (recordingId) =>
6770
set({ selectedRecordingId: recordingId }),
71+
setCurrentlyPlaying: (recordingId) =>
72+
set({ currentlyPlayingId: recordingId }),
6873

6974
startRecording: async () => {
7075
try {

0 commit comments

Comments
 (0)