fix(voice): improve duration detection for new waveform playback
Some checks failed
CI / test (push) Has been cancelled
Some checks failed
CI / test (push) Has been cancelled
This commit is contained in:
@@ -23,7 +23,7 @@ Legend:
|
||||
14. Delivery Status - `DONE` (sent/delivered/read + reconnect reconciliation after backend restarts)
|
||||
15. Typing Realtime - `DONE` (typing start/stop + recording voice start/stop + recording video start/stop in circle-video send flow)
|
||||
16. Media & Attachments - `DONE` (upload/preview/download/gallery; sticker/GIF inline media no longer opens photo viewer on click)
|
||||
17. Voice Messages - `PARTIAL` (record/send/play/seek + global speed 1x/1.5x/2x; recorder uses mime fallback + chunked capture; websocket send/recorder stop race on fast chat switch is guarded; UX still being polished)
|
||||
17. Voice Messages - `PARTIAL` (record/send/play/seek + global speed 1x/1.5x/2x; recorder uses improved mime priority for better duration metadata; audio store tracks duration via `durationchange/seekable` fallback; websocket send/recorder stop race on fast chat switch is guarded; UX still being polished)
|
||||
18. Circle Video Messages - `PARTIAL` (send/play present, recording UX basic)
|
||||
19. Stickers - `PARTIAL` (web sticker picker with preset pack + favorites)
|
||||
20. GIF - `PARTIAL` (web GIF picker with Tenor search + preset fallback + favorites)
|
||||
|
||||
@@ -64,10 +64,10 @@ function pickSupportedAudioMimeType(): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
const candidates = [
|
||||
"audio/ogg;codecs=opus",
|
||||
"audio/mp4",
|
||||
"audio/webm;codecs=opus",
|
||||
"audio/webm",
|
||||
"audio/mp4",
|
||||
"audio/ogg;codecs=opus",
|
||||
];
|
||||
for (const mime of candidates) {
|
||||
if (MediaRecorder.isTypeSupported(mime)) {
|
||||
|
||||
@@ -47,6 +47,23 @@ function ensureAudio(): HTMLAudioElement {
|
||||
return globalAudioEl;
|
||||
}
|
||||
|
||||
function getPlayableDuration(audio: HTMLAudioElement): number {
|
||||
if (Number.isFinite(audio.duration) && audio.duration > 0) {
|
||||
return audio.duration;
|
||||
}
|
||||
try {
|
||||
if (audio.seekable && audio.seekable.length > 0) {
|
||||
const end = audio.seekable.end(audio.seekable.length - 1);
|
||||
if (Number.isFinite(end) && end > 0) {
|
||||
return end;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export const useAudioPlayerStore = create<AudioPlayerState>((set, get) => ({
|
||||
track: null,
|
||||
audioEl: ensureAudio(),
|
||||
@@ -59,10 +76,13 @@ export const useAudioPlayerStore = create<AudioPlayerState>((set, get) => ({
|
||||
const audio = ensureAudio();
|
||||
if (!isBound) {
|
||||
audio.addEventListener("timeupdate", () => {
|
||||
set({ position: audio.currentTime || 0 });
|
||||
set({ position: audio.currentTime || 0, duration: getPlayableDuration(audio) });
|
||||
});
|
||||
audio.addEventListener("loadedmetadata", () => {
|
||||
set({ duration: Number.isFinite(audio.duration) ? audio.duration : 0 });
|
||||
set({ duration: getPlayableDuration(audio) });
|
||||
});
|
||||
audio.addEventListener("durationchange", () => {
|
||||
set({ duration: getPlayableDuration(audio) });
|
||||
});
|
||||
audio.addEventListener("play", () => set({ isPlaying: true }));
|
||||
audio.addEventListener("pause", () => set({ isPlaying: false }));
|
||||
|
||||
Reference in New Issue
Block a user