fix
This commit is contained in:
parent
de70929a62
commit
20b9b17493
@ -1,72 +1,72 @@
|
||||
// useAudioRecorder.ts
|
||||
import { useState, useCallback, useEffect } from 'react'
|
||||
import vmsg from 'vmsg'
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
import vmsg from "vmsg";
|
||||
|
||||
// Initialize the recorder once
|
||||
const recorder = new vmsg.Recorder({
|
||||
wasmURL: "https://unpkg.com/vmsg@0.3.0/vmsg.wasm"
|
||||
})
|
||||
});
|
||||
|
||||
export function useAudioRecorder() {
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [isRecording, setIsRecording] = useState(false)
|
||||
const [recordings, setRecordings] = useState<string[]>([])
|
||||
const [currentRecording, setCurrentRecording] = useState<string | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isRecording, setIsRecording] = useState(false);
|
||||
const [recordings, setRecordings] = useState<string[]>([]);
|
||||
const [currentRecording, setCurrentRecording] = useState<string | null>(null);
|
||||
|
||||
const startRecording = useCallback(async () => {
|
||||
setIsLoading(true)
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await recorder.initAudio()
|
||||
await recorder.initWorker()
|
||||
recorder.startRecording()
|
||||
setIsRecording(true)
|
||||
} catch (e) {
|
||||
console.error('Failed to start recording:', e)
|
||||
// Nécessaire sur mobile : initAudio DOIT être dans un handler utilisateur (tap/click)
|
||||
await recorder.initAudio();
|
||||
await recorder.initWorker();
|
||||
await recorder.startRecording();
|
||||
setIsRecording(true);
|
||||
} catch (error) {
|
||||
console.error("Erreur lors du démarrage de l'enregistrement :", error);
|
||||
throw error;
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [])
|
||||
}, []);
|
||||
|
||||
const stopRecording = useCallback(async () => {
|
||||
if (!isRecording) return
|
||||
if (!isRecording) return;
|
||||
|
||||
setIsLoading(true)
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const blob = await recorder.stopRecording()
|
||||
const url = URL.createObjectURL(blob)
|
||||
setRecordings(prev => [...prev, url])
|
||||
setCurrentRecording(url)
|
||||
return { blob, url }
|
||||
} catch (e) {
|
||||
console.error('Failed to stop recording:', e)
|
||||
const blob = await recorder.stopRecording();
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
setRecordings(prev => [...prev, url]);
|
||||
setCurrentRecording(url);
|
||||
|
||||
return { blob, url };
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de l'arrêt de l'enregistrement :", error);
|
||||
throw error;
|
||||
} finally {
|
||||
setIsRecording(false)
|
||||
setIsLoading(false)
|
||||
setIsRecording(false);
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [isRecording])
|
||||
}, [isRecording]);
|
||||
|
||||
const toggleRecording = useCallback(async () => {
|
||||
if (isRecording) {
|
||||
return await stopRecording()
|
||||
return await stopRecording();
|
||||
} else {
|
||||
await startRecording()
|
||||
return null
|
||||
return await startRecording();
|
||||
}
|
||||
}, [isRecording, startRecording, stopRecording])
|
||||
}, [isRecording, startRecording, stopRecording]);
|
||||
|
||||
const clearRecordings = useCallback(() => {
|
||||
// Revoke object URLs to prevent memory leaks
|
||||
recordings.forEach(url => URL.revokeObjectURL(url))
|
||||
setRecordings([])
|
||||
setCurrentRecording(null)
|
||||
}, [recordings])
|
||||
recordings.forEach(url => URL.revokeObjectURL(url));
|
||||
setRecordings([]);
|
||||
setCurrentRecording(null);
|
||||
}, [recordings]);
|
||||
|
||||
// Clean up object URLs when component unmounts
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
recordings.forEach(url => URL.revokeObjectURL(url))
|
||||
}
|
||||
}, [recordings])
|
||||
recordings.forEach(url => URL.revokeObjectURL(url));
|
||||
};
|
||||
}, [recordings]);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
@ -77,5 +77,5 @@ export function useAudioRecorder() {
|
||||
stopRecording,
|
||||
toggleRecording,
|
||||
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