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)
|
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)
|
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)
|
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)
|
18. Circle Video Messages - `PARTIAL` (send/play present, recording UX basic)
|
||||||
19. Stickers - `PARTIAL` (web sticker picker with preset pack + favorites)
|
19. Stickers - `PARTIAL` (web sticker picker with preset pack + favorites)
|
||||||
20. GIF - `PARTIAL` (web GIF picker with Tenor search + preset fallback + favorites)
|
20. GIF - `PARTIAL` (web GIF picker with Tenor search + preset fallback + favorites)
|
||||||
|
|||||||
@@ -64,10 +64,10 @@ function pickSupportedAudioMimeType(): string | undefined {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const candidates = [
|
const candidates = [
|
||||||
|
"audio/ogg;codecs=opus",
|
||||||
|
"audio/mp4",
|
||||||
"audio/webm;codecs=opus",
|
"audio/webm;codecs=opus",
|
||||||
"audio/webm",
|
"audio/webm",
|
||||||
"audio/mp4",
|
|
||||||
"audio/ogg;codecs=opus",
|
|
||||||
];
|
];
|
||||||
for (const mime of candidates) {
|
for (const mime of candidates) {
|
||||||
if (MediaRecorder.isTypeSupported(mime)) {
|
if (MediaRecorder.isTypeSupported(mime)) {
|
||||||
|
|||||||
@@ -47,6 +47,23 @@ function ensureAudio(): HTMLAudioElement {
|
|||||||
return globalAudioEl;
|
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) => ({
|
export const useAudioPlayerStore = create<AudioPlayerState>((set, get) => ({
|
||||||
track: null,
|
track: null,
|
||||||
audioEl: ensureAudio(),
|
audioEl: ensureAudio(),
|
||||||
@@ -59,10 +76,13 @@ export const useAudioPlayerStore = create<AudioPlayerState>((set, get) => ({
|
|||||||
const audio = ensureAudio();
|
const audio = ensureAudio();
|
||||||
if (!isBound) {
|
if (!isBound) {
|
||||||
audio.addEventListener("timeupdate", () => {
|
audio.addEventListener("timeupdate", () => {
|
||||||
set({ position: audio.currentTime || 0 });
|
set({ position: audio.currentTime || 0, duration: getPlayableDuration(audio) });
|
||||||
});
|
});
|
||||||
audio.addEventListener("loadedmetadata", () => {
|
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("play", () => set({ isPlaying: true }));
|
||||||
audio.addEventListener("pause", () => set({ isPlaying: false }));
|
audio.addEventListener("pause", () => set({ isPlaying: false }));
|
||||||
|
|||||||
Reference in New Issue
Block a user