现在python项目越来越多,有需要需要在python项目里集成webrtc视频通话
WebRTC(Web Real-Time Communication)是一种用于在Web浏览器之间实时传输音频、视频和数据的开放标准和技术集合。 WebRTC 提供了一组 API 和协议,使得开发者可以直接在Web浏览器中实现点对点的实时通信,而无需使用第三方插件或应用程序。它通过使用浏览器内置的音频、视频和数据通道,实现了实时的音视频传输和数据传输。
点对点视频连接
根据上面,我们对基本WebRTC有了最基本的认识,下面就从点对点实际例子来从代码角度进一步了解其原理。
Peer:通信双方设备。
Signaling Server: 信令服务器,用于交互连接双方的信令数据(SDP、ICE等),以保证通信的对等连接建立。
NAT:处理私有网络和公共网络之间的地址转换问题(因为大多数设置都处于内网中,需要转换为公共网络才能进行外网访问)
STUN:用于发现设备的公共地址(通过NAT转换的公网地址),辅助穿越NAT进行点对点连接。
TURN:在无法建立直接连接时提供数据中继,确保通信的可靠性。对等连接异常时的兜底方案。
SDP:会话描述协议,用于描述和协商媒体会话的协议,它定义了会话的所有技术细节,包括媒体格式、编解码器、网络地址等。,
ICE:用于发现和选择最优网络路径的框架,确保在各种网络环境下都能成功建立和维持连接。
实现点对点连接:
没有使用STUN/TRUN服务器,可以使用Chrome提供的公共服务器stun:stun.l.google.com:19302
主要步骤如下:
1、和信令服务器建立连接,并获取自身的clientid作为唯一标识
2、申请方将信令通过信令服务器到达接受方
3、接受方接受,将发起方的信令保存到对等连接peer中,并且将自己的信令通过信令服务器给到发送方
4、发送方将接受方的信令数据保存到对等连接peer中,至此发送方-接受方对等连接建立完成
5、在发送方和接受方监听peer的stream,来获取视频流,然后展示在页面
服务端部分代码:
from flask import Flask,jsonify,request
from flask_cors import CORS
from flask_socket import Socket#import urllib.parse
userlist={}
app = Flask(__name__, static_folder='client/dist')
CORS(app,cors_allowed_origins="*")
socket = Socket(app,cors_allowed_origins='*')@socket.on('init')
def client_init(data):print('Client init-----------',data)clientid=data.get("id")if userlist.get(clientid):emit('error', { message: '用户已登录!' })elif clientid:userlist[clientid] = request.sidjoin_room(clientid)emit('init', {'id': clientid})print(userlist)else:emit('error', { message: '无法取的用户id' })@socket.on('disconnect')
def client_disconnect():clientid=get_key_by_value(userlist,request.sid)if clientid:del userlist[clientid] leave_room(clientid)print('Client disconnected---------',userlist)
if __name__ == '__main__': socketio.run(app,host="0.0.0.0",port=5000,ssl_context=("keys/server.crt","keys/server.key"))#app.run(debug=True,host="0.0.0.0",port=5000,ssl_context=("keys/server.crt","keys/server.key"))
客户端代码:
import React, { Component } from 'react';
import _ from 'lodash';
import { socket, PeerConnection } from './communication';
import MainWindow from './components/MainWindow';
import CallWindow from './components/CallWindow';
import CallModal from './components/CallModal';
import UrlParse from 'url-parse';class App extends Component {constructor() {super();this.state = {callWindow: '',callModal: '',callFrom: '',localSrc: null,peerSrc: null};this.pc = {};this.config = null;this.startCallHandler = this.startCall.bind(this);this.endCallHandler = this.endCall.bind(this);this.rejectCallHandler = this.rejectCall.bind(this);}componentDidMount() {let MIN = 1000;let MAX = 9999;let num = Math.floor(Math.random() * ((MAX + 1) - MIN)) + MIN;//let clientid =""+num;const urlParser = new UrlParse(window.location.href, true);let clientid =urlParser.query.username || ""+num;socket.on('request', ({ from: callFrom }) => {this.setState({ callModal: 'active', callFrom });}).on('call', (data) => {if (data.sdp) {this.pc.setRemoteDescription(data.sdp);if (data.sdp.type === 'offer') this.pc.createAnswer();} else this.pc.addIceCandidate(data.candidate);}).on('end', this.endCall.bind(this, false)).emit('init',{id:clientid});}startCall(isCaller, friendID, config) {this.config = config;this.pc = new PeerConnection(friendID).on('localStream', (src) => {const newState = { callWindow: 'active', localSrc: src };if (!isCaller) newState.callModal = '';this.setState(newState);}).on('peerStream', (src) => this.setState({ peerSrc: src })).start(isCaller);}rejectCall() {const { callFrom } = this.state;socket.emit('end', { to: callFrom });this.setState({ callModal: '' });}endCall(isStarter) {if (_.isFunction(this.pc.stop)) {this.pc.stop(isStarter);}this.pc = {};this.config = null;this.setState({callWindow: '',callModal: '',localSrc: null,peerSrc: null});}render() {const { callFrom, callModal, callWindow, localSrc, peerSrc } = this.state;return (<div><MainWindow startCall={this.startCallHandler} />{!_.isEmpty(this.config) && (<CallWindowstatus={callWindow}localSrc={localSrc}peerSrc={peerSrc}config={this.config}mediaDevice={this.pc.mediaDevice}endCall={this.endCallHandler}/>) }<CallModalstatus={callModal}startCall={this.startCallHandler}rejectCall={this.rejectCallHandler}callFrom={callFrom}/></div>);}
}export default App;
演示:https://m.ovmeet.com:5000 可以支持android,IOS,微信内打开
总结
实现点对点通信,主要就是信令数据的交换sdp,通知对端地址(通信参数、IP地址等)以保证对等连接的成功建立,然后采集视频流传给对方。
其中信令服务器仅用于对等连接前的信令交换。NAT是将设备内网地址转换为外网公共地址。STUN来获取设置的公网地址。TURN服务器是用于对等连接不能Nat后做流转发。