import jwt import time import argparse baseURL = "https://brtc-wtn.baijiayun.com" appid = "APPID" appsecret = "APPSECRET" def sign(appid, appsecret, info): payload = {**info, 'app_id': appid} token = jwt.encode(payload, appsecret, algorithm='HS256') return token info = { 'version': '1.0' } if __name__ == '__main__': parser = argparse.ArgumentParser(description='Sign JWT') parser.add_argument('-t', '--time', type=int, help='token expire time', default=3600) parser.add_argument( '-A', '--auth', help='enable sub auth', action='store_true') parser.add_argument('-a', '--action', help='pub or sub', default='pub') parser.add_argument('-r', '--room', help='room id') parser.add_argument('-s', '--stream', help='stream id', default='stream') args = parser.parse_args() room_id = args.room info['exp'] = int(time.time()) + args.time info['action'] = args.action if room_id is not None: info['room_id'] = room_id info['stream_id'] = args.stream info['enable_sub_auth'] = args.auth token = sign(appid, appsecret, info) print(token) print() if args.action == 'pub': if room_id is None: print(f'{baseURL}/whip/{appid}/{args.stream}') else: print(f'{baseURL}/whip/{appid}/{args.stream}?room_id={args.room}') else: if room_id is None: print(f'{baseURL}/whep/{appid}/{args.stream}') else: print(f'{baseURL}/whep/{appid}/{args.stream}?room_id={args.room}')
脚本命令参数介绍
python3 wtn.py -h usage: wtn.py [-h] [-t TIME] [-A] [-a ACTION] [-r ROOM] [-s STREAM] Sign JWT optional arguments: -h, --help show this help message and exit -t TIME, --time Token过期时间, 单位 秒 -A, --auth 是否启用认证, 推流时需要设置流是否需要验证 -a ACTION, --action 操作类型 [pub/sub] -r ROOM, --room 房间号[可选] -s STREAM, --stream 流ID
AppID
StreamId
以上步骤可以使用 辅助接入脚本 来实现
辅助接入脚本
# 推流, 开启拉流验证, 无具体房间 python3 wtn.py -A -a pub -s streamId # 拉流 python3 wtn.py -a sub -s streamId
[# Puller.js #]
/** * BRTC-WTN 实现demo * @class Puller */ class Puller { gateway = "https://brtc-wtn.baijiayun.com"; action = "sub"; defaultExp = 86400; /** * @constructor * @param {String} appid 应用ID * @param {String} appkey 应用密钥 * @param {String} streamId 流ID * @param {String} token 签发token * @param {String} roomId 房间ID */ constructor(appid, streamId, token, roomId="") { this.appid = appid; this.streamId = streamId; this.token = token; this.roomId = roomId; this.resource = "" } /** * 开始推流 * @method start * */ start(ontrack) { let url = `${this.gateway}/whep/${this.appid}/${this.streamId}?token=${this.token}`; if (this.roomId) { url += `&room_id=${this.roomId}`; } let pc = new RTCPeerConnection(); pc.ontrack = ontrack; pc.createOffer({ offerToReceiveAudio: false, offerToReceiveVideo: false }).then(offer => { pc.setLocalDescription(offer); fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/sdp' }, body: offer.sdp }).then(res => { this.resource = res.headers.get('Location'); res.text().then(sdp => { pc.setRemoteDescription({ type: 'answer', sdp: sdp }); }); }).catch(err => { throw err; }); }).catch(err => { throw err; }); } /** * 停止推流 * @method stop */ stop() { if (this.resource) { // fetch delete resource fetch(this.resource, { method: 'DELETE', headers: { 'Content-Type': 'application/json' }}) .catch(err => console.log(err)); } } /** * 控制音频 * @method muteAudio * @param {Boolean} mute 是否静音 */ muteAudio(mute) { if (this.resource) { // fetch patch resource fetch(this.resource, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({mute_audio: mute}) }) .catch(err => console.log(err)); } } /** * 控制视频 * @method muteVideo * @param {Boolean} mute 是否静音 */ muteVideo(mute) { if (this.resource) { // fetch patch resource fetch(this.resource, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({mute_video: mute}) }) .catch(err => console.log(err)); } } }
[# Pusher.js #]
/** * BRTC-WTN 实现demo * @class Pusher */ class Pusher { gateway = "https://brtc-wtn.baijiayun.com"; action = "pub"; defaultExp = 86400; /** * @constructor * @param {String} appid 应用ID * @param {String} appkey 应用密钥 * @param {String} streamId 流ID * @param {String} token 签发token * @param {String} roomId 房间ID */ constructor(appid, streamId, token, roomId = "") { this.appid = appid; this.streamId = streamId; this.token = token; this.roomId = roomId; this.resource = "" } /** * 开始推流 * @method start * */ start(stream) { let url = `${this.gateway}/whip/${this.appid}/${this.streamId}?token=${this.token}`; if (this.roomId) { url += `&room_id=${this.roomId}`; } let pc = new RTCPeerConnection(); pc.addStream(stream); pc.createOffer({ offerToReceiveAudio: false, offerToReceiveVideo: false }).then(offer => { pc.setLocalDescription(offer); fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/sdp' }, body: offer.sdp }).then(res => { this.resource = res.headers.get('Location'); res.text().then(sdp => { pc.setRemoteDescription({ type: 'answer', sdp: sdp }); }); }).catch(err => { throw err; }); }).catch(err => { throw err; }); } /** * 停止推流 * @method stop */ stop() { if (this.resource) { // fetch delete resource fetch(this.resource, { method: 'DELETE', headers: { 'Content-Type': 'application/json' } }) .catch(err => console.log(err)); } } /** * 控制音频 * @method muteAudio * @param {Boolean} mute 是否静音 */ muteAudio(mute) { if (this.resource) { // fetch patch resource fetch(this.resource, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mute_audio: mute }) }) .catch(err => console.log(err)); } } /** * 控制视频 * @method muteVideo * @param {Boolean} mute 是否静音 */ muteVideo(mute) { if (this.resource) { // fetch patch resource fetch(this.resource, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mute_video: mute }) }) .catch(err => console.log(err)); } } }
[# 推流实现 #]
<!DOCTYPE html> <html> <head> <title>WTN 推流实现</title> <script src="./Pusher.js"></script> </head> <div style="text-align: center;"> <div style="margin-top: 20px; margin-bottom: 20px; border: 1px solid #28a745;"> <video id="localVideo" autoplay muted style="width: 100%; max-width: 400px;"></video> </div> <button id="push" style="padding: 10px 20px; background-color: #007bff; color: #fff; border: none; border-radius: 5px;">推流</button> <button id="stop" style="padding: 10px 20px; background-color: #dc3545; color: #fff; border: none; border-radius: 5px;">停止推流</button> <button id="muteAudio" style="padding: 10px 20px; background-color: #28a745; color: #fff; border: none; border-radius: 5px;">控制音频</button> <button id="muteVideo" style="padding: 10px 20px; background-color: #ffc107; color: #fff; border: none; border-radius: 5px;">控制视频</button> <br> </div> <script> var ispub = false; var muteAudio = false; var muteVideo = false; var pusher = new Pusher("appid", "streamId", "token"); document.getElementById("push").onclick = function () { navigator.mediaDevices.getUserMedia( { video: true, audio: true } ).then(stream => { pusher.start(stream); console.log("推流成功"); ispub = true; var video = document.getElementById("localVideo"); video.srcObject = stream; }).catch(err => { console.log(err); }); } // 停止推流 document.getElementById("stop").onclick = function () { if (ispub) { pusher.stop(); ispub = false; var video = document.getElementById("localVideo"); video.srcObject = null; } } // 静音 document.getElementById("muteAudio").onclick = function () { muteAudio = !muteAudio; pusher.muteAudio(muteAudio); } // 关闭视频 document.getElementById("muteVideo").onclick = function () { muteVideo = !muteVideo; pusher.muteVideo(muteVideo); } </script> </html>
[# 拉流实现 #]
<!DOCTYPE html> <html> <head> <title>WTN 拉流实现</title> <script src="./puller.js"></script> </head> <div style="text-align: center;"> <div style="margin-top: 20px; margin-bottom: 20px; border: 1px solid #28a745;"> <video id="localVideo" autoplay muted style="width: 100%; max-width: 400px;"></video> <audio id="localAudio"></audio> </div> <button id="push" style="padding: 10px 20px; background-color: #007bff; color: #fff; border: none; border-radius: 5px;">开始拉流</button> <button id="stop" style="padding: 10px 20px; background-color: #dc3545; color: #fff; border: none; border-radius: 5px;">停止拉流</button> <button id="muteAudio" style="padding: 10px 20px; background-color: #28a745; color: #fff; border: none; border-radius: 5px;">控制音频</button> <button id="muteVideo" style="padding: 10px 20px; background-color: #ffc107; color: #fff; border: none; border-radius: 5px;">控制视频</button> <br> </div> <script> var ispub = false; var muteAudio = false; var muteVideo = false; var puller = new Puller("appid", "streamId", "token"); function onTrack(track) { if (track.kind === "video") { var video = document.getElementById("localVideo"); video.srcObject = new MediaStream([track]); } else if (track.kind === "audio") { var audio = document.getElementById("localAudio"); audio.srcObject = new MediaStream([track]); } } document.getElementById("push").onclick = function () { puller.start(onTrack); } // 停止推流 document.getElementById("stop").onclick = function () { if (ispub) { puller.stop(); ispub = false; var video = document.getElementById("localVideo"); video.srcObject = null; } } // 静音 document.getElementById("muteAudio").onclick = function () { muteAudio = !muteAudio; puller.muteAudio(muteAudio); } // 关闭视频 document.getElementById("muteVideo").onclick = function () { muteVideo = !muteVideo; puller.muteVideo(muteVideo); } </script> </html>
WHIP
完成 1 配置后, 可以点击 开始直播
开始直播
接入示例
接入流程
1. 辅助接入脚本
接入流程
AppID
和StreamId
定义推拉流的地地址以上步骤可以使用
辅助接入脚本
来实现JS 示例
1. 基础类定义
[# Puller.js #]
[# Pusher.js #]
2. 使用方式
[# 推流实现 #]
[# 拉流实现 #]
OBS 示例
1. 接入配置
WHIP
2. 开始推流
完成 1 配置后, 可以点击
开始直播
Eyevinn 示例
1. 配置推拉流地址
2. 推拉流效果
SRS 示例
1. 配置推拉流地址
2. 推拉流效果