首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

HTML5 音頻捕獲實現「按住說話」功能:MediaRecorder API 完整方案

     編輯:程式設計綜合問答

核心技術棧

  • 前端:MediaRecorder API + getUserMedia + Web Audio API
  • 後端:PHP 接收 Base64/Blob 上傳,存儲文件/數據庫
  • 通訊:FormData + AJAX/Fetch 上傳

前端完整實現

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>按住說話演示</title>
    <style>
        #recordBtn { width: 120px; height: 120px; border-radius: 60px; background: #e74c3c; color: white; font-size: 18px; border: none; cursor: pointer; touch-action: none; }
        #recordBtn.recording { background: #27ae60; animation: pulse 1s infinite; }
        @keyframes pulse { 0%,100% { transform: scale(1); } 50% { transform: scale(1.05); } }
        #status { margin-top: 20px; font-family: monospace; }
        audio { display: block; margin: 10px 0; }
    </style>
</head>
<body>
    <button id="recordBtn">按住說話</button>
    <div id="status">就緒</div>
    <audio id="playback" controls></audio>

    <script>
        const btn = document.getElementById('recordBtn');
        const status = document.getElementById('status');
        const playback = document.getElementById('playback');
        let mediaRecorder, chunks = [], startTime;

        // 1. 請求麥克風權限
        async function initMicrophone() {
            try {
                const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
                mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/webm;codecs=opus' });
                
                mediaRecorder.ondataavailable = e => {
                    if (e.data.size > 0) chunks.push(e.data);
                };
                
                mediaRecorder.onstop = async () => {
                    const blob = new Blob(chunks, { type: 'audio/webm' });
                    chunks = [];
                    await uploadAudio(blob);
                    playback.src = URL.createObjectURL(blob);
                };
                updateStatus('麥克風就緒,可開始錄音');
            } catch (err) {
                updateStatus('麥克風權限被拒絕: ' + err.message);
            }
        }

        // 2. 上傳到 PHP 後端
        async function uploadAudio(blob) {
            updateStatus('上傳中...');
            const formData = new FormData();
            formData.append('audio', blob, `recording_${Date.now()}.webm`);
            
            try {
                const resp = await fetch('upload.php', { method: 'POST', body: formData });
                const result = await resp.json();
                updateStatus(result.success ? `上傳成功: ${result.path}` : '上傳失敗: ' + result.msg);
            } catch (err) {
                updateStatus('上傳異常: ' + err.message);
            }
        }

        // 3. 觸摸/鼠標事件綁定
        btn.addEventListener('mousedown', startRecord);
        btn.addEventListener('touchstart', e => { e.preventDefault(); startRecord(); });
        
        window.addEventListener('mouseup', stopRecord);
        window.addEventListener('touchend', stopRecord);
        window.addEventListener('mouseleave', stopRecord); // 鼠標移出按鈕也停止

        function startRecord() {
            if (mediaRecorder && mediaRecorder.state === 'inactive') {
                chunks = [];
                mediaRecorder.start(100); // 每 100ms 觸發 ondataavailable
                startTime = Date.now();
                btn.classList.add('recording');
                btn.textContent = '釋放結束';
                updateStatus('錄音中...');
            }
        }

        function stopRecord() {
            if (mediaRecorder && mediaRecorder.state === 'recording') {
                mediaRecorder.stop();
                btn.classList.remove('recording');
                btn.textContent = '按住說話';
                updateStatus(`錄音結束,時長 ${((Date.now()-startTime)/1000).toFixed(1)}s`);
            }
        }

        function updateStatus(msg) {
            status.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`;
        }

        initMicrophone();
    </script>
</body>
</html>

PHP 後端

<?php
// upload.php
header('Content-Type: application/json');

$uploadDir = __DIR__ . '/uploads/';
if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true);

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['audio'])) {
    $file = $_FILES['audio'];
    if ($file['error'] === UPLOAD_ERR_OK) {
        $ext = pathinfo($file['name'], PATHINFO_EXTENSION);
        $newName = 'audio_' . date('YmdHis') . '_' . bin2hex(random_bytes(4)) . '.' . $ext;
        $dest = $uploadDir . $newName;
        
        if (move_uploaded_file($file['tmp_name'], $dest)) {
            // 可選:存入數據庫
            // $pdo->prepare("INSERT INTO recordings (filename, path, created_at) VALUES (?, ?, NOW())")
            //     ->execute([$file['name'], $dest]);
            
            echo json_encode(['success' => true, 'path' => $dest, 'url' => '/uploads/' . $newName]);
        } else {
            echo json_encode(['success' => false, 'msg' => '文件移動失敗']);
        }
    } else {
        echo json_encode(['success' => false, 'msg' => '上傳錯誤碼: ' . $file['error']]);
    }
} else {
    echo json_encode(['success' => false, 'msg' => '無效請求']);
}
?>

關鍵兼容性處理

問題解決方案
iOS Safari 不支援 MediaRecorder使用 audio-recorder-polyfill (基於 ScriptProcessorNode)
Android Chrome 需 HTTPS生產環境必須部署 SSL 證書
長時間錄音內存暴漲定期 mediaRecorder.stop() + start() 分段,或用 Web Audio API 直接寫入 AudioWorklet
音頻格式轉換前端錄製 WebM/Opus,後端用 FFmpeg 轉 MP3/WAV 存儲

進階:實時波形顯示

// 使用 Web Audio API 獲取實時頻域數據
const audioCtx = new AudioContext();
const analyser = audioCtx.createAnalyser();
const source = audioCtx.createMediaStreamSource(stream);
source.connect(analyser);
analyser.fftSize = 256;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);

function drawWaveform() {
    requestAnimationFrame(drawWaveform);
    analyser.getByteFrequencyData(dataArray);
    // Canvas 繪製 dataArray 柱狀圖...
}

總結

HTML5 「按住說話」核心流程:getUserMedia 獲取流 → MediaRecorder 錄製 → Blob 上傳 → PHP 存儲。關鍵點:HTTPS 部署、iOS 兼容性、長錄音分段、格式轉碼。前端 50 行、後端 30 行即可實現微信級語音體驗。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved