fix
This commit is contained in:
parent
de70929a62
commit
20b9b17493
@ -1,72 +1,72 @@
|
|||||||
// useAudioRecorder.ts
|
import { useState, useCallback, useEffect } from "react";
|
||||||
import { useState, useCallback, useEffect } from 'react'
|
import vmsg from "vmsg";
|
||||||
import vmsg from 'vmsg'
|
|
||||||
|
|
||||||
// Initialize the recorder once
|
|
||||||
const recorder = new vmsg.Recorder({
|
const recorder = new vmsg.Recorder({
|
||||||
wasmURL: "https://unpkg.com/vmsg@0.3.0/vmsg.wasm"
|
wasmURL: "https://unpkg.com/vmsg@0.3.0/vmsg.wasm"
|
||||||
})
|
});
|
||||||
|
|
||||||
export function useAudioRecorder() {
|
export function useAudioRecorder() {
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [isRecording, setIsRecording] = useState(false)
|
const [isRecording, setIsRecording] = useState(false);
|
||||||
const [recordings, setRecordings] = useState<string[]>([])
|
const [recordings, setRecordings] = useState<string[]>([]);
|
||||||
const [currentRecording, setCurrentRecording] = useState<string | null>(null)
|
const [currentRecording, setCurrentRecording] = useState<string | null>(null);
|
||||||
|
|
||||||
const startRecording = useCallback(async () => {
|
const startRecording = useCallback(async () => {
|
||||||
setIsLoading(true)
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
await recorder.initAudio()
|
// Nécessaire sur mobile : initAudio DOIT être dans un handler utilisateur (tap/click)
|
||||||
await recorder.initWorker()
|
await recorder.initAudio();
|
||||||
recorder.startRecording()
|
await recorder.initWorker();
|
||||||
setIsRecording(true)
|
await recorder.startRecording();
|
||||||
} catch (e) {
|
setIsRecording(true);
|
||||||
console.error('Failed to start recording:', e)
|
} catch (error) {
|
||||||
|
console.error("Erreur lors du démarrage de l'enregistrement :", error);
|
||||||
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false)
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const stopRecording = useCallback(async () => {
|
const stopRecording = useCallback(async () => {
|
||||||
if (!isRecording) return
|
if (!isRecording) return;
|
||||||
|
|
||||||
setIsLoading(true)
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const blob = await recorder.stopRecording()
|
const blob = await recorder.stopRecording();
|
||||||
const url = URL.createObjectURL(blob)
|
const url = URL.createObjectURL(blob);
|
||||||
setRecordings(prev => [...prev, url])
|
|
||||||
setCurrentRecording(url)
|
setRecordings(prev => [...prev, url]);
|
||||||
return { blob, url }
|
setCurrentRecording(url);
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to stop recording:', e)
|
return { blob, url };
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de l'arrêt de l'enregistrement :", error);
|
||||||
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
setIsRecording(false)
|
setIsRecording(false);
|
||||||
setIsLoading(false)
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
}, [isRecording])
|
}, [isRecording]);
|
||||||
|
|
||||||
const toggleRecording = useCallback(async () => {
|
const toggleRecording = useCallback(async () => {
|
||||||
if (isRecording) {
|
if (isRecording) {
|
||||||
return await stopRecording()
|
return await stopRecording();
|
||||||
} else {
|
} else {
|
||||||
await startRecording()
|
return await startRecording();
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
}, [isRecording, startRecording, stopRecording])
|
}, [isRecording, startRecording, stopRecording]);
|
||||||
|
|
||||||
const clearRecordings = useCallback(() => {
|
const clearRecordings = useCallback(() => {
|
||||||
// Revoke object URLs to prevent memory leaks
|
recordings.forEach(url => URL.revokeObjectURL(url));
|
||||||
recordings.forEach(url => URL.revokeObjectURL(url))
|
setRecordings([]);
|
||||||
setRecordings([])
|
setCurrentRecording(null);
|
||||||
setCurrentRecording(null)
|
}, [recordings]);
|
||||||
}, [recordings])
|
|
||||||
|
|
||||||
// Clean up object URLs when component unmounts
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
recordings.forEach(url => URL.revokeObjectURL(url))
|
recordings.forEach(url => URL.revokeObjectURL(url));
|
||||||
}
|
};
|
||||||
}, [recordings])
|
}, [recordings]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isLoading,
|
isLoading,
|
||||||
@ -77,5 +77,5 @@ export function useAudioRecorder() {
|
|||||||
stopRecording,
|
stopRecording,
|
||||||
toggleRecording,
|
toggleRecording,
|
||||||
clearRecordings
|
clearRecordings
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
22
recorder/package-lock.json
generated
Normal file
22
recorder/package-lock.json
generated
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "recorder",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "recorder",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"vmsg": "^0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vmsg": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vmsg/-/vmsg-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-46BBqRSfqdFGUpO2j+Hpz8T9YE5uWG0/PWal1PT+R1o8NEthtjG/XWl4HzbB8hIHpg/UtmKvsxL2OKQBrIYcHQ==",
|
||||||
|
"license": "CC0-1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
recorder/package.json
Normal file
15
recorder/package.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "recorder",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"dependencies": {
|
||||||
|
"vmsg": "^0.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
302
recorder/recorder.html
Normal file
302
recorder/recorder.html
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Enregistreur Audio</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
color: #e67e22;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recorder-controls {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #e67e22;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: #d35400;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
background-color: #ccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recordings {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recording-item {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recording-info {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
text-align: center;
|
||||||
|
margin: 10px 0;
|
||||||
|
font-style: italic;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pulse {
|
||||||
|
animation: pulse 1.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mic-icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<!-- Charger le script vmsg avant notre script principal -->
|
||||||
|
<script src="https://unpkg.com/vmsg@0.3.0/vmsg.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>Enregistreur Audio</h1>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<p>Enregistrez votre voix et écoutez le résultat. Les enregistrements sont stockés localement dans votre navigateur.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div id="status" class="status">Prêt à enregistrer</div>
|
||||||
|
|
||||||
|
<div class="recorder-controls">
|
||||||
|
<button id="recordButton">
|
||||||
|
<svg class="mic-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path>
|
||||||
|
<path d="M19 10v2a7 7 0 0 1-14 0v-2"></path>
|
||||||
|
<line x1="12" y1="19" x2="12" y2="23"></line>
|
||||||
|
<line x1="8" y1="23" x2="16" y2="23"></line>
|
||||||
|
</svg>
|
||||||
|
Commencer l'enregistrement
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="recordings" class="recordings">
|
||||||
|
<h3>Vos enregistrements</h3>
|
||||||
|
<div id="recordingsList"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Attendre que vmsg soit complètement chargé
|
||||||
|
window.onload = function () {
|
||||||
|
// Vérifier si vmsg est disponible
|
||||||
|
if (typeof vmsg === 'undefined') {
|
||||||
|
console.error("La bibliothèque vmsg n'a pas été chargée correctement");
|
||||||
|
document.getElementById('status').textContent = "Erreur: Impossible de charger l'enregistreur audio";
|
||||||
|
document.getElementById('recordButton').disabled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialiser le recorder
|
||||||
|
const recorder = new vmsg.Recorder({
|
||||||
|
wasmURL: "https://unpkg.com/vmsg@0.3.0/vmsg.wasm"
|
||||||
|
});
|
||||||
|
|
||||||
|
const recordButton = document.getElementById('recordButton');
|
||||||
|
const statusElement = document.getElementById('status');
|
||||||
|
const recordingsList = document.getElementById('recordingsList');
|
||||||
|
|
||||||
|
let isLoading = false;
|
||||||
|
let isRecording = false;
|
||||||
|
let recordings = [];
|
||||||
|
|
||||||
|
// Fonction pour mettre à jour l'interface utilisateur
|
||||||
|
function updateUI() {
|
||||||
|
if (isLoading) {
|
||||||
|
recordButton.disabled = true;
|
||||||
|
statusElement.textContent = isRecording
|
||||||
|
? "Arrêt de l'enregistrement..."
|
||||||
|
: "Initialisation de l'enregistrement...";
|
||||||
|
} else if (isRecording) {
|
||||||
|
recordButton.innerHTML = `
|
||||||
|
<svg class="mic-icon pulse" viewBox="0 0 24 24" fill="none" stroke="red" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path>
|
||||||
|
<path d="M19 10v2a7 7 0 0 1-14 0v-2"></path>
|
||||||
|
<line x1="12" y1="19" x2="12" y2="23"></line>
|
||||||
|
<line x1="8" y1="23" x2="16" y2="23"></line>
|
||||||
|
</svg>
|
||||||
|
Arrêter l'enregistrement
|
||||||
|
`;
|
||||||
|
recordButton.style.backgroundColor = '#e74c3c';
|
||||||
|
statusElement.textContent = "Enregistrement en cours...";
|
||||||
|
} else {
|
||||||
|
recordButton.disabled = false;
|
||||||
|
recordButton.innerHTML = `
|
||||||
|
<svg class="mic-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path>
|
||||||
|
<path d="M19 10v2a7 7 0 0 1-14 0v-2"></path>
|
||||||
|
<line x1="12" y1="19" x2="12" y2="23"></line>
|
||||||
|
<line x1="8" y1="23" x2="16" y2="23"></line>
|
||||||
|
</svg>
|
||||||
|
Commencer l'enregistrement
|
||||||
|
`;
|
||||||
|
recordButton.style.backgroundColor = '#e67e22';
|
||||||
|
statusElement.textContent = "Prêt à enregistrer";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction pour gérer l'enregistrement
|
||||||
|
async function toggleRecording() {
|
||||||
|
isLoading = true;
|
||||||
|
updateUI();
|
||||||
|
|
||||||
|
if (isRecording) {
|
||||||
|
try {
|
||||||
|
const blob = await recorder.stopRecording();
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const timestamp = new Date().toLocaleString();
|
||||||
|
const size = (blob.size / 1024).toFixed(2);
|
||||||
|
|
||||||
|
recordings.push({ url, timestamp, size });
|
||||||
|
renderRecordings();
|
||||||
|
|
||||||
|
isRecording = false;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Erreur lors de l'arrêt de l'enregistrement:", e);
|
||||||
|
alert("Une erreur est survenue lors de l'arrêt de l'enregistrement.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
await recorder.initAudio();
|
||||||
|
await recorder.initWorker();
|
||||||
|
recorder.startRecording();
|
||||||
|
isRecording = true;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Erreur lors du démarrage de l'enregistrement:", e);
|
||||||
|
alert("Impossible d'accéder au microphone. Vérifiez les permissions.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading = false;
|
||||||
|
updateUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction pour afficher les enregistrements
|
||||||
|
function renderRecordings() {
|
||||||
|
recordingsList.innerHTML = '';
|
||||||
|
|
||||||
|
if (recordings.length === 0) {
|
||||||
|
recordingsList.innerHTML = '<p>Aucun enregistrement pour le moment.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
recordings.forEach((recording, index) => {
|
||||||
|
const recordingItem = document.createElement('div');
|
||||||
|
recordingItem.className = 'recording-item';
|
||||||
|
|
||||||
|
recordingItem.innerHTML = `
|
||||||
|
<div class="recording-info">
|
||||||
|
<strong>Enregistrement #${index + 1}</strong> - ${recording.timestamp} (${recording.size} KB)
|
||||||
|
</div>
|
||||||
|
<audio controls src="${recording.url}"></audio>
|
||||||
|
<div style="margin-top: 10px;">
|
||||||
|
<button class="download-btn" data-index="${index}" style="background-color: #3498db;">
|
||||||
|
Télécharger
|
||||||
|
</button>
|
||||||
|
<button class="delete-btn" data-index="${index}" style="background-color: #e74c3c; margin-left: 10px;">
|
||||||
|
Supprimer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
recordingsList.appendChild(recordingItem);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ajouter les écouteurs d'événements pour les boutons
|
||||||
|
document.querySelectorAll('.download-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', function () {
|
||||||
|
const index = parseInt(this.dataset.index);
|
||||||
|
const recording = recordings[index];
|
||||||
|
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = recording.url;
|
||||||
|
a.download = `enregistrement-${index + 1}.mp3`;
|
||||||
|
a.click();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.delete-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', function () {
|
||||||
|
const index = parseInt(this.dataset.index);
|
||||||
|
URL.revokeObjectURL(recordings[index].url);
|
||||||
|
recordings.splice(index, 1);
|
||||||
|
renderRecordings();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialiser l'interface
|
||||||
|
renderRecordings();
|
||||||
|
updateUI();
|
||||||
|
|
||||||
|
// Ajouter l'écouteur d'événement pour le bouton d'enregistrement
|
||||||
|
recordButton.addEventListener('click', toggleRecording);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user