order ticket
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
import { useState, useEffect, useRef, useCallback } from "react";
|
||||
|
||||
type UseVoiceRecorderOptions = {
|
||||
onRecordingComplete?: (audioBlob: Blob) => void;
|
||||
};
|
||||
|
||||
export const useVoiceRecorder = (options?: UseVoiceRecorderOptions) => {
|
||||
const { onRecordingComplete } = options || {};
|
||||
|
||||
const [isRecording, setIsRecording] = useState(false);
|
||||
const [audioUrl, setAudioUrl] = useState<string | null>(null);
|
||||
const [audioFile, setAudioFile] = useState<File | null>(null);
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [currentTime, setCurrentTime] = useState(0);
|
||||
const [duration, setDuration] = useState(0);
|
||||
|
||||
const mediaRecorderRef = useRef<MediaRecorder | null>(null);
|
||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||
const chunksRef = useRef<Blob[]>([]);
|
||||
const animationFrameRef = useRef<number | null>(null);
|
||||
|
||||
const startRecording = useCallback(async () => {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
const mediaRecorder = new MediaRecorder(stream);
|
||||
|
||||
mediaRecorderRef.current = mediaRecorder;
|
||||
chunksRef.current = [];
|
||||
|
||||
mediaRecorder.ondataavailable = (event) => {
|
||||
if (event.data.size > 0) {
|
||||
chunksRef.current.push(event.data);
|
||||
}
|
||||
};
|
||||
|
||||
mediaRecorder.onstop = () => {
|
||||
const audioBlob = new Blob(chunksRef.current, { type: "audio/webm" });
|
||||
const file = new File([audioBlob], "voice-message.webm", {
|
||||
type: "audio/webm",
|
||||
});
|
||||
const url = URL.createObjectURL(file);
|
||||
|
||||
setAudioUrl(url);
|
||||
setAudioFile(file);
|
||||
onRecordingComplete?.(audioBlob);
|
||||
|
||||
stream.getTracks().forEach((track) => track.stop());
|
||||
};
|
||||
|
||||
mediaRecorder.start();
|
||||
setIsRecording(true);
|
||||
} catch (err) {
|
||||
console.error("Microphone access error:", err);
|
||||
}
|
||||
}, [onRecordingComplete]);
|
||||
|
||||
const stopRecording = useCallback(() => {
|
||||
if (mediaRecorderRef.current && isRecording) {
|
||||
mediaRecorderRef.current.stop();
|
||||
setIsRecording(false);
|
||||
}
|
||||
}, [isRecording]);
|
||||
|
||||
const resetRecorder = useCallback(() => {
|
||||
if (mediaRecorderRef.current && isRecording) {
|
||||
mediaRecorderRef.current.stop();
|
||||
}
|
||||
|
||||
if (audioRef.current) {
|
||||
audioRef.current.pause();
|
||||
audioRef.current.currentTime = 0;
|
||||
}
|
||||
|
||||
if (audioUrl) {
|
||||
URL.revokeObjectURL(audioUrl);
|
||||
}
|
||||
|
||||
chunksRef.current = [];
|
||||
setIsRecording(false);
|
||||
setIsPlaying(false);
|
||||
setCurrentTime(0);
|
||||
setDuration(0);
|
||||
setAudioUrl(null);
|
||||
setAudioFile(null);
|
||||
}, [audioUrl, isRecording]);
|
||||
|
||||
const togglePlayPause = useCallback(() => {
|
||||
if (!audioRef.current) return;
|
||||
|
||||
if (isPlaying) {
|
||||
audioRef.current.pause();
|
||||
} else {
|
||||
audioRef.current.play();
|
||||
}
|
||||
setIsPlaying((prev) => !prev);
|
||||
}, [isPlaying]);
|
||||
|
||||
const updateProgress = useCallback(() => {
|
||||
if (!audioRef.current) return;
|
||||
|
||||
setCurrentTime(audioRef.current.currentTime);
|
||||
|
||||
if (isPlaying) {
|
||||
animationFrameRef.current = requestAnimationFrame(updateProgress);
|
||||
}
|
||||
}, [isPlaying]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!audioRef.current) return;
|
||||
|
||||
const audio = audioRef.current;
|
||||
|
||||
const onLoaded = () => setDuration(audio.duration);
|
||||
const onEnded = () => {
|
||||
setIsPlaying(false);
|
||||
setCurrentTime(0);
|
||||
};
|
||||
|
||||
audio.addEventListener("loadedmetadata", onLoaded);
|
||||
audio.addEventListener("ended", onEnded);
|
||||
|
||||
return () => {
|
||||
audio.removeEventListener("loadedmetadata", onLoaded);
|
||||
audio.removeEventListener("ended", onEnded);
|
||||
};
|
||||
}, [audioUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isPlaying) updateProgress();
|
||||
|
||||
return () => {
|
||||
if (animationFrameRef.current) {
|
||||
cancelAnimationFrame(animationFrameRef.current);
|
||||
}
|
||||
};
|
||||
}, [isPlaying, updateProgress]);
|
||||
|
||||
return {
|
||||
// state
|
||||
isRecording,
|
||||
isPlaying,
|
||||
audioUrl,
|
||||
audioFile,
|
||||
currentTime,
|
||||
duration,
|
||||
|
||||
// refs
|
||||
audioRef,
|
||||
|
||||
// actions
|
||||
startRecording,
|
||||
stopRecording,
|
||||
togglePlayPause,
|
||||
resetRecorder,
|
||||
|
||||
// helpers
|
||||
progress: duration ? currentTime / duration : 0,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user