Python项目集成WebRTC,实现Web视频通话

现在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.sid
join_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) && (
<CallWindow
status={callWindow}
localSrc={localSrc}
peerSrc={peerSrc}
config={this.config}
mediaDevice={this.pc.mediaDevice}
endCall={this.endCallHandler}
/>
) }
<CallModal
status={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后做流转发。

posted @ 2024-09-25 11:32  ovmeet  阅读(631)  评论(0编辑  收藏  举报