“从文字到声音:重新定义在线沟通”实时语音通话功能!简单聊天小程序!
添加实时语音通话功能时,通常会使用 WebRTC 技术。WebRTC 是一种用于实时通信的协议,允许在对等网络之间直接传输音频和视频。在 Flask 中实现 WebRTC 可能稍微复杂一些,但下面是一个简化的示例,指导你如何将语音通话功能整合到我们之前的实时聊天应用中。
·
添加实时语音通话功能时,通常会使用 WebRTC 技术。WebRTC 是一种用于实时通信的协议,允许在对等网络之间直接传输音频和视频。在 Flask 中实现 WebRTC 可能稍微复杂一些,但下面是一个简化的示例,指导你如何将语音通话功能整合到我们之前的实时聊天应用中。
1. 修改 app.py
确保你已经安装 Flask、Flask-SocketIO 和其他依赖库。将 app.py 更新如下:
from flask import Flask, render_template
from flask_socketio import SocketIO, emit
import base64
app = Flask(__name__)
socketio = SocketIO(app)
@app.route('/')
def index():
return render_template('index.html')
@socketio.on('message')
def handle_message(msg):
emit('message', msg, broadcast=True)
@socketio.on('image')
def handle_image(image_data):
emit('image', image_data, broadcast=True)
@socketio.on('webrtc_offer')
def handle_webrtc_offer(data):
emit('webrtc_offer', data, broadcast=True)
@socketio.on('webrtc_answer')
def handle_webrtc_answer(data):
emit('webrtc_answer', data, broadcast=True)
@socketio.on('ice_candidate')
def handle_ice_candidate(data):
emit('ice_candidate', data, broadcast=True)
if __name__ == '__main__':
socketio.run(app)
2. 修改 index.html
在 index.html 中添加 WebRTC 的相关代码,更新后的代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>聊天应用</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.0.3/socket.io.js"></script>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
#messages { border: 1px solid #ccc; height: 300px; overflow-y: scroll; padding: 10px; }
#form { display: flex; }
#input { flex: 1; padding: 10px; }
#send { padding: 10px; }
#imageInput { display: none; }
</style>
</head>
<body>
<h1>实时聊天应用</h1>
<div id="messages"></div>
<div id="form">
<input id="input" autocomplete="off" placeholder="输入消息..." />
<input type="file" id="imageInput" accept="image/*" />
<button id="send">发送</button>
<button id="uploadImage">上传图片</button>
<button id="startCall">开始语音通话</button>
</div>
<audio id="remoteAudio" autoplay></audio>
<script>
const socket = io();
let localStream;
let peerConnection;
const config = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' } // STUN 服务器
]
};
document.getElementById('send').onclick = function() {
const input = document.getElementById('input');
socket.emit('message', input.value);
input.value = '';
input.focus();
};
document.getElementById('uploadImage').onclick = function() {
document.getElementById('imageInput').click();
};
document.getElementById('imageInput').onchange = function(event) {
const file = event.target.files[0];
const reader = new FileReader();
reader.onloadend = function() {
socket.emit('image', reader.result);
};
if (file) {
reader.readAsDataURL(file);
}
};
document.getElementById('startCall').onclick = async function() {
localStream = await navigator.mediaDevices.getUserMedia({ audio: true });
peerConnection = new RTCPeerConnection(config);
localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
peerConnection.onicecandidate = event => {
if (event.candidate) {
socket.emit('ice_candidate', event.candidate);
}
};
peerConnection.ontrack = event => {
const remoteAudio = document.getElementById('remoteAudio');
remoteAudio.srcObject = event.streams[0];
};
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
socket.emit('webrtc_offer', offer);
};
socket.on('message', function(msg) {
const messages = document.getElementById('messages');
messages.innerHTML += '<div>' + msg + '</div>';
messages.scrollTop = messages.scrollHeight;
});
socket.on('image', function(imageData) {
const messages = document.getElementById('messages');
messages.innerHTML += '<div><img src="' + imageData + '" style="max-width: 300px; max-height: 300px;" /></div>';
messages.scrollTop = messages.scrollHeight;
});
socket.on('webrtc_offer', async function(offer) {
peerConnection = new RTCPeerConnection(config);
peerConnection.setRemoteDescription(new RTCSessionDescription(offer));
localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
socket.emit('webrtc_answer', answer);
peerConnection.onicecandidate = event => {
if (event.candidate) {
socket.emit('ice_candidate', event.candidate);
}
};
peerConnection.ontrack = event => {
const remoteAudio = document.getElementById('remoteAudio');
remoteAudio.srcObject = event.streams[0];
};
});
socket.on('webrtc_answer', function(answer) {
peerConnection.setRemoteDescription(new RTCSessionDescription(answer));
});
socket.on('ice_candidate', function(candidate) {
peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
});
document.getElementById('input').addEventListener('keypress', function(event) {
if (event.key === 'Enter') {
document.getElementById('send').click();
}
});
</script>
</body>
</html>
说明
- 获取用户媒体:使用
navigator.mediaDevices.getUserMedia获取音频流,并将其传递到 WebRTC 连接中。 - STUN 服务器:添加一个 STUN 服务器(
stun:stun.l.google.com:19302),用于获取 NAT(网络地址转换)和防火墙后的 IP 地址。 - 创建和处理 Offer:当点击“开始语音通话”按钮时,获取本地音频流并创建 WebRTC 连接,同时生成并发送 offer。
- 接收 Offer 和 Answer:当接收到远程 offer 时,建立连接并发送 answer。
- ICE 候选者管理:在建立连接过程中,处理 ICE 候选者,并将其发送到 socket 进行广播。
运行应用
在终端中运行 python app.py 并在浏览器中打开 http://127.0.0.1:5000/。你可以尝试使用多个标签页进行测试,确保你允许浏览器使用麦克风。
注意事项
- 这个简单的示例是基于信任的用户环境。要在生产环境中使用 WebRTC,需要更复杂的信号处理机制(例如使用 TURN 服务器)、身份验证和数据安全性。
- 语音质量可能会受到网络带宽和延迟的影响,要确保使用稳定的网络连接。
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)