From 9be6399827472d496dc2d3708808acc7e4f42a39 Mon Sep 17 00:00:00 2001 From: Vittorio Palmisano Date: Sun, 26 Feb 2023 14:52:43 +0100 Subject: [PATCH 1/7] External peerconnection implementation --- scripts/peer-connection-external.js | 159 +++++++++++++++++++++++----- src/session.ts | 26 ++++- src/utils.ts | 141 +++++++++++++++++++----- 3 files changed, 266 insertions(+), 60 deletions(-) diff --git a/scripts/peer-connection-external.js b/scripts/peer-connection-external.js index 048c86f..fcdc395 100644 --- a/scripts/peer-connection-external.js +++ b/scripts/peer-connection-external.js @@ -1,54 +1,155 @@ /* global log, PeerConnections */ window.RTCPeerConnection = class { + #pendingTasks = [] + connectionState = '' + iceConnectionState = '' + localDescription = null + constructor(options) { log(`RTCPeerConnection`, options) - window.createPeerConnection(options).then(({ id }) => { - this.id = id - PeerConnections.set(id, this) + let id = Math.round(Math.random() * 1e8) + while (PeerConnections.has(id)) { + id = Math.round(Math.random() * 1e8) + } + this.id = id + PeerConnections.set(id, this) + this.#pendingTasks.push( + window.createPeerConnectionExternal(id, JSON.stringify(options)), + ) + + this.addEventListener('connectionstatechange', connectionState => { + log(`RTCPeerConnection connectionstatechange`, connectionState) + this.connectionState = connectionState + if (connectionState === 'closed') { + PeerConnections.delete(this.id) + } + }) + + this.addEventListener('iceconnectionstatechange', iceConnectionState => { + log(`RTCPeerConnection iceconnectionstatechange`, iceConnectionState) + this.iceConnectionState = iceConnectionState + if (iceConnectionState === 'closed') { + PeerConnections.delete(this.id) + } }) + } - /* pc.addEventListener('connectionstatechange', () => { - if (pc.connectionState === 'closed') { - log(`RTCPeerConnection closed (connectionState: ${pc.connectionState})`) - PeerConnections.delete(id) + async waitPendingTasks() { + log( + `RTCPeerConnection-${this.id} waitPendingTasks ${ + this.#pendingTasks.length + }`, + ) + for (const p of this.#pendingTasks.splice(0, this.#pendingTasks.length)) { + try { + await p + } catch (e) { + log(`Task error: ${e.message}`, e) } - }) */ + } + } + + async close() { + log(`RTCPeerConnection-${this.id} close`) + await this.waitPendingTasks() + await window.callPeerConnectionExternalMethod(this.id, 'close') } addEventListener(name, cb) { - log(`RTCPeerConnection-${this.id} addEventListener`, name, cb) + log(`RTCPeerConnection-${this.id} addEventListener ${name}`) + window.addEventListener( + `peer-connection-${this.id}-event-${name}`, + event => { + log( + `RTCPeerConnection-${this.id} peer-connection-event-${name}`, + event.detail, + ) + cb(event.detail) + }, + ) + } + + async addTransceiver(trackOrKind, init) { + log(`RTCPeerConnection-${this.id} addTransceiver`, { trackOrKind, init }) + await this.waitPendingTasks() + this.#pendingTasks.push( + window.callPeerConnectionExternalMethod( + this.id, + 'addTransceiver', + JSON.stringify({ + trackOrKind, + init, + }), + ), + ) } async createOffer(options) { - log(`RTCPeerConnection-${this.id} createOffer`, { options }) - return {} + log(`RTCPeerConnection-${this.id} createOffer`, JSON.stringify(options)) + await this.waitPendingTasks() + const ret = await window.callPeerConnectionExternalMethod( + this.id, + 'createOffer', + JSON.stringify(options), + ) + this.localDescription = ret + return JSON.parse(ret) + } + + async createAnswer(options) { + log(`RTCPeerConnection-${this.id} createAnswer`, JSON.stringify(options)) + await this.waitPendingTasks() + const ret = await window.callPeerConnectionExternalMethod( + this.id, + 'createAnswer', + JSON.stringify(options), + ) + this.localDescription = ret + return JSON.parse(ret) } async setLocalDescription(description) { - log(`RTCPeerConnection-${this.id} setLocalDescription`, description) + log( + `RTCPeerConnection-${this.id} setLocalDescription`, + JSON.stringify(description), + ) + await this.waitPendingTasks() + await window.callPeerConnectionExternalMethod( + this.id, + 'setLocalDescription', + JSON.stringify(description), + ) } async setRemoteDescription(description) { - log(`RTCPeerConnection-${this.id} setRemoteDescription`, description) - } - - addTransceiver(...args) { - log(`RTCPeerConnection-${this.id} addTransceiver`, args) - /* if (transceiver.sender) { - const setParametersNative = transceiver.sender.setParameters.bind( - transceiver.sender, - ) - setParametersNative.setParameters = parameters => { - log(`RTCPeerConnection-${id} transceiver.setParameters`, parameters) - return setParametersNative(parameters) - } - } */ - return {} + log( + `RTCPeerConnection-${this.id} setRemoteDescription`, + JSON.stringify(description), + ) + await this.waitPendingTasks() + await window.callPeerConnectionExternalMethod( + this.id, + 'setRemoteDescription', + JSON.stringify(description), + ) + } + + async addStream(mediaStream) { + log(`RTCPeerConnection-${this.id} addStream`, mediaStream) + await this.waitPendingTasks() + this.#pendingTasks.push( + window.callPeerConnectionExternalMethod( + this.id, + 'addStream', + JSON.stringify(mediaStream), // TODO + ), + ) } - addStream(...args) { - log(`RTCPeerConnection-${this.id} addStream`, args) + async getStats() { + log(`RTCPeerConnection-${this.id} getStats`) + return {} } } diff --git a/src/session.ts b/src/session.ts index c52e63a..8b432bb 100644 --- a/src/session.ts +++ b/src/session.ts @@ -57,7 +57,6 @@ import { logger, md5, PeerConnectionExternal, - PeerConnectionExternalMethod, resolveIP, sleep, } from './utils' @@ -892,8 +891,21 @@ window.GET_DISPLAY_MEDIA_OVERRIDE = JSON.parse('${JSON.stringify(override)}'); await page.exposeFunction( 'createPeerConnectionExternal', // eslint-disable-next-line @typescript-eslint/no-explicit-any - async (options: any) => { - const pc = new PeerConnectionExternal(options) + (id: number, options: string) => { + const pc = new PeerConnectionExternal(id, options) + pc.on('event', async ({ name, value }) => { + await page.evaluate( + ({ id, name, value }) => { + window.dispatchEvent( + new CustomEvent(`peer-connection-${id}-event-${name}`, { + bubbles: true, + detail: value, + }), + ) + }, + { id, name, value }, + ) + }) return { id: pc.id } }, ) @@ -901,10 +913,14 @@ window.GET_DISPLAY_MEDIA_OVERRIDE = JSON.parse('${JSON.stringify(override)}'); await page.exposeFunction( 'callPeerConnectionExternalMethod', // eslint-disable-next-line @typescript-eslint/no-explicit-any - async (id: number, name: PeerConnectionExternalMethod, arg: any) => { + async (id: number, name: string, args: string) => { const pc = PeerConnectionExternal.get(id) if (pc) { - return pc[name](arg) + return pc.sendCommand(name, args) + } else { + log.warn( + `callPeerConnectionExternalMethod ${name}: id=${id} not found`, + ) } }, ) diff --git a/src/utils.ts b/src/utils.ts index 58598e3..4a900d3 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,3 +1,6 @@ +import { EventEmitter } from 'node:events' + +import assert from 'assert' import axios from 'axios' import { exec, spawn } from 'child_process' import { createHash } from 'crypto' @@ -461,7 +464,7 @@ const runExitHandlers = async (signal: string): Promise => { let runExitHandlersPromise: Promise | null = null const SIGNALS = [ - 'beforeExit', + 'exit', 'uncaughtException', 'unhandledRejection', 'SIGHUP', @@ -727,29 +730,109 @@ export class Scheduler { } } +export class Future { + promise: Promise + _resolve?: (value: T) => void + _reject?: (reason?: unknown) => void + + constructor() { + this.promise = new Promise((resolve, reject) => { + this._resolve = resolve + this._reject = reject + }) + } + + resolve(value: T): void { + if (this._resolve) { + this._resolve(value) + this._resolve = undefined + } + } + + reject(reason?: unknown): void { + if (this._reject) { + this._reject(reason) + this._reject = undefined + } + } +} + // -export class PeerConnectionExternal { - public id: number - private process +export class PeerConnectionExternal extends EventEmitter { + public readonly id: number + public connectionState = 'closed' + private readonly process private static cache = new Map() - + private commandId = 0 // eslint-disable-next-line @typescript-eslint/no-explicit-any - constructor(options?: any) { - this.process = spawn('sleep', ['600']) - this.id = this.process.pid || -1 - log.debug(`PeerConnectionExternal contructor: ${this.id}`, options) + private commands = new Map>() + private stdoutBuf = '' + + constructor(id: number, options: string) { + super() + log.debug(`PeerConnectionExternal-${id} constructor`, options) + this.id = id PeerConnectionExternal.cache.set(this.id, this) + const dirPath = + '__nexe' in process ? '.' : path.resolve(path.dirname(__filename), '..') + const execPath = path.join( + dirPath, + 'peer-connection-external/peer-connection-external', + ) + log.debug(`starting ${execPath} ${options}`) + this.process = spawn(execPath, [options]) + assert(this.process.pid, 'PeerConnectionExternal spawn failed') + this.process.stdout.on('data', data => { - log.debug(`PeerConnectionExternal stdout: ${data}`) + //log.debug(`PeerConnectionExternal-${this.id} stdout: "${data}"`) + this.stdoutBuf += String(data) + while (this.stdoutBuf.length) { + const index = this.stdoutBuf.indexOf('\n') + if (index === -1) { + break + } + const line = this.stdoutBuf.slice(0, index) + this.stdoutBuf = this.stdoutBuf.slice(index + 1) + + const [sid, name, value] = line.split('|', 3) + log.debug( + `PeerConnectionExternal-${this.id} > [${sid}] ${name}: "${value}"`, + ) + if (sid === 'e') { + if (name === 'connectionstatechange') { + this.connectionState = value + } + this.emit('event', { + name, + value, + }) + } else { + const id = parseInt(sid) + const command = this.commands.get(id) + if (command) { + this.commands.delete(id) + command.resolve(value) + } + } + } }) this.process.stderr.on('data', data => { - log.debug(`PeerConnectionExternal stderr: ${data}`) + log.debug(`PeerConnectionExternal-${this.id} [stderr] "${data}"`) }) this.process.on('close', code => { - log.debug(`PeerConnectionExternal process exited with code ${code}`) + log.debug( + `PeerConnectionExternal-${this.id} process exited with code ${code}`, + ) + PeerConnectionExternal.cache.delete(this.id) + this.connectionState = 'closed' + this.emit('event', { + name: 'connectionstatechange', + value: this.connectionState, + }) + this.stdoutBuf = '' PeerConnectionExternal.cache.delete(this.id) }) } @@ -758,22 +841,28 @@ export class PeerConnectionExternal { return PeerConnectionExternal.cache.get(id) } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - async createOffer(options: any) { - log.debug(`PeerConnectionExternal createOffer`, { options }) - return {} - } - - setLocalDescription(description: string) { - log.debug(`PeerConnectionExternal setLocalDescription`, description) + static stopAll(): void { + log.debug( + `PeerConnectionExternal stopAll (${PeerConnectionExternal.cache.size})`, + ) + PeerConnectionExternal.cache.forEach(p => { + p.process.kill('SIGINT') + }) + PeerConnectionExternal.cache.clear() } - setRemoteDescription(description: string) { - log.debug(`PeerConnectionExternal setRemoteDescription`, description) + sendCommand(name: string, value = ''): Promise { + const id = this.commandId + log.debug(`PeerConnectionExternal-${this.id} < [${id}] ${name}: "${value}"`) + this.commandId++ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const p = new Future() + this.commands.set(id, p) + this.process.stdin.write(`${id}|${name}|${value}\n`) + return p.promise } } -export type PeerConnectionExternalMethod = - | 'createOffer' - | 'setLocalDescription' - | 'setRemoteDescription' +registerExitHandler(async () => { + PeerConnectionExternal.stopAll() +}) From 910c804ca2a11c59636c57326b501b8183cdbb11 Mon Sep 17 00:00:00 2001 From: Vittorio Palmisano Date: Sun, 26 Feb 2023 14:53:35 +0100 Subject: [PATCH 2/7] Changed: --- peer-connection-external/.gitignore | 1 + peer-connection-external/go.mod | 31 +++ peer-connection-external/go.sum | 216 +++++++++++++++++++ peer-connection-external/main.go | 311 ++++++++++++++++++++++++++++ 4 files changed, 559 insertions(+) create mode 100644 peer-connection-external/.gitignore create mode 100644 peer-connection-external/go.mod create mode 100644 peer-connection-external/go.sum create mode 100644 peer-connection-external/main.go diff --git a/peer-connection-external/.gitignore b/peer-connection-external/.gitignore new file mode 100644 index 0000000..a82f671 --- /dev/null +++ b/peer-connection-external/.gitignore @@ -0,0 +1 @@ +peer-connection-external diff --git a/peer-connection-external/go.mod b/peer-connection-external/go.mod new file mode 100644 index 0000000..1c7856d --- /dev/null +++ b/peer-connection-external/go.mod @@ -0,0 +1,31 @@ +module peer-connection-external + +go 1.18 + +require ( + github.com/pion/example-webrtc-applications/v3 v3.0.5 + github.com/pion/rtcp v1.2.10 + github.com/pion/webrtc/v3 v3.1.50 +) + +require ( + github.com/google/uuid v1.3.0 // indirect + github.com/pion/datachannel v1.5.5 // indirect + github.com/pion/dtls/v2 v2.1.5 // indirect + github.com/pion/ice/v2 v2.2.12 // indirect + github.com/pion/interceptor v0.1.11 // indirect + github.com/pion/logging v0.2.2 // indirect + github.com/pion/mdns v0.0.5 // indirect + github.com/pion/randutil v0.1.0 // indirect + github.com/pion/rtp v1.7.13 // indirect + github.com/pion/sctp v1.8.5 // indirect + github.com/pion/sdp/v3 v3.0.6 // indirect + github.com/pion/srtp/v2 v2.0.10 // indirect + github.com/pion/stun v0.3.5 // indirect + github.com/pion/transport v0.14.1 // indirect + github.com/pion/turn/v2 v2.0.8 // indirect + github.com/pion/udp v0.1.1 // indirect + golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2 // indirect + golang.org/x/net v0.3.0 // indirect + golang.org/x/sys v0.3.0 // indirect +) diff --git a/peer-connection-external/go.sum b/peer-connection-external/go.sum new file mode 100644 index 0000000..fcadd97 --- /dev/null +++ b/peer-connection-external/go.sum @@ -0,0 +1,216 @@ +github.com/at-wat/ebml-go v0.16.0/go.mod h1:w1cJs7zmGsb5nnSvhWGKLCxvfu4FVx5ERvYDIalj1ww= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hybridgroup/mjpeg v0.0.0-20140228234708-4680f319790e/go.mod h1:eagM805MRKrioHYuU7iKLUyFPVKqVV6um5DAvCkUtXs= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/notedit/janus-go v0.0.0-20210115013133-fdce1b146d0e/go.mod h1:BN/Txse3qz8tZOmCm2OfajB2wHVujWmX3o9nVdsI6gE= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pion/datachannel v1.4.21/go.mod h1:oiNyP4gHx2DIwRzX/MFyH0Rz/Gz05OgBlayAI2hAWjg= +github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8= +github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0= +github.com/pion/dtls/v2 v2.0.9/go.mod h1:O0Wr7si/Zj5/EBFlDzDd6UtVxx25CE1r7XM7BQKYQho= +github.com/pion/dtls/v2 v2.1.5 h1:jlh2vtIyUBShchoTDqpCCqiYCyRFJ/lvf/gQ8TALs+c= +github.com/pion/dtls/v2 v2.1.5/go.mod h1:BqCE7xPZbPSubGasRoDFJeTsyJtdD1FanJYL0JGheqY= +github.com/pion/example-webrtc-applications/v3 v3.0.5 h1:Bycp74ODTRa+zKa6dpy3uHOW/kwg2XEBmGjFTWfVPpA= +github.com/pion/example-webrtc-applications/v3 v3.0.5/go.mod h1:jQDdp7mkpHvTtw8GC77xPdVYUuiALSJVdFG3FYYBs4A= +github.com/pion/ice/v2 v2.1.10/go.mod h1:kV4EODVD5ux2z8XncbLHIOtcXKtYXVgLVCeVqnpoeP0= +github.com/pion/ice/v2 v2.1.12/go.mod h1:ovgYHUmwYLlRvcCLI67PnQ5YGe+upXZbGgllBDG/ktU= +github.com/pion/ice/v2 v2.2.12 h1:n3M3lUMKQM5IoofhJo73D3qVla+mJN2nVvbSPq32Nig= +github.com/pion/ice/v2 v2.2.12/go.mod h1:z2KXVFyRkmjetRlaVRgjO9U3ShKwzhlUylvxKfHfd5A= +github.com/pion/interceptor v0.0.15/go.mod h1:pg3J253eGi5bqyKzA74+ej5Y19ez2jkWANVnF+Z9Dfk= +github.com/pion/interceptor v0.1.11 h1:00U6OlqxA3FFB50HSg25J/8cWi7P6FbSzw4eFn24Bvs= +github.com/pion/interceptor v0.1.11/go.mod h1:tbtKjZY14awXd7Bq0mmWvgtHB5MDaRN7HV3OZ/uy7s8= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw= +github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g= +github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= +github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= +github.com/pion/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0= +github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo= +github.com/pion/rtcp v1.2.10 h1:nkr3uj+8Sp97zyItdN60tE/S6vk4al5CPRR6Gejsdjc= +github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I= +github.com/pion/rtp v1.7.0/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= +github.com/pion/rtp v1.7.1/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= +github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA= +github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= +github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0= +github.com/pion/sctp v1.7.12/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s= +github.com/pion/sctp v1.8.5 h1:JCc25nghnXWOlSn3OVtEnA9PjQ2JsxQbG+CXZ1UkJKQ= +github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0= +github.com/pion/sdp/v2 v2.4.0/go.mod h1:L2LxrOpSTJbAns244vfPChbciR/ReU1KWfG04OpkR7E= +github.com/pion/sdp/v3 v3.0.4/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk= +github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw= +github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw= +github.com/pion/srtp/v2 v2.0.5/go.mod h1:8k6AJlal740mrZ6WYxc4Dg6qDqqhxoRG2GSjlUhDF0A= +github.com/pion/srtp/v2 v2.0.10 h1:b8ZvEuI+mrL8hbr/f1YiJFB34UMrOac3R3N1yq2UN0w= +github.com/pion/srtp/v2 v2.0.10/go.mod h1:XEeSWaK9PfuMs7zxXyiN252AHPbH12NX5q/CFDWtUuA= +github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg= +github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA= +github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A= +github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q= +github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A= +github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g= +github.com/pion/transport v0.13.1/go.mod h1:EBxbqzyv+ZrmDb82XswEE0BjfQFtuw1Nu6sjnjWCsGg= +github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40= +github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI= +github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw= +github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw= +github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw= +github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o= +github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M= +github.com/pion/webrtc/v3 v3.1.0-beta.3/go.mod h1:I4O6v2pkiXdVmcn7sUhCNwHUAepGU19PVEyR204s1qc= +github.com/pion/webrtc/v3 v3.1.50 h1:wLMo1+re4WMZ9Kun9qcGcY+XoHkE3i0CXrrc0sjhVCk= +github.com/pion/webrtc/v3 v3.1.50/go.mod h1:y9n09weIXB+sjb9mi0GBBewNxo4TKUQm5qdtT5v3/X4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +gocv.io/x/gocv v0.28.0/go.mod h1:oc6FvfYqfBp99p+yOEzs9tbYF9gOrAQSeL/dyIPefJU= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210812204632-0ba0e8f03122/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2 h1:x8vtB3zMecnlqZIwJNUUpwYKYSqCz5jXbiyv0ZJJZeI= +golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/peer-connection-external/main.go b/peer-connection-external/main.go new file mode 100644 index 0000000..eb9f97a --- /dev/null +++ b/peer-connection-external/main.go @@ -0,0 +1,311 @@ +package main + +import ( + "bufio" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "strings" + + "github.com/pion/webrtc/v3" +) + +func readStdin(channel chan string) { + r := bufio.NewReader(os.Stdin) + for { + var in string + for { + var err error + in, err = r.ReadString('\n') + if err != io.EOF { + if err != nil { + panic(err) + } + } + in = strings.TrimSpace(in) + if len(in) > 0 { + break + } + } + channel <- in + } +} + +func onError(id string, msg string, err error) { + println(fmt.Sprintf("[peer-connection-external] [%s] %s error: %s", id, msg, err)) + fmt.Printf("e|error|%s", err.Error()) +} + +func main() { + config := webrtc.Configuration{} + err := json.Unmarshal([]byte(os.Args[1]), &config) + if err != nil { + panic(err) + } + + peerConnection, err := webrtc.NewPeerConnection(config) + if err != nil { + panic(err) + } + + peerConnection.OnConnectionStateChange(func(state webrtc.PeerConnectionState) { + fmt.Println("e|connectionstatechange|" + state.String()) + }) + + peerConnection.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) { + fmt.Println("e|iceconnectionstatechange|" + state.String()) + }) + + peerConnection.OnICEGatheringStateChange(func(state webrtc.ICEGathererState) { + fmt.Println("e|icegatheringstatechange|" + state.String()) + }) + + peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) { + buf := make([]byte, 1400) + for { + i, _, readErr := track.Read(buf) + if readErr != nil { + panic(err) + } + println("[peer-connection-external] OnTrack data", i) + } + }) + + /* gatherComplete := webrtc.GatheringCompletePromise(peerConnection) + go func() { + <-gatherComplete + + b, err := json.Marshal(*peerConnection.LocalDescription()) + if err != nil { + panic(err) + } + fmt.Println("e||" + string(b)) + } */ + + // + channel := make(chan string) + go readStdin(channel) + + for { + msg := <-channel + commands := strings.SplitN(msg, "|", 3) + id := commands[0] + command := commands[1] + value := commands[2] + + //println(fmt.Sprintf("[peer-connection-external] command [%s] %s: \"%s\"", id, command, value)) + + switch { + case command == "close": + fmt.Println(id + "|" + command) + return + + case command == "addTransceiver": + var args map[string]interface{} + err = json.Unmarshal([]byte(value), &args) + if err != nil { + onError(id, command, err) + continue + } + trackOrKind := args["trackOrKind"].(string) + if trackOrKind == "audio" { + audioTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: "audio/opus"}, "audio", "a1") + if err != nil { + onError(id, command, err) + continue + } + _, err = peerConnection.AddTrack(audioTrack) + if err != nil { + onError(id, command, err) + continue + } + fmt.Println(id + "|" + command) + } else if trackOrKind == "video" { + videoTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: "video/vp8"}, "video", "v1") + if err != nil { + onError(id, command, err) + continue + } + _, err = peerConnection.AddTrack(videoTrack) + if err != nil { + onError(id, command, err) + continue + } + fmt.Println(id + "|" + command) + } else { + onError(id, command, errors.New("InvalidTrackOrKind")) + } + + case command == "createOffer": + offer, err := peerConnection.CreateOffer(nil) + if err != nil { + onError(id, command, err) + continue + } + err = peerConnection.SetLocalDescription(offer) + if err != nil { + onError(id, command, err) + continue + } + b, err := json.Marshal(offer) + if err != nil { + onError(id, command, err) + continue + } + fmt.Println(id + "|" + command + "|" + string(b)) + + case command == "createAnswer": + answer, err := peerConnection.CreateAnswer(nil) + if err != nil { + onError(id, command, err) + continue + } + gatherComplete := webrtc.GatheringCompletePromise(peerConnection) + err = peerConnection.SetLocalDescription(answer) + if err != nil { + onError(id, command, err) + continue + } + <-gatherComplete + b, err := json.Marshal(*peerConnection.LocalDescription()) + if err != nil { + onError(id, command, err) + continue + } + fmt.Println(id + "|" + command + "|" + string(b)) + + case command == "setLocalDescription": + offer := webrtc.SessionDescription{} + err = json.Unmarshal([]byte(value), &offer) + if err != nil { + onError(id, command, err) + continue + } + /* if err = peerConnection.SetLocalDescription(offer); err != nil { + onError(id, command, err) + continue + } */ + fmt.Println(id + "|" + command) + + case command == "setRemoteDescription": + answer := webrtc.SessionDescription{} + err = json.Unmarshal([]byte(value), &answer) + if err != nil { + onError(id, command, err) + continue + } + err = peerConnection.SetRemoteDescription(answer) + if err != nil { + onError(id, command, err) + continue + } + fmt.Println(id + "|" + command) + } + } +} + +func test() { + config := webrtc.Configuration{ + ICEServers: []webrtc.ICEServer{ + { + URLs: []string{"stun:stun.l.google.com:19302"}, + }, + }, + } + peerConnection, err := webrtc.NewPeerConnection(config) + if err != nil { + panic(err) + } + + peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) { + buf := make([]byte, 1400) + for { + i, _, readErr := track.Read(buf) + if readErr != nil { + panic(err) + } + fmt.Printf("Read %d", i) + } + }) + + // Set the handler for ICE connection state + peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) { + fmt.Printf("[connectionstatechange]%s\n", connectionState.String()) + }) + + // Create a audio track + audioTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: "audio/opus"}, "audio", "a1") + if err != nil { + panic(err) + } + _, err = peerConnection.AddTrack(audioTrack) + if err != nil { + panic(err) + } + + // Create a video track + firstVideoTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: "video/vp8"}, "video", "v1") + if err != nil { + panic(err) + } + _, err = peerConnection.AddTrack(firstVideoTrack) + if err != nil { + panic(err) + } + + // Create an offer + offer, err := peerConnection.CreateOffer(nil) + if err != nil { + panic(err) + } + + // Sets the LocalDescription, and starts our UDP listeners + // Note: this will start the gathering of ICE candidates + if err = peerConnection.SetLocalDescription(offer); err != nil { + panic(err) + } + + fmt.Println(offer) + + /* + // Wait for the offer to be pasted + offer := webrtc.SessionDescription{} + err = json.Unmarshal([]byte(signal.MustReadStdin()), &offer) + if err != nil { + panic(err) + } + + // Set the remote SessionDescription + err = peerConnection.SetRemoteDescription(offer) + if err != nil { + panic(err) + } + + // Create an answer + answer, err := peerConnection.CreateAnswer(nil) + if err != nil { + panic(err) + } + + // Create channel that is blocked until ICE Gathering is complete + gatherComplete := webrtc.GatheringCompletePromise(peerConnection) + + // Sets the LocalDescription, and starts our UDP listeners + err = peerConnection.SetLocalDescription(answer) + if err != nil { + panic(err) + } + + // Block until ICE Gathering is complete, disabling trickle ICE + // we do this because we only can exchange one signaling message + // in a production application you should exchange ICE Candidates via OnICECandidate + <-gatherComplete + + // Output the answer in base64 so we can paste it in browser + fmt.Println(signal.Encode(*peerConnection.LocalDescription())) */ + + // Block forever + select {} +} From 475c39a889d2074f4c34ef9ecc64ec9d5d1574af Mon Sep 17 00:00:00 2001 From: Vittorio Palmisano Date: Sun, 26 Feb 2023 17:03:06 +0100 Subject: [PATCH 3/7] Changed: peer-connection-external/main.go Scripts/peer-connection-external.js Src/utils.ts --- peer-connection-external/main.go | 231 +++++++--------------------- scripts/peer-connection-external.js | 82 ++++++---- src/utils.ts | 27 ++-- 3 files changed, 124 insertions(+), 216 deletions(-) diff --git a/peer-connection-external/main.go b/peer-connection-external/main.go index eb9f97a..3acb52d 100644 --- a/peer-connection-external/main.go +++ b/peer-connection-external/main.go @@ -3,12 +3,12 @@ package main import ( "bufio" "encoding/json" - "errors" "fmt" - "io" "os" "strings" + "time" + "github.com/pion/rtcp" "github.com/pion/webrtc/v3" ) @@ -19,10 +19,8 @@ func readStdin(channel chan string) { for { var err error in, err = r.ReadString('\n') - if err != io.EOF { - if err != nil { - panic(err) - } + if err != nil { + os.Exit(0) } in = strings.TrimSpace(in) if len(in) > 0 { @@ -33,9 +31,15 @@ func readStdin(channel chan string) { } } -func onError(id string, msg string, err error) { - println(fmt.Sprintf("[peer-connection-external] [%s] %s error: %s", id, msg, err)) - fmt.Printf("e|error|%s", err.Error()) +func onResult(id string, command string, value string) { + println(fmt.Sprintf("[peer-connection-external] [%s] %s", id, command)) + //println(fmt.Sprintf("[peer-connection-external] [%s] %s result: '%s'", id, command, value)) + fmt.Printf("r%s|%s|%s\n", id, command, value) +} + +func onError(id string, command string, err error) { + println(fmt.Sprintf("[peer-connection-external] [%s] %s error: %s", id, command, err)) + fmt.Printf("e%s|%s|%s\n", id, command, err.Error()) } func main() { @@ -44,52 +48,53 @@ func main() { if err != nil { panic(err) } - peerConnection, err := webrtc.NewPeerConnection(config) if err != nil { panic(err) } peerConnection.OnConnectionStateChange(func(state webrtc.PeerConnectionState) { - fmt.Println("e|connectionstatechange|" + state.String()) + onResult("ev", "connectionstatechange", state.String()) }) peerConnection.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) { - fmt.Println("e|iceconnectionstatechange|" + state.String()) + onResult("ev", "iceconnectionstatechange", state.String()) }) peerConnection.OnICEGatheringStateChange(func(state webrtc.ICEGathererState) { - fmt.Println("e|icegatheringstatechange|" + state.String()) + onResult("ev", "icegatheringstatechange", state.String()) }) peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) { - buf := make([]byte, 1400) + // Send a PLI on an interval so that the publisher is pushing a keyframe every rtcpPLIInterval + go func() { + ticker := time.NewTicker(time.Second * 5) + for range ticker.C { + rtcpSendErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(track.SSRC())}}) + if rtcpSendErr != nil { + fmt.Println(rtcpSendErr) + } + } + }() + + codecName := strings.Split(track.Codec().RTPCodecCapability.MimeType, "/")[1] + println(fmt.Sprintf("[peer-connection-external] OnTrack type %d: %s", track.PayloadType(), codecName)) + + /* buf := make([]byte, 1400) for { i, _, readErr := track.Read(buf) if readErr != nil { panic(err) } println("[peer-connection-external] OnTrack data", i) - } + } */ }) - /* gatherComplete := webrtc.GatheringCompletePromise(peerConnection) - go func() { - <-gatherComplete - - b, err := json.Marshal(*peerConnection.LocalDescription()) - if err != nil { - panic(err) - } - fmt.Println("e||" + string(b)) - } */ - // channel := make(chan string) go readStdin(channel) - for { - msg := <-channel + for msg := range channel { commands := strings.SplitN(msg, "|", 3) id := commands[0] command := commands[1] @@ -99,7 +104,7 @@ func main() { switch { case command == "close": - fmt.Println(id + "|" + command) + onResult(id, command, "") return case command == "addTransceiver": @@ -110,33 +115,12 @@ func main() { continue } trackOrKind := args["trackOrKind"].(string) - if trackOrKind == "audio" { - audioTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: "audio/opus"}, "audio", "a1") - if err != nil { - onError(id, command, err) - continue - } - _, err = peerConnection.AddTrack(audioTrack) - if err != nil { - onError(id, command, err) - continue - } - fmt.Println(id + "|" + command) - } else if trackOrKind == "video" { - videoTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: "video/vp8"}, "video", "v1") - if err != nil { - onError(id, command, err) - continue - } - _, err = peerConnection.AddTrack(videoTrack) - if err != nil { - onError(id, command, err) - continue - } - fmt.Println(id + "|" + command) - } else { - onError(id, command, errors.New("InvalidTrackOrKind")) + _, err := peerConnection.AddTransceiverFromKind(webrtc.NewRTPCodecType(trackOrKind)) + if err != nil { + onError(id, command, err) + continue } + onResult(id, command, "") case command == "createOffer": offer, err := peerConnection.CreateOffer(nil) @@ -154,7 +138,7 @@ func main() { onError(id, command, err) continue } - fmt.Println(id + "|" + command + "|" + string(b)) + onResult(id, command, string(b)) case command == "createAnswer": answer, err := peerConnection.CreateAnswer(nil) @@ -174,20 +158,20 @@ func main() { onError(id, command, err) continue } - fmt.Println(id + "|" + command + "|" + string(b)) + onResult(id, command, string(b)) - case command == "setLocalDescription": - offer := webrtc.SessionDescription{} - err = json.Unmarshal([]byte(value), &offer) - if err != nil { - onError(id, command, err) - continue - } - /* if err = peerConnection.SetLocalDescription(offer); err != nil { - onError(id, command, err) - continue - } */ - fmt.Println(id + "|" + command) + /* case command == "setLocalDescription": + offer := webrtc.SessionDescription{} + err = json.Unmarshal([]byte(value), &offer) + if err != nil { + onError(id, command, err) + continue + } + if err = peerConnection.SetLocalDescription(offer); err != nil { + onError(id, command, err) + continue + } + fmt.Println(id + "|" + command) */ case command == "setRemoteDescription": answer := webrtc.SessionDescription{} @@ -201,111 +185,12 @@ func main() { onError(id, command, err) continue } - fmt.Println(id + "|" + command) - } - } -} - -func test() { - config := webrtc.Configuration{ - ICEServers: []webrtc.ICEServer{ - { - URLs: []string{"stun:stun.l.google.com:19302"}, - }, - }, - } - peerConnection, err := webrtc.NewPeerConnection(config) - if err != nil { - panic(err) - } - - peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) { - buf := make([]byte, 1400) - for { - i, _, readErr := track.Read(buf) - if readErr != nil { - panic(err) + b, err := json.Marshal(*peerConnection.RemoteDescription()) + if err != nil { + onError(id, command, err) + continue } - fmt.Printf("Read %d", i) + onResult(id, command, string(b)) } - }) - - // Set the handler for ICE connection state - peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) { - fmt.Printf("[connectionstatechange]%s\n", connectionState.String()) - }) - - // Create a audio track - audioTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: "audio/opus"}, "audio", "a1") - if err != nil { - panic(err) } - _, err = peerConnection.AddTrack(audioTrack) - if err != nil { - panic(err) - } - - // Create a video track - firstVideoTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: "video/vp8"}, "video", "v1") - if err != nil { - panic(err) - } - _, err = peerConnection.AddTrack(firstVideoTrack) - if err != nil { - panic(err) - } - - // Create an offer - offer, err := peerConnection.CreateOffer(nil) - if err != nil { - panic(err) - } - - // Sets the LocalDescription, and starts our UDP listeners - // Note: this will start the gathering of ICE candidates - if err = peerConnection.SetLocalDescription(offer); err != nil { - panic(err) - } - - fmt.Println(offer) - - /* - // Wait for the offer to be pasted - offer := webrtc.SessionDescription{} - err = json.Unmarshal([]byte(signal.MustReadStdin()), &offer) - if err != nil { - panic(err) - } - - // Set the remote SessionDescription - err = peerConnection.SetRemoteDescription(offer) - if err != nil { - panic(err) - } - - // Create an answer - answer, err := peerConnection.CreateAnswer(nil) - if err != nil { - panic(err) - } - - // Create channel that is blocked until ICE Gathering is complete - gatherComplete := webrtc.GatheringCompletePromise(peerConnection) - - // Sets the LocalDescription, and starts our UDP listeners - err = peerConnection.SetLocalDescription(answer) - if err != nil { - panic(err) - } - - // Block until ICE Gathering is complete, disabling trickle ICE - // we do this because we only can exchange one signaling message - // in a production application you should exchange ICE Candidates via OnICECandidate - <-gatherComplete - - // Output the answer in base64 so we can paste it in browser - fmt.Println(signal.Encode(*peerConnection.LocalDescription())) */ - - // Block forever - select {} } diff --git a/scripts/peer-connection-external.js b/scripts/peer-connection-external.js index fcdc395..222587a 100644 --- a/scripts/peer-connection-external.js +++ b/scripts/peer-connection-external.js @@ -2,9 +2,11 @@ window.RTCPeerConnection = class { #pendingTasks = [] - connectionState = '' - iceConnectionState = '' + + connectionState = 'new' + iceConnectionState = 'new' localDescription = null + remoteDescription = null constructor(options) { log(`RTCPeerConnection`, options) @@ -36,12 +38,17 @@ window.RTCPeerConnection = class { }) } + async addTask(p) { + this.#pendingTasks.push(p) + return p + } + async waitPendingTasks() { - log( + /* log( `RTCPeerConnection-${this.id} waitPendingTasks ${ this.#pendingTasks.length }`, - ) + ) */ for (const p of this.#pendingTasks.splice(0, this.#pendingTasks.length)) { try { await p @@ -71,10 +78,9 @@ window.RTCPeerConnection = class { ) } - async addTransceiver(trackOrKind, init) { + addTransceiver(trackOrKind, init) { log(`RTCPeerConnection-${this.id} addTransceiver`, { trackOrKind, init }) - await this.waitPendingTasks() - this.#pendingTasks.push( + this.addTask( window.callPeerConnectionExternalMethod( this.id, 'addTransceiver', @@ -86,28 +92,37 @@ window.RTCPeerConnection = class { ) } + getTransceivers() { + log(`RTCPeerConnection-${this.id} getTransceivers`) + return [] + } + async createOffer(options) { log(`RTCPeerConnection-${this.id} createOffer`, JSON.stringify(options)) await this.waitPendingTasks() - const ret = await window.callPeerConnectionExternalMethod( - this.id, - 'createOffer', - JSON.stringify(options), + const ret = await this.addTask( + window.callPeerConnectionExternalMethod( + this.id, + 'createOffer', + JSON.stringify(options), + ), ) - this.localDescription = ret - return JSON.parse(ret) + this.localDescription = JSON.parse(ret) + return this.localDescription } async createAnswer(options) { log(`RTCPeerConnection-${this.id} createAnswer`, JSON.stringify(options)) await this.waitPendingTasks() - const ret = await window.callPeerConnectionExternalMethod( - this.id, - 'createAnswer', - JSON.stringify(options), + const ret = await this.addTask( + window.callPeerConnectionExternalMethod( + this.id, + 'createAnswer', + JSON.stringify(options), + ), ) - this.localDescription = ret - return JSON.parse(ret) + this.localDescription = JSON.parse(ret) + return this.localDescription } async setLocalDescription(description) { @@ -116,11 +131,14 @@ window.RTCPeerConnection = class { JSON.stringify(description), ) await this.waitPendingTasks() - await window.callPeerConnectionExternalMethod( - this.id, - 'setLocalDescription', - JSON.stringify(description), - ) + /* + await this.addTask( + window.callPeerConnectionExternalMethod( + this.id, + 'setLocalDescription', + JSON.stringify(description), + ), + ) */ } async setRemoteDescription(description) { @@ -129,23 +147,25 @@ window.RTCPeerConnection = class { JSON.stringify(description), ) await this.waitPendingTasks() - await window.callPeerConnectionExternalMethod( - this.id, - 'setRemoteDescription', - JSON.stringify(description), + const ret = await this.addTask( + window.callPeerConnectionExternalMethod( + this.id, + 'setRemoteDescription', + JSON.stringify(description), + ), ) + this.remoteDescription = JSON.parse(ret) } async addStream(mediaStream) { log(`RTCPeerConnection-${this.id} addStream`, mediaStream) - await this.waitPendingTasks() - this.#pendingTasks.push( + /* this.addTask( window.callPeerConnectionExternalMethod( this.id, 'addStream', JSON.stringify(mediaStream), // TODO ), - ) + ) */ } async getStats() { diff --git a/src/utils.ts b/src/utils.ts index 4a900d3..6991eb7 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -760,7 +760,6 @@ export class Future { // export class PeerConnectionExternal extends EventEmitter { public readonly id: number - public connectionState = 'closed' private readonly process private static cache = new Map() private commandId = 0 @@ -785,7 +784,8 @@ export class PeerConnectionExternal extends EventEmitter { assert(this.process.pid, 'PeerConnectionExternal spawn failed') this.process.stdout.on('data', data => { - //log.debug(`PeerConnectionExternal-${this.id} stdout: "${data}"`) + log.debug(`PeerConnectionExternal-${this.id} [stdout] "${data}"`) + this.stdoutBuf += String(data) while (this.stdoutBuf.length) { const index = this.stdoutBuf.indexOf('\n') @@ -796,24 +796,28 @@ export class PeerConnectionExternal extends EventEmitter { this.stdoutBuf = this.stdoutBuf.slice(index + 1) const [sid, name, value] = line.split('|', 3) - log.debug( + /* log.debug( `PeerConnectionExternal-${this.id} > [${sid}] ${name}: "${value}"`, - ) - if (sid === 'e') { - if (name === 'connectionstatechange') { - this.connectionState = value - } + ) */ + if (sid.startsWith('rev')) { this.emit('event', { name, value, }) - } else { - const id = parseInt(sid) + } else if (sid.startsWith('r')) { + const id = parseInt(sid.slice(1)) const command = this.commands.get(id) if (command) { this.commands.delete(id) command.resolve(value) } + } else if (sid.startsWith('e')) { + const id = parseInt(sid.slice(1)) + const command = this.commands.get(id) + if (command) { + this.commands.delete(id) + command.reject(new Error(value)) + } } } }) @@ -827,10 +831,9 @@ export class PeerConnectionExternal extends EventEmitter { `PeerConnectionExternal-${this.id} process exited with code ${code}`, ) PeerConnectionExternal.cache.delete(this.id) - this.connectionState = 'closed' this.emit('event', { name: 'connectionstatechange', - value: this.connectionState, + value: 'closed', }) this.stdoutBuf = '' PeerConnectionExternal.cache.delete(this.id) From e8a64dd5eee2041421c1f3e6c73d5b3bb36a66d5 Mon Sep 17 00:00:00 2001 From: Vittorio Palmisano Date: Mon, 27 Feb 2023 23:41:37 +0100 Subject: [PATCH 4/7] Changed: peer-connection-external/main.go Scripts/peer-connection-external.js --- peer-connection-external/main.go | 32 +++++++++--- scripts/peer-connection-external.js | 75 ++++++++++++++++++++++------- 2 files changed, 82 insertions(+), 25 deletions(-) diff --git a/peer-connection-external/main.go b/peer-connection-external/main.go index 3acb52d..2714a6b 100644 --- a/peer-connection-external/main.go +++ b/peer-connection-external/main.go @@ -6,9 +6,8 @@ import ( "fmt" "os" "strings" - "time" - "github.com/pion/rtcp" + "github.com/pion/interceptor" "github.com/pion/webrtc/v3" ) @@ -43,12 +42,24 @@ func onError(id string, command string, err error) { } func main() { + m := &webrtc.MediaEngine{} + if err := m.RegisterDefaultCodecs(); err != nil { + panic(err) + } + i := &interceptor.Registry{} + if err := webrtc.RegisterDefaultInterceptors(m, i); err != nil { + panic(err) + } + settingEngine := webrtc.SettingEngine{} + settingEngine.SetLite(true) + api := webrtc.NewAPI(webrtc.WithMediaEngine(m), webrtc.WithInterceptorRegistry(i), webrtc.WithSettingEngine(settingEngine)) + config := webrtc.Configuration{} err := json.Unmarshal([]byte(os.Args[1]), &config) if err != nil { panic(err) } - peerConnection, err := webrtc.NewPeerConnection(config) + peerConnection, err := api.NewPeerConnection(config) if err != nil { panic(err) } @@ -67,7 +78,7 @@ func main() { peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) { // Send a PLI on an interval so that the publisher is pushing a keyframe every rtcpPLIInterval - go func() { + /* go func() { ticker := time.NewTicker(time.Second * 5) for range ticker.C { rtcpSendErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(track.SSRC())}}) @@ -75,19 +86,19 @@ func main() { fmt.Println(rtcpSendErr) } } - }() + }() */ codecName := strings.Split(track.Codec().RTPCodecCapability.MimeType, "/")[1] println(fmt.Sprintf("[peer-connection-external] OnTrack type %d: %s", track.PayloadType(), codecName)) - /* buf := make([]byte, 1400) + buf := make([]byte, 1400) for { i, _, readErr := track.Read(buf) if readErr != nil { panic(err) } println("[peer-connection-external] OnTrack data", i) - } */ + } }) // @@ -171,7 +182,12 @@ func main() { onError(id, command, err) continue } - fmt.Println(id + "|" + command) */ + b, err := json.Marshal(*peerConnection.LocalDescription()) + if err != nil { + onError(id, command, err) + continue + } + onResult(id, command, string(b)) */ case command == "setRemoteDescription": answer := webrtc.SessionDescription{} diff --git a/scripts/peer-connection-external.js b/scripts/peer-connection-external.js index 222587a..a67ba78 100644 --- a/scripts/peer-connection-external.js +++ b/scripts/peer-connection-external.js @@ -1,5 +1,26 @@ /* global log, PeerConnections */ +const FakeAudioMediaStreamTrack = () => { + const ctx = new AudioContext() + const oscillator = ctx.createOscillator() + const dst = oscillator.connect(ctx.createMediaStreamDestination()) + oscillator.start() + return Object.assign(dst.stream.getAudioTracks()[0], { enabled: false }) +} + +const FakeVideoMediaStreamTrack = ({ width = 640, height = 480 } = {}) => { + const canvas = Object.assign(document.createElement('canvas'), { + width, + height, + }) + canvas.getContext('2d').fillRect(0, 0, width, height) + const stream = canvas.captureStream() + return Object.assign(stream.getVideoTracks()[0], { enabled: false }) +} + +const fakeAudioTrack = FakeAudioMediaStreamTrack() +const fakeVideoTrack = FakeVideoMediaStreamTrack() + window.RTCPeerConnection = class { #pendingTasks = [] @@ -7,6 +28,7 @@ window.RTCPeerConnection = class { iceConnectionState = 'new' localDescription = null remoteDescription = null + transceivers = [] constructor(options) { log(`RTCPeerConnection`, options) @@ -28,14 +50,6 @@ window.RTCPeerConnection = class { PeerConnections.delete(this.id) } }) - - this.addEventListener('iceconnectionstatechange', iceConnectionState => { - log(`RTCPeerConnection iceconnectionstatechange`, iceConnectionState) - this.iceConnectionState = iceConnectionState - if (iceConnectionState === 'closed') { - PeerConnections.delete(this.id) - } - }) } async addTask(p) { @@ -58,10 +72,11 @@ window.RTCPeerConnection = class { } } - async close() { + close() { log(`RTCPeerConnection-${this.id} close`) - await this.waitPendingTasks() - await window.callPeerConnectionExternalMethod(this.id, 'close') + this.waitPendingTasks().then( + window.callPeerConnectionExternalMethod(this.id, 'close'), + ) } addEventListener(name, cb) { @@ -93,8 +108,8 @@ window.RTCPeerConnection = class { } getTransceivers() { - log(`RTCPeerConnection-${this.id} getTransceivers`) - return [] + log(`RTCPeerConnection-${this.id} getTransceivers`, this.transceivers) + return this.transceivers } async createOffer(options) { @@ -122,6 +137,31 @@ window.RTCPeerConnection = class { ), ) this.localDescription = JSON.parse(ret) + + const sections = [] + for (const line of this.localDescription.sdp.split('\r\n')) { + if (line.startsWith('m=')) { + const kind = line.replace('m=', '').split(' ')[0] + sections.push({ kind }) + } else if (line.startsWith('a=mid:')) { + const mid = line.replace('a=mid:', '') + sections[sections.length - 1].mid = mid + } + } + sections.forEach(({ mid, kind }) => { + if (this.transceivers.findIndex(t => t.mid === mid) === -1) { + this.transceivers.push({ + mid, + receiver: { + track: kind === 'audio' ? fakeAudioTrack : fakeVideoTrack, + getStats: () => { + return [] + }, + }, + }) + } + }) + return this.localDescription } @@ -131,14 +171,15 @@ window.RTCPeerConnection = class { JSON.stringify(description), ) await this.waitPendingTasks() - /* - await this.addTask( + /* const ret = await this.addTask( window.callPeerConnectionExternalMethod( this.id, 'setLocalDescription', JSON.stringify(description), ), - ) */ + ) + this.localDescription = JSON.parse(ret) + return this.localDescription */ } async setRemoteDescription(description) { @@ -170,6 +211,6 @@ window.RTCPeerConnection = class { async getStats() { log(`RTCPeerConnection-${this.id} getStats`) - return {} + return [] } } From 6bc0c386bb00c0cab3dcbb826bd1064806b8557d Mon Sep 17 00:00:00 2001 From: Vittorio Palmisano Date: Tue, 28 Feb 2023 13:21:19 +0100 Subject: [PATCH 5/7] Changed: scripts/peer-connection-external.js --- scripts/peer-connection-external.js | 50 ++++++++++++++--------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/scripts/peer-connection-external.js b/scripts/peer-connection-external.js index a67ba78..9859b63 100644 --- a/scripts/peer-connection-external.js +++ b/scripts/peer-connection-external.js @@ -137,31 +137,6 @@ window.RTCPeerConnection = class { ), ) this.localDescription = JSON.parse(ret) - - const sections = [] - for (const line of this.localDescription.sdp.split('\r\n')) { - if (line.startsWith('m=')) { - const kind = line.replace('m=', '').split(' ')[0] - sections.push({ kind }) - } else if (line.startsWith('a=mid:')) { - const mid = line.replace('a=mid:', '') - sections[sections.length - 1].mid = mid - } - } - sections.forEach(({ mid, kind }) => { - if (this.transceivers.findIndex(t => t.mid === mid) === -1) { - this.transceivers.push({ - mid, - receiver: { - track: kind === 'audio' ? fakeAudioTrack : fakeVideoTrack, - getStats: () => { - return [] - }, - }, - }) - } - }) - return this.localDescription } @@ -196,6 +171,31 @@ window.RTCPeerConnection = class { ), ) this.remoteDescription = JSON.parse(ret) + + // Add fake transceivers. + const sections = [] + for (const line of this.remoteDescription.sdp.split('\r\n')) { + if (line.startsWith('m=')) { + const kind = line.replace('m=', '').split(' ')[0] + sections.push({ kind }) + } else if (line.startsWith('a=mid:')) { + const mid = line.replace('a=mid:', '') + sections[sections.length - 1].mid = mid + } + } + sections.forEach(({ mid, kind }) => { + if (this.transceivers.findIndex(t => t.mid === mid) === -1) { + this.transceivers.push({ + mid, + receiver: { + track: kind === 'audio' ? fakeAudioTrack : fakeVideoTrack, + getStats: () => { + return [] + }, + }, + }) + } + }) } async addStream(mediaStream) { From 8f86e408105142b5584fc5eb6011390c75c9b698 Mon Sep 17 00:00:00 2001 From: Vittorio Palmisano Date: Wed, 15 Mar 2023 20:29:05 +0100 Subject: [PATCH 6/7] Changed: package.json Peer-connection-external/main.go Src/utils.ts --- package.json | 2 +- peer-connection-external/main.go | 2 +- src/utils.ts | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 7a3ce34..23ba5a3 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "webrtcperf": "app.min.js" }, "scripts": { - "prepare": "yarn lint && tsc -b && webpack", + "prepare": "yarn lint && tsc -b && webpack && cd peer-connection-external && go build", "prepublishOnly": "yarn lint", "postversion": "git push && git push origin $(git tag | sort -V | tail -1)", "release": "yarn && yarn update-docs && npm version patch && npm publish", diff --git a/peer-connection-external/main.go b/peer-connection-external/main.go index 2714a6b..9cd9913 100644 --- a/peer-connection-external/main.go +++ b/peer-connection-external/main.go @@ -51,7 +51,7 @@ func main() { panic(err) } settingEngine := webrtc.SettingEngine{} - settingEngine.SetLite(true) + // settingEngine.SetLite(true) api := webrtc.NewAPI(webrtc.WithMediaEngine(m), webrtc.WithInterceptorRegistry(i), webrtc.WithSettingEngine(settingEngine)) config := webrtc.Configuration{} diff --git a/src/utils.ts b/src/utils.ts index 6991eb7..0b4c6fe 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -854,6 +854,7 @@ export class PeerConnectionExternal extends EventEmitter { PeerConnectionExternal.cache.clear() } + // eslint-disable-next-line @typescript-eslint/no-explicit-any sendCommand(name: string, value = ''): Promise { const id = this.commandId log.debug(`PeerConnectionExternal-${this.id} < [${id}] ${name}: "${value}"`) From 10491a69142eb49c98afee25eb02f56f7193821f Mon Sep 17 00:00:00 2001 From: Vittorio Palmisano Date: Tue, 23 May 2023 17:18:35 +0200 Subject: [PATCH 7/7] Changed: peer-connection-external/main.go Scripts/peer-connection-external.js Src/utils.ts --- peer-connection-external/main.go | 63 +++--- .../peer-connection-external.py | 211 ++++++++++++++++++ scripts/peer-connection-external.js | 60 +++-- src/utils.ts | 6 +- 4 files changed, 284 insertions(+), 56 deletions(-) create mode 100644 peer-connection-external/peer-connection-external.py diff --git a/peer-connection-external/main.go b/peer-connection-external/main.go index 9cd9913..cc35e32 100644 --- a/peer-connection-external/main.go +++ b/peer-connection-external/main.go @@ -3,6 +3,7 @@ package main import ( "bufio" "encoding/json" + "errors" "fmt" "os" "strings" @@ -31,8 +32,8 @@ func readStdin(channel chan string) { } func onResult(id string, command string, value string) { - println(fmt.Sprintf("[peer-connection-external] [%s] %s", id, command)) - //println(fmt.Sprintf("[peer-connection-external] [%s] %s result: '%s'", id, command, value)) + //println(fmt.Sprintf("[peer-connection-external] [%s] %s", id, command)) + println(fmt.Sprintf("[peer-connection-external] [%s] %s result: '%s'", id, command, value)) fmt.Printf("r%s|%s|%s\n", id, command, value) } @@ -46,6 +47,7 @@ func main() { if err := m.RegisterDefaultCodecs(); err != nil { panic(err) } + i := &interceptor.Registry{} if err := webrtc.RegisterDefaultInterceptors(m, i); err != nil { panic(err) @@ -59,6 +61,7 @@ func main() { if err != nil { panic(err) } + peerConnection, err := api.NewPeerConnection(config) if err != nil { panic(err) @@ -139,7 +142,13 @@ func main() { onError(id, command, err) continue } - err = peerConnection.SetLocalDescription(offer) + b, err := json.Marshal(offer) + if err != nil { + onError(id, command, err) + continue + } + onResult(id, command, string(b)) + /* err = peerConnection.SetLocalDescription(offer) if err != nil { onError(id, command, err) continue @@ -149,7 +158,7 @@ func main() { onError(id, command, err) continue } - onResult(id, command, string(b)) + onResult(id, command, string(b)) */ case command == "createAnswer": answer, err := peerConnection.CreateAnswer(nil) @@ -157,13 +166,13 @@ func main() { onError(id, command, err) continue } - gatherComplete := webrtc.GatheringCompletePromise(peerConnection) + //gatherComplete := webrtc.GatheringCompletePromise(peerConnection) err = peerConnection.SetLocalDescription(answer) if err != nil { onError(id, command, err) continue } - <-gatherComplete + //<-gatherComplete b, err := json.Marshal(*peerConnection.LocalDescription()) if err != nil { onError(id, command, err) @@ -171,42 +180,36 @@ func main() { } onResult(id, command, string(b)) - /* case command == "setLocalDescription": - offer := webrtc.SessionDescription{} - err = json.Unmarshal([]byte(value), &offer) - if err != nil { - onError(id, command, err) - continue - } - if err = peerConnection.SetLocalDescription(offer); err != nil { - onError(id, command, err) - continue - } - b, err := json.Marshal(*peerConnection.LocalDescription()) - if err != nil { - onError(id, command, err) - continue - } - onResult(id, command, string(b)) */ - - case command == "setRemoteDescription": - answer := webrtc.SessionDescription{} - err = json.Unmarshal([]byte(value), &answer) + case command == "setLocalDescription": + sdp := webrtc.SessionDescription{} + err = json.Unmarshal([]byte(value), &sdp) if err != nil { onError(id, command, err) continue } - err = peerConnection.SetRemoteDescription(answer) + /* if err = peerConnection.SetLocalDescription(sdp); err != nil { + onError(id, command, err) + continue + } */ + onResult(id, command, "") + + case command == "setRemoteDescription": + sdp := webrtc.SessionDescription{} + err = json.Unmarshal([]byte(value), &sdp) if err != nil { onError(id, command, err) continue } - b, err := json.Marshal(*peerConnection.RemoteDescription()) + err = peerConnection.SetRemoteDescription(sdp) if err != nil { onError(id, command, err) continue } - onResult(id, command, string(b)) + onResult(id, command, "") + + default: + onError(id, command, errors.New("command not found")) + continue } } } diff --git a/peer-connection-external/peer-connection-external.py b/peer-connection-external/peer-connection-external.py new file mode 100644 index 0000000..d209b84 --- /dev/null +++ b/peer-connection-external/peer-connection-external.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python + +import gi +import sys +import signal +import os +import logging +import json + +gi.require_version('Gst', '1.0') +gi.require_version('GstBase', '1.0') +gi.require_version('GObject', '2.0') +gi.require_version('GstWebRTC', '1.0') +gi.require_version('GstSdp', '1.0') +gi.require_version('Gio', '2.0') +from gi.repository import GObject, Gst, GLib, Gio +from gi.repository import GstWebRTC +from gi.repository import GstSdp + +logging.basicConfig(level=logging.DEBUG) + +class PeerConnectionExternal: + logger = logging.getLogger('PeerConnectionExternal') + _connection_states = { + GstWebRTC.WebRTCPeerConnectionState.NEW: 'new', + GstWebRTC.WebRTCPeerConnectionState.CONNECTING: 'connecting', + GstWebRTC.WebRTCPeerConnectionState.CONNECTED: 'connected', + GstWebRTC.WebRTCPeerConnectionState.DISCONNECTED: 'disconnected', + GstWebRTC.WebRTCPeerConnectionState.FAILED: 'failed', + GstWebRTC.WebRTCPeerConnectionState.CLOSED: 'closed', + } + + def __init__(self): + self.logger.debug('init') + self.pipeline = Gst.parse_bin_from_description(''' + webrtcbin name=webrtcbin bundle-policy=max-bundle latency=200 + + audiotestsrc is-live=true wave=silence num-buffers=100 ! opusenc ! rtpopuspay ! webrtcbin. + ''', False) + self.webrtcbin = self.pipeline.get_by_name('webrtcbin') + self.webrtcbin.connect('pad-added', self.on_pad_added) + self.webrtcbin.connect('notify::connection-state', self.on_connection_state_changed) + self.pipeline.set_state(Gst.State.PLAYING) + + def on_connection_state_changed(self, webrtcbin, value): + state = webrtcbin.get_property('connection-state') + state = self._connection_states[state] + self.logger.debug('on_connection_state_changed: %s', state) + print('rev|connectionstatechange|%s' %state, file=sys.stdout, flush=True) + + def on_pad_added(self, webrtcbin, pad): + self.logger.info('on_pad_added: %s', pad) + if pad.direction != Gst.PadDirection.SRC: + return + decodebin = Gst.ElementFactory.make('decodebin', 'decodebin') + decodebin.connect('pad-added', self.on_incoming_decodebin_stream) + self.pipeline.add(decodebin) + self.webrtcbin.link(decodebin) + decodebin.sync_state_with_parent() + + def on_incoming_decodebin_stream(self, decodebin, pad): + if not pad.has_current_caps(): + self.logger.warn('pad has no caps, ignoring') + return + caps = pad.get_current_caps() + self.logger.info('on_incoming_decodebin_stream: %s', caps.to_string()) + + # + def add_transceiver(self, value, cb): + value = json.loads(value) + self.logger.debug('add_transceiver %s', value) + kind = value['trackOrKind'] + if kind == 'audio': + caps = Gst.caps_from_string('application/x-rtp,media=audio,encoding-name=OPUS,clock-rate=48000,payload=96,ssrc=1') + elif kind == 'video': + caps = Gst.caps_from_string('application/x-rtp,media=video,encoding-name=VP9,clock-rate=90000,payload=101,ssrc=2') + self.webrtcbin.emit('add-transceiver', GstWebRTC.WebRTCRTPTransceiverDirection.RECVONLY, caps) + cb() + + def set_remote_description(self, offer, cb): + offer = json.loads(offer) + self.logger.debug('set_remote_description %s', offer) + res, sdpmsg = GstSdp.SDPMessage.new() + GstSdp.sdp_message_parse_buffer(bytes(offer['sdp'].encode()), sdpmsg) + offer = GstWebRTC.WebRTCSessionDescription.new(GstWebRTC.WebRTCSDPType.OFFER, sdpmsg) + + def on_remote_description_set(promise): + promise.wait() + reply = promise.get_reply() + self.logger.debug('on_remote_description_set: %s', reply) + cb() + self.webrtcbin.emit('set-remote-description', offer, Gst.Promise.new_with_change_func(on_remote_description_set)) + + def set_local_description(self, answer, cb): + answer = json.loads(answer) + self.logger.debug('set_local_description: %s', answer) + res, sdpmsg = GstSdp.SDPMessage.new() + GstSdp.sdp_message_parse_buffer(bytes(answer['sdp'].encode()), sdpmsg) + answer = GstWebRTC.WebRTCSessionDescription.new(GstWebRTC.WebRTCSDPType.ANSWER, sdpmsg) + + def on_local_description_set(promise): + promise.wait() + reply = promise.get_reply() + self.logger.debug('on_local_description_set: %s', reply) + cb() + self.webrtcbin.emit('set-local-description', answer, Gst.Promise.new_with_change_func(on_local_description_set)) + + def create_offer(self, cb): + self.logger.debug('create_offer') + def on_offer_created(promise): + promise.wait() + reply = promise.get_reply() + offer = reply.get_value('offer') + sdp = offer.sdp.as_text() + self.logger.debug('on_offer_created: %s', sdp) + cb(json.dumps({ 'sdp': sdp, 'type': 'offer' })) + self.webrtcbin.emit('create-offer', None, Gst.Promise.new_with_change_func(on_offer_created)) + + def create_answer(self, cb): + self.logger.debug('create_answer') + def on_answer_created(promise): + promise.wait() + reply = promise.get_reply() + answer = reply.get_value('answer') + sdp = answer.sdp.as_text() + self.logger.debug('on_answer_created: %s', sdp) + cb(json.dumps({ 'sdp': sdp, 'type': 'answer' })) + # self.webrtcbin.emit('set-local-description', answer, Gst.Promise.new_with_change_func(on_local_description_set)) + #def on_local_description_set(promise): + # promise.wait() + # reply = promise.get_reply() + # localDescription = self.webrtcbin.get_property('local-description') + # sdp = localDescription.sdp.as_text() + # self.logger.debug('on_local_description_set: %s', sdp) + # cb(json.dumps({ 'sdp': sdp, 'type': 'answer' })) + self.webrtcbin.emit('create-answer', None, Gst.Promise.new_with_change_func(on_answer_created)) + + def stop(self): + self.logger.debug('stop') + if self.pipeline: + self.pipeline.set_state(Gst.State.NULL) + self.pipeline = None + self.webrtcbin = None + +if __name__=='__main__': + Gst.init(sys.argv) + logger = logging.getLogger('app') + + pc = PeerConnectionExternal() + + def callback(stream, task, buf): + data = stream.read_bytes_finish(task).get_data().decode() + buf += data + + while len(buf) > 0 and buf.find('\n') != -1: + pos = buf.find('\n') + cmd = buf[0:pos] + buf = buf[pos+1:] + + [id, command, value] = cmd.strip().split('|') + logger.debug('command: [%s] %s "%s"', id, command, value) + + def reply(ret=''): + logger.debug('reply [%s] %s: "%s"', id, command, ret) + print('r%s|%s|%s' % (id, command, ret), file=sys.stdout, flush=True) + + def error(ret=''): + logger.debug('error [%s] %s: "%s"', id, command, ret) + print('e%s|%s|%s' % (id, command, ret), file=sys.stdout, flush=True) + + try: + if command == 'close': + on_exit() + return + elif command == 'addTransceiver': + pc.add_transceiver(value, reply) + elif command == 'setRemoteDescription': + pc.set_remote_description(value, reply) + elif command == 'setLocalDescription': + pc.set_local_description(value, reply) + elif command == 'createAnswer': + pc.create_answer(reply) + elif command == 'createOffer': + pc.create_offer(reply) + except Exception as e: + error(str(e)) + + read_next(stream, buf) + + def read_next(stream, buf): + stream.read_bytes_async(1024, GLib.PRIORITY_DEFAULT, None, callback, buf) + + stream = Gio.UnixInputStream.new(sys.stdin.fileno(), False) + buf = '' + read_next(stream, buf) + + def on_exit(signum=-1, frame=None): + logger.info('Exiting (%d)', signum) + try: + pc.stop() + except Exception as e: + logger.error('Error stopping: %s', e) + sys.exit(-1) + else: + sys.exit(0) + signal.signal(signal.SIGINT, on_exit) + + try: + GLib.MainLoop().run() + except: + on_exit() diff --git a/scripts/peer-connection-external.js b/scripts/peer-connection-external.js index 9859b63..80f103b 100644 --- a/scripts/peer-connection-external.js +++ b/scripts/peer-connection-external.js @@ -50,14 +50,19 @@ window.RTCPeerConnection = class { PeerConnections.delete(this.id) } }) + + this.addEventListener('iceconnectionstatechange', connectionState => { + log(`RTCPeerConnection iceconnectionstatechange`, connectionState) + this.iceConnectionState = connectionState + }) } - async addTask(p) { + async #addTask(p) { this.#pendingTasks.push(p) return p } - async waitPendingTasks() { + async #waitPendingTasks() { /* log( `RTCPeerConnection-${this.id} waitPendingTasks ${ this.#pendingTasks.length @@ -74,7 +79,7 @@ window.RTCPeerConnection = class { close() { log(`RTCPeerConnection-${this.id} close`) - this.waitPendingTasks().then( + this.#waitPendingTasks().then( window.callPeerConnectionExternalMethod(this.id, 'close'), ) } @@ -95,7 +100,7 @@ window.RTCPeerConnection = class { addTransceiver(trackOrKind, init) { log(`RTCPeerConnection-${this.id} addTransceiver`, { trackOrKind, init }) - this.addTask( + this.#addTask( window.callPeerConnectionExternalMethod( this.id, 'addTransceiver', @@ -114,22 +119,21 @@ window.RTCPeerConnection = class { async createOffer(options) { log(`RTCPeerConnection-${this.id} createOffer`, JSON.stringify(options)) - await this.waitPendingTasks() - const ret = await this.addTask( + await this.#waitPendingTasks() + const ret = await this.#addTask( window.callPeerConnectionExternalMethod( this.id, 'createOffer', JSON.stringify(options), ), ) - this.localDescription = JSON.parse(ret) - return this.localDescription + return JSON.parse(ret) } async createAnswer(options) { - log(`RTCPeerConnection-${this.id} createAnswer`, JSON.stringify(options)) - await this.waitPendingTasks() - const ret = await this.addTask( + log(`RTCPeerConnection-${this.id} createAnswer`, options) + await this.#waitPendingTasks() + const ret = await this.#addTask( window.callPeerConnectionExternalMethod( this.id, 'createAnswer', @@ -137,40 +141,43 @@ window.RTCPeerConnection = class { ), ) this.localDescription = JSON.parse(ret) + log( + `RTCPeerConnection-${this.id} createAnswer done, localDescription:`, + this.localDescription, + ) return this.localDescription } async setLocalDescription(description) { log( `RTCPeerConnection-${this.id} setLocalDescription`, - JSON.stringify(description), + description, + 'current localDescription:', + this.localDescription, ) - await this.waitPendingTasks() - /* const ret = await this.addTask( + await this.#waitPendingTasks() + await this.#addTask( window.callPeerConnectionExternalMethod( this.id, 'setLocalDescription', JSON.stringify(description), ), ) - this.localDescription = JSON.parse(ret) - return this.localDescription */ + this.localDescription = description } async setRemoteDescription(description) { - log( - `RTCPeerConnection-${this.id} setRemoteDescription`, - JSON.stringify(description), - ) - await this.waitPendingTasks() - const ret = await this.addTask( + log(`RTCPeerConnection-${this.id} setRemoteDescription`, description) + await this.#waitPendingTasks() + await this.#addTask( window.callPeerConnectionExternalMethod( this.id, 'setRemoteDescription', JSON.stringify(description), ), ) - this.remoteDescription = JSON.parse(ret) + log(`RTCPeerConnection-${this.id} setRemoteDescription done`, description) + this.remoteDescription = description // Add fake transceivers. const sections = [] @@ -200,7 +207,7 @@ window.RTCPeerConnection = class { async addStream(mediaStream) { log(`RTCPeerConnection-${this.id} addStream`, mediaStream) - /* this.addTask( + /* this.#addTask( window.callPeerConnectionExternalMethod( this.id, 'addStream', @@ -213,4 +220,9 @@ window.RTCPeerConnection = class { log(`RTCPeerConnection-${this.id} getStats`) return [] } + + getSenders() { + log(`RTCPeerConnection-${this.id} getSenders`) + return [] + } } diff --git a/src/utils.ts b/src/utils.ts index aca9cc0..02f630e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -803,7 +803,7 @@ export class PeerConnectionExternal extends EventEmitter { assert(this.process.pid, 'PeerConnectionExternal spawn failed') this.process.stdout.on('data', data => { - log.debug(`PeerConnectionExternal-${this.id} [stdout] "${data}"`) + //log.debug(`PeerConnectionExternal-${this.id} [stdout] "${data}"`) this.stdoutBuf += String(data) while (this.stdoutBuf.length) { @@ -842,7 +842,9 @@ export class PeerConnectionExternal extends EventEmitter { }) this.process.stderr.on('data', data => { - log.debug(`PeerConnectionExternal-${this.id} [stderr] "${data}"`) + log.debug( + `PeerConnectionExternal-${this.id} [stderr] "${String(data).trim()}"`, + ) }) this.process.on('close', code => {