javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > JS纯前端浏览器语音播报朗读

JS纯前端实现浏览器语音播报、朗读功能的完整代码

作者:ᥬ 小月亮

在现代互联网的发展中,语音技术正逐渐成为改变用户体验的重要一环,下面这篇文章主要介绍了JS纯前端实现浏览器语音播报、朗读功能的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

一、朗读单条文本:

① 语音自选参数,按钮控制语音:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文本转语音播报</title>
    <style>
        body {
            background: #f0f0f0;
            text-align: center;
        }

        .select {
            display: flex;
            justify-content: center;
            align-items: center;
        }
        button {
            font-size: 18px;
        }
        #status {
            margin: 20px;
            color: yellowgreen;
        }
    </style>
</head>

<body>
    <div style="color: rgb(248, 74, 103);">文本转语音播报:此功能依赖浏览器支持Web Speech API,目前主流浏览器(Chrome、Edge等)均已支持</div>
    <div>
        <h2>输入要朗读的文本:</h2>
        <textarea id="textToSpeak" placeholder="请输入需要朗读的文本内容..." rows="20" cols="100"></textarea>
    </div>
    <div class="select">
        <h3>选择语音:</h3>
        <select id="voiceSelect">
            <option value="">加载中...</option>
        </select>
    </div>
    <div class="select">
        <h3>语速:</h3>
        <input type="range" id="rate" min="0.5" max="2" step="0.1" value="1">
    </div>
    <div class="select">
        <h3>音调:</h3>
        <input type="range" id="pitch" min="0.5" max="2" step="0.1" value="1">
    </div>
    <div class="select">
        <h3>音量:</h3>
        <input type="range" id="volume" min="0" max="1" step="0.1" value="1">
    </div>
    <!-- 控制按钮区域 -->
    <div>
        <button id="startBtn">开始朗读</button>
        <button id="pauseBtn" disabled>暂停</button>
        <button id="resumeBtn" disabled>继续</button>
        <button id="cancelBtn" disabled>停止</button>
    </div>

    <!-- 状态显示区域 -->
    <div id="status"></div>

</body>
<script>
    // 检查浏览器是否支持Web Speech API
    if ('speechSynthesis' in window) {
        const synth = window.speechSynthesis;
        let voices = [];

        // DOM元素
        const textInput = document.getElementById('textToSpeak');

        const voiceSelect = document.getElementById('voiceSelect');
        const rateInput = document.getElementById('rate');
        const pitchInput = document.getElementById('pitch');
        const volumeInput = document.getElementById('volume');

        const rateValue = document.getElementById('rateValue');
        const pitchValue = document.getElementById('pitchValue');
        const volumeValue = document.getElementById('volumeValue');

        const startBtn = document.getElementById('startBtn');
        const pauseBtn = document.getElementById('pauseBtn');
        const resumeBtn = document.getElementById('resumeBtn');
        const cancelBtn = document.getElementById('cancelBtn');

        const statusDisplay = document.getElementById('status');

        // 获取可用语音列表
        function loadVoices() {
            voices = synth.getVoices();
            voiceSelect.innerHTML = '';

            // 筛选中文语音优先显示
            const chineseVoices = voices.filter(voice =>
                voice.lang.includes('zh') || voice.name.includes('Chinese')
            );
            const otherVoices = voices.filter(voice =>
                !voice.lang.includes('zh') && !voice.name.includes('Chinese')
            );

            // 添加中文语音
            chineseVoices.forEach(voice => {
                const option = document.createElement('option');
                option.textContent = `${voice.name} (${voice.lang})`;
                option.value = voice.name;
                voiceSelect.appendChild(option);
            });

            // 如果有中文语音,添加分隔线
            if (chineseVoices.length > 0 && otherVoices.length > 0) {
                const separator = document.createElement('option');
                separator.textContent = '────────── 其他语言 ──────────';
                separator.disabled = true;
                voiceSelect.appendChild(separator);
            }

            // 添加其他语音
            otherVoices.forEach(voice => {
                const option = document.createElement('option');
                option.textContent = `${voice.name} (${voice.lang})`;
                option.value = voice.name;
                voiceSelect.appendChild(option);
            });

            // 默认选择第一个中文语音
            if (chineseVoices.length > 0) {
                voiceSelect.value = chineseVoices[0].name;
            } else if (voices.length > 0) {
                voiceSelect.value = voices[0].name;
            }
        }

        // 初始化时加载语音,某些浏览器需要触发一次语音合成才能加载语音列表
        loadVoices();
        if (speechSynthesis.onvoiceschanged !== undefined) {
            speechSynthesis.onvoiceschanged = loadVoices;
        }

        // 显示当前速率、音调、音量值
        rateInput.addEventListener('input', () => {
            rateValue.textContent = rateInput.value;
        });

        pitchInput.addEventListener('input', () => {
            pitchValue.textContent = pitchInput.value;
        });

        volumeInput.addEventListener('input', () => {
            volumeValue.textContent = volumeInput.value;
        });

        // 开始朗读
        startBtn.addEventListener('click', () => {
            if (synth.speaking) {
                synth.cancel();
            }

            const text = textInput.value.trim();
            if (text) {
                statusDisplay.textContent = '正在朗读...';
                updateButtonStates(true);

                const utterThis = new SpeechSynthesisUtterance(text);

                // 设置语音
                const selectedVoice = voices.find(voice => voice.name === voiceSelect.value);
                if (selectedVoice) {
                    utterThis.voice = selectedVoice;
                }

                // 设置语音参数
                utterThis.rate = parseFloat(rateInput.value);
                utterThis.pitch = parseFloat(pitchInput.value);
                utterThis.volume = parseFloat(volumeInput.value);

                // 朗读结束时的回调
                utterThis.onend = () => {
                    statusDisplay.textContent = '朗读完成';
                    updateButtonStates(false);
                };

                // 朗读出错时的回调
                utterThis.onerror = (event) => {
                    statusDisplay.textContent = `朗读出错: ${event.error}`;
                    updateButtonStates(false);
                };

                synth.speak(utterThis);
            } else {
                statusDisplay.textContent = '请输入要朗读的文本';
            }
        });

        // 暂停朗读
        pauseBtn.addEventListener('click', () => {
            if (synth.speaking) {
                if (synth.paused) {
                    // 如果已经暂停,则继续
                    synth.resume();
                    statusDisplay.textContent = '继续朗读...';
                    pauseBtn.disabled = false;
                    resumeBtn.disabled = true;
                } else {
                    // 暂停朗读
                    synth.pause();
                    statusDisplay.textContent = '已暂停';
                    pauseBtn.disabled = true;
                    resumeBtn.disabled = false;
                }
            }
        });

        // 继续朗读
        resumeBtn.addEventListener('click', () => {
            if (synth.paused) {
                synth.resume();
                statusDisplay.textContent = '继续朗读...';
                pauseBtn.disabled = false;
                resumeBtn.disabled = true;
            }
        });

        // 停止朗读
        cancelBtn.addEventListener('click', () => {
            synth.cancel();
            statusDisplay.textContent = '已停止';
            updateButtonStates(false);
        });

        // 更新按钮状态
        function updateButtonStates(isSpeaking) {
            startBtn.disabled = isSpeaking;
            pauseBtn.disabled = !isSpeaking;
            cancelBtn.disabled = !isSpeaking;
            resumeBtn.disabled = true;
        }

    } else {
        // 浏览器不支持Web Speech API时的处理
        document.getElementById('status').textContent = '抱歉,您的浏览器不支持语音合成功能,请使用Chrome、Edge等现代浏览器。';

        // 禁用所有控制按钮
        const buttons = document.querySelectorAll('button');
        buttons.forEach(button => {
            button.disabled = true;
            button.classList.add('opacity-50', 'cursor-not-allowed');
        });

        // 禁用选择框
        document.getElementById('voiceSelect').disabled = true;
        const sliders = document.querySelectorAll('input[type="range"]');
        sliders.forEach(slider => {
            slider.disabled = true;
        });
    }
</script>
</body>

</html>

② 效果图:

二、朗读多条文本:

① 语音有默认值:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文本转语音播报(后端数据)</title>
    <style>
        body {
            background: #f0f0f0;
            text-align: center;
            padding: 20px;
            font-family: Arial, sans-serif;
        }

        .control-panel {
            margin: 20px 0;
        }

        button {
            font-size: 18px;
            padding: 8px 16px;
            margin: 0 5px;
            cursor: pointer;
            border: none;
            border-radius: 4px;
            background: #4CAF50;
            color: white;
        }

        button:disabled {
            background: #cccccc;
            cursor: not-allowed;
        }

        #status {
            margin: 20px auto;
            color: #333;
            padding: 10px;
            background: #e8f0fe;
            display: inline-block;
            min-width: 300px;
            border-radius: 4px;
        }

        .alarm-list {
            max-width: 600px;
            margin: 20px auto;
            text-align: left;
            background: white;
            padding: 15px;
            border-radius: 4px;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
        }

        .alarm-item {
            padding: 8px 0;
            border-bottom: 1px solid #eee;
        }

        .alarm-item:last-child {
            border-bottom: none;
        }

        .loading {
            color: #666;
            font-style: italic;
        }
    </style>
</head>

<body>
    <div style="color: rgb(248, 74, 103);">文本转语音播报:此功能依赖浏览器支持Web Speech API,目前主流浏览器(Chrome、Edge等)均已支持</div>

    <!-- 报警信息列表预览 -->
    <div class="alarm-list">
        <h4>待朗读报警信息:</h4>
        <div id="alarmContainer" class="loading">正在从后端获取数据...</div>
    </div>

    <!-- 控制按钮区域 -->
    <div class="control-panel">
        <button id="fetchDataBtn">获取后端数据</button>
        <button id="initVoiceBtn" disabled>初始化语音(必须先获取数据)</button>
        <button id="startBtn" disabled>开始批量朗读</button>
        <button id="pauseBtn" disabled>暂停</button>
        <button id="resumeBtn" disabled>继续</button>
        <button id="cancelBtn" disabled>停止</button>
    </div>

    <!-- 状态显示区域 -->
    <div id="status">请点击"获取后端数据"按钮开始</div>

</body>
<script>
    // 检查浏览器是否支持Web Speech API
    if ('speechSynthesis' in window) {
        const synth = window.speechSynthesis;
        let voices = [];
        let isVoiceReady = false; // 语音是否准备就绪
        let currentAlarmIndex = 0; // 当前播放的报警索引
        let alarmList = []; // 报警信息列表(后端返回的数据)
        let isPlaying = false; // 是否正在播放

        // DOM元素
        const fetchDataBtn = document.getElementById('fetchDataBtn');
        const initVoiceBtn = document.getElementById('initVoiceBtn');
        const startBtn = document.getElementById('startBtn');
        const pauseBtn = document.getElementById('pauseBtn');
        const resumeBtn = document.getElementById('resumeBtn');
        const cancelBtn = document.getElementById('cancelBtn');
        const statusDisplay = document.getElementById('status');
        const alarmContainer = document.getElementById('alarmContainer');

        // 模拟从后端获取报警信息
        fetchDataBtn.addEventListener('click', async () => {
            statusDisplay.textContent = '正在从后端获取报警信息...';
            fetchDataBtn.disabled = true;

            try {
                // 模拟API请求延迟
                const response = await simulateBackendRequest();

                if (response.success && response.data && response.data.length > 0) {
                    alarmList = response.data;
                    displayAlarmList(alarmList);

                    statusDisplay.textContent = `成功获取${alarmList.length}条报警信息,请初始化语音`;
                    initVoiceBtn.disabled = false; // 启用初始化语音按钮
                } else {
                    statusDisplay.textContent = '后端未返回任何报警信息';
                    fetchDataBtn.disabled = false;
                }
            } catch (err) {
                statusDisplay.textContent = `获取数据失败:${err.message}`;
                fetchDataBtn.disabled = false;
            }
        });

        // 模拟后端请求
        function simulateBackendRequest() {
            return new Promise((resolve) => {
                // 模拟网络延迟
                setTimeout(() => {
                    // 模拟后端返回的数据
                    const mockData = {
                        success: true,
                        data: [
                            {
                                "id": 1,
                                "title": "空调异常警报",
                                "text": "25楼北区空调参数异常:总有功功率实际值5.79kW,异常状态累计15分0秒",
                                "type": 1,
                                "typeText": "轻微",
                                "timestamp": "2023-10-15 09:23:45"
                            },
                            {
                                "id": 2,
                                "title": "配电柜温度过高",
                                "text": "B栋配电室3号柜温度超标:当前温度62°C(阈值55°C),持续8分30秒",
                                "type": 2,
                                "typeText": "严重",
                                "timestamp": "2023-10-15 10:15:22"
                            },
                            {
                                "id": 3,
                                "title": "水泵压力异常",
                                "text": "地下二层供水系统压力异常:当前压力0.25MPa(正常范围0.3-0.5MPa),持续12分钟",
                                "type": 1,
                                "typeText": "轻微",
                                "timestamp": "2023-10-15 11:07:18"
                            },
                            {
                                "id": 4,
                                "title": "消防水箱水位不足",
                                "text": "1号楼消防水箱水位过低:当前水位1.2m(最低警戒线1.5m),请立即处理",
                                "type": 3,
                                "typeText": "紧急",
                                "timestamp": "2023-10-15 13:45:03"
                            }
                        ]
                    };
                    resolve(mockData);
                }, 1500); // 1.5秒延迟,模拟真实网络请求
            });
        }

        // 在页面上显示报警信息列表
        function displayAlarmList(alarms) {
            alarmContainer.innerHTML = '';
            alarms.forEach((alarm, index) => {
                const item = document.createElement('div');
                item.className = 'alarm-item';
                item.innerHTML = `${index + 1}. ${alarm.text} <span style="color:#666;font-size:0.8em;">(${alarm.typeText})</span>`;
                alarmContainer.appendChild(item);
            });
        }

        // 初始化语音引擎
        initVoiceBtn.addEventListener('click', async () => {
            statusDisplay.textContent = '正在初始化语音引擎...';
            initVoiceBtn.disabled = true;

            try {
                const loadedVoices = await getAvailableVoices();
                // 筛选中文语音(确保有可用的中文语音)
                const chineseVoices = loadedVoices.filter(v =>
                    v.lang === 'zh-CN' ||
                    v.lang === 'zh' ||
                    v.name.includes('Chinese') ||
                    v.name.includes('微软') ||
                    v.name.includes('慧涛')
                );

                if (chineseVoices.length === 0) {
                    statusDisplay.textContent = '未找到中文语音包,请先在系统中安装中文语音(如Windows的“微软慧涛”)';
                    initVoiceBtn.disabled = false;
                    return;
                }

                // 语音初始化完成
                isVoiceReady = true;
                startBtn.disabled = false;
                statusDisplay.textContent = `语音初始化成功!找到${chineseVoices.length}个中文语音包,点击"开始批量朗读"按钮`;
                console.log('可用中文语音:', chineseVoices);
            } catch (err) {
                statusDisplay.textContent = `语音初始化失败:${err.message}`;
                initVoiceBtn.disabled = false;
            }
        });

        // 获取语音列表(带重试机制)
        function getAvailableVoices() {
            return new Promise((resolve, reject) => {
                // 最多重试3次
                let attempts = 0;
                const checkVoices = () => {
                    const voices = synth.getVoices();
                    if (voices.length > 0) {
                        resolve(voices);
                    } else if (attempts < 3) {
                        attempts++;
                        // 触发一次空语音来激活引擎
                        const testUtter = new SpeechSynthesisUtterance('');
                        synth.speak(testUtter);
                        setTimeout(() => {
                            synth.cancel();
                            checkVoices();
                        }, 500);
                    } else {
                        reject(new Error('无法加载语音列表,请刷新页面重试'));
                    }
                };

                // 监听语音变化事件
                synth.onvoiceschanged = checkVoices;
                // 立即检查一次
                checkVoices();
            });
        }

        // 开始批量朗读
        startBtn.addEventListener('click', () => {
            if (!isVoiceReady || alarmList.length === 0) return;

            isPlaying = true;
            currentAlarmIndex = 0; // 从第一条开始
            updateButtonStates(true);
            statusDisplay.textContent = `准备开始朗读,共${alarmList.length}条信息`;
            speakNextAlarm(); // 开始朗读第一条
        });

        // 朗读下一条报警信息
        function speakNextAlarm() {
            // 如果已经读完所有信息或已停止,则退出
            if (currentAlarmIndex >= alarmList.length || !isPlaying) {
                completeBatchReading();
                return;
            }

            // 停止当前正在播放的语音
            if (synth.speaking) {
                synth.cancel();
            }

            const currentAlarm = alarmList[currentAlarmIndex];
            statusDisplay.textContent = `正在朗读第${currentAlarmIndex + 1}/${alarmList.length}条:${currentAlarm.title}`;

            // 创建语音实例
            const utterThis = new SpeechSynthesisUtterance(currentAlarm.text);

            // 获取中文语音
            const chineseVoices = voices.filter(v =>
                v.lang === 'zh-CN' ||
                v.name.includes('Chinese')
            );
            const selectedVoice = chineseVoices.find(voice => voice.lang === 'zh-CN') || chineseVoices[0];
            if (selectedVoice) {
                utterThis.voice = selectedVoice;
            }

            // 设置语音参数
            utterThis.rate = 1; // 语速
            utterThis.pitch = 1; // 音调
            utterThis.volume = 1; // 音量
            utterThis.lang = 'zh-CN'; // 语言

            // 朗读结束后处理
            utterThis.onend = () => {
                currentAlarmIndex++;
                // 延迟500ms播放下一条,避免连在一起
                setTimeout(speakNextAlarm, 500);
            };

            // 朗读出错处理
            utterThis.onerror = (event) => {
                statusDisplay.textContent = `第${currentAlarmIndex + 1}条朗读出错:${event.error}`;
                currentAlarmIndex++;
                setTimeout(speakNextAlarm, 500);
            };

            // 开始朗读
            synth.speak(utterThis);
        }

        // 批量朗读完成
        function completeBatchReading() {
            isPlaying = false;
            statusDisplay.textContent = `所有${alarmList.length}条报警信息朗读完成`;
            updateButtonStates(false);
        }

        // 暂停朗读
        pauseBtn.addEventListener('click', () => {
            if (synth.speaking) {
                synth.pause();
                statusDisplay.textContent = `已暂停在第${currentAlarmIndex + 1}条`;
                pauseBtn.disabled = true;
                resumeBtn.disabled = false;
            }
        });

        // 继续朗读
        resumeBtn.addEventListener('click', () => {
            if (synth.paused) {
                synth.resume();
                statusDisplay.textContent = `继续朗读第${currentAlarmIndex + 1}条...`;
                pauseBtn.disabled = false;
                resumeBtn.disabled = true;
            }
        });

        // 停止朗读
        cancelBtn.addEventListener('click', () => {
            synth.cancel();
            isPlaying = false;
            statusDisplay.textContent = '已停止朗读';
            updateButtonStates(false);
        });

        // 更新按钮状态
        function updateButtonStates(isSpeaking) {
            fetchDataBtn.disabled = isSpeaking;
            initVoiceBtn.disabled = isSpeaking;
            startBtn.disabled = isSpeaking;
            pauseBtn.disabled = !isSpeaking || synth.paused;
            resumeBtn.disabled = !isSpeaking || !synth.paused;
            cancelBtn.disabled = !isSpeaking;
        }

    } else {
        // 浏览器不支持Web Speech API时的处理
        document.getElementById('status').textContent = '抱歉,您的浏览器不支持语音合成功能,请使用Chrome、Edge等现代浏览器。';

        // 禁用所有控制按钮
        const buttons = document.querySelectorAll('button');
        buttons.forEach(button => {
            button.disabled = true;
            button.style.opacity = '0.5';
            button.style.cursor = 'not-allowed';
        });
    }
</script>
</body>

</html>

② 效果图:

三、如果没有声音请注意:

① 是否进行了语音包的初始化:

现代浏览器(Chrome、Edge、Safari 等)从安全和用户体验角度出发,强制要求 “语音合成 / 音频播放” 必须由用户主动交互触发(如点击、触摸、键盘输入),模拟点击会被认为是脚本,没有任何技术手段能完全 “绕过” 这一限制。

解决办法: “弱交互触发”

页面加载完成后,用户只要进行 “极轻微的交互”(如点击页面任意位置、滚动鼠标滚轮、按任意键盘键),就会立即触发语音播报。

② 是否清除了其它正在播放的语音:

if (window.speechSynthesis.speaking) {
  window.speechSynthesis.cancel();
}

总结 

到此这篇关于JS纯前端实现浏览器语音播报、朗读功能的文章就介绍到这了,更多相关JS纯前端浏览器语音播报朗读内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文