Compare commits
No commits in common. "e10a4929107f61cbad01683d5866611c5cdd6fef" and "3e50e2bfb499e0e14f2ae1a2d39fdacf5986e503" have entirely different histories.
e10a492910
...
3e50e2bfb4
@ -1,12 +1,9 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@roamhq/wrtc": "0.8.0",
|
"@roamhq/wrtc": "0.8.0",
|
||||||
"node-dht-sensor": "^0.4.5",
|
|
||||||
"node-pre-gyp": "^0.17.0",
|
"node-pre-gyp": "^0.17.0",
|
||||||
"pigpio": "^3.3.1",
|
"ws": "^8.18.0",
|
||||||
"sass": "1.86.3",
|
"sass": "1.86.3"
|
||||||
"serialport": "^13.0.0",
|
|
||||||
"ws": "^8.18.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:scss": "npx sass src/static/css:dist/static/css",
|
"build:scss": "npx sass src/static/css:dist/static/css",
|
||||||
@ -19,7 +16,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.10.2",
|
"@types/node": "^22.10.2",
|
||||||
"@types/node-dht-sensor": "^0.4.2",
|
|
||||||
"@types/ws": "^8.5.13",
|
"@types/ws": "^8.5.13",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.8.2"
|
"typescript": "^5.8.2"
|
||||||
|
16
sketch.c
16
sketch.c
@ -1,16 +0,0 @@
|
|||||||
void setup() {
|
|
||||||
Serial.begin(9600);
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
int moisture0 = analogRead(A0);
|
|
||||||
delayMicroseconds(100);
|
|
||||||
analogRead(A1);
|
|
||||||
int moisture1 = analogRead(A1);
|
|
||||||
Serial.print("{\"A0\":");
|
|
||||||
Serial.print(moisture0);
|
|
||||||
Serial.print(",\"A1\":");
|
|
||||||
Serial.print(moisture1);
|
|
||||||
Serial.println("}");
|
|
||||||
delay(1000);
|
|
||||||
}
|
|
@ -1,7 +1,6 @@
|
|||||||
import * as http from "http";
|
import * as http from "http";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import IO, { ISensors } from "./io";
|
|
||||||
|
|
||||||
|
|
||||||
export default class HttpServer {
|
export default class HttpServer {
|
||||||
@ -11,7 +10,7 @@ export default class HttpServer {
|
|||||||
private root: string;
|
private root: string;
|
||||||
// public constructor(port: number, root: string, tune: (ch: string, adp?: number) => void, getChannels: ()=>string[], getSignal: (adapter:number)=>object) {
|
// public constructor(port: number, root: string, tune: (ch: string, adp?: number) => void, getChannels: ()=>string[], getSignal: (adapter:number)=>object) {
|
||||||
|
|
||||||
public constructor(port: number, root: string, io: IO) {
|
public constructor(port: number, root: string) {
|
||||||
this.port = port;
|
this.port = port;
|
||||||
this.root = root;
|
this.root = root;
|
||||||
this.httpServer = http.createServer((req, res) => {
|
this.httpServer = http.createServer((req, res) => {
|
||||||
@ -26,8 +25,8 @@ export default class HttpServer {
|
|||||||
switch (req.method) {
|
switch (req.method) {
|
||||||
case "GET":
|
case "GET":
|
||||||
switch (api) {
|
switch (api) {
|
||||||
case "sensors":
|
case "list":
|
||||||
body = JSON.stringify(io.getSensors());
|
// body = JSON.stringify(getChannels());
|
||||||
status = 200;
|
status = 200;
|
||||||
break;
|
break;
|
||||||
case "signal":
|
case "signal":
|
||||||
@ -40,7 +39,7 @@ export default class HttpServer {
|
|||||||
break;
|
break;
|
||||||
case "PUT":
|
case "PUT":
|
||||||
switch (api) {
|
switch (api) {
|
||||||
case "power":
|
case "tune":
|
||||||
const channel = decodeURIComponent(query[3]);
|
const channel = decodeURIComponent(query[3]);
|
||||||
const adapter = parseInt(url.searchParams.get('adapter'));
|
const adapter = parseInt(url.searchParams.get('adapter'));
|
||||||
// tune(channel, adapter);
|
// tune(channel, adapter);
|
||||||
|
78
src/io.ts
78
src/io.ts
@ -1,78 +0,0 @@
|
|||||||
import Serial from "./serial";
|
|
||||||
import * as pigpio from 'pigpio';
|
|
||||||
import * as dht from 'node-dht-sensor';
|
|
||||||
|
|
||||||
type ONOFF = 0 | 1;
|
|
||||||
const ON: ONOFF = 0
|
|
||||||
const OFF: ONOFF = 1
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface ISensors {
|
|
||||||
temperature: number;
|
|
||||||
lights: boolean;
|
|
||||||
heat: boolean
|
|
||||||
moisture0: number;
|
|
||||||
moisture1: number;
|
|
||||||
humidity: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class IO {
|
|
||||||
gpioLights: pigpio.Gpio
|
|
||||||
gpioHeat: pigpio.Gpio
|
|
||||||
serial: Serial;
|
|
||||||
lights: boolean;
|
|
||||||
heat: boolean;
|
|
||||||
getMoisture: () => ({ moisture0: number, moisture1: number });
|
|
||||||
|
|
||||||
public constructor(LIGHTS_GPIO = 17, HEAT_GPIO=22, DHT_GPIO = 27, DHT_MODEL = 22) {
|
|
||||||
const serial = new Serial()
|
|
||||||
this.getMoisture = serial.getMoisture.bind(serial);
|
|
||||||
this.serial = serial;
|
|
||||||
|
|
||||||
const readLights = new pigpio.Gpio(LIGHTS_GPIO, { mode: pigpio.Gpio.INPUT });
|
|
||||||
this.lights = readLights.digitalRead() == ON ? true : false
|
|
||||||
this.gpioLights = new pigpio.Gpio(LIGHTS_GPIO, { mode: pigpio.Gpio.OUTPUT });
|
|
||||||
|
|
||||||
const readHeat = new pigpio.Gpio(HEAT_GPIO, { mode: pigpio.Gpio.INPUT });
|
|
||||||
this.lights = readHeat.digitalRead() == ON ? true : false
|
|
||||||
this.gpioHeat = new pigpio.Gpio(HEAT_GPIO, { mode: pigpio.Gpio.OUTPUT });
|
|
||||||
|
|
||||||
|
|
||||||
dht.initialize(22, DHT_GPIO);
|
|
||||||
dht.setMaxRetries(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
public setPower(state: boolean, GPIO= 17) {
|
|
||||||
if(GPIO == 17){
|
|
||||||
this.lights = state;
|
|
||||||
this.gpioLights.digitalWrite(this.lights ? ON : OFF);
|
|
||||||
}
|
|
||||||
else if(GPIO == 22){
|
|
||||||
this.heat = state;
|
|
||||||
this.gpioHeat.digitalWrite(this.heat ? ON : OFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// public togglePower() {
|
|
||||||
// this.power = !this.power;
|
|
||||||
// this.gpio.digitalWrite(this.power ? ON : OFF);
|
|
||||||
// }
|
|
||||||
|
|
||||||
private round = (n) =>
|
|
||||||
(n * 100) / 100
|
|
||||||
|
|
||||||
public getSensors(): ISensors {
|
|
||||||
const { temperature, humidity } = dht.read(22, 27)
|
|
||||||
const { moisture0, moisture1 } = this.getMoisture()
|
|
||||||
return {
|
|
||||||
lights: this.lights,
|
|
||||||
heat: this.heat,
|
|
||||||
moisture0: moisture0,
|
|
||||||
moisture1: moisture1,
|
|
||||||
temperature: this.round(temperature),
|
|
||||||
humidity: this.round(humidity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
import * as serialport from 'serialport';
|
|
||||||
|
|
||||||
// interface ISerial {
|
|
||||||
// moisture: number;
|
|
||||||
// parser: serialport.ReadlineParser
|
|
||||||
// }
|
|
||||||
|
|
||||||
export default class Serial {
|
|
||||||
private moisture0: number;
|
|
||||||
private moisture1: number;
|
|
||||||
|
|
||||||
public constructor(path = '/dev/ttyUSB0', baudRate = 9600, delimiter = '\n') {
|
|
||||||
const port = new serialport.SerialPort({ path, baudRate });
|
|
||||||
const readline = new serialport.ReadlineParser({ delimiter });
|
|
||||||
const parser = port.pipe(readline);
|
|
||||||
parser.on('data', line => {
|
|
||||||
const data = JSON.parse(line)
|
|
||||||
const moisture0 = parseInt(data['A0'], 10);
|
|
||||||
const moisture1 = parseInt(data['A1'], 10);
|
|
||||||
if (!isNaN(moisture0) && !isNaN(moisture1)) {
|
|
||||||
this.moisture0 = moisture0;
|
|
||||||
this.moisture1 = moisture1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.error("Error reading analog data")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
port.on('error', err => {
|
|
||||||
console.error('Serial Error:', err.message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private scale = (value: number) => {
|
|
||||||
const percent = (value / 1024) * 100;
|
|
||||||
return Math.min(Math.max(percent, 0), 100); // Clamp to 0-100 just in case
|
|
||||||
}
|
|
||||||
|
|
||||||
public getMoisture() {
|
|
||||||
return { moisture0: this.scale(this.moisture0), moisture1: this.scale(this.moisture1) };
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
import HttpServer from './http';
|
import HttpServer from './http';
|
||||||
import IO, { ISensors } from './io';
|
|
||||||
import VideoSocket from './ws';
|
import VideoSocket from './ws';
|
||||||
|
|
||||||
const HTTP_PORT = process.env.HTTP_PORT ? parseInt(process.env.HTTP_PORT, 10) : 8080;
|
const HTTP_PORT = process.env.HTTP_PORT ? parseInt(process.env.HTTP_PORT, 10) : 8080;
|
||||||
@ -25,16 +24,8 @@ const TV_DEV_0 = process.env.TV_DEV_0 ?? '/dev/video0'
|
|||||||
// const getSignal = (adapter: number) =>
|
// const getSignal = (adapter: number) =>
|
||||||
// zap.getSignal(adapter)
|
// zap.getSignal(adapter)
|
||||||
|
|
||||||
const io = new IO();
|
const httpServer = new HttpServer(HTTP_PORT, STATIC_ROOT);
|
||||||
const getSensors: ()=>ISensors = io.getSensors.bind(io);
|
const videoSocket = new VideoSocket(WS_PORT, TV_DEV_0);
|
||||||
// setTimeout(()=>{
|
|
||||||
// console.log("sen ",io.getSensors())
|
|
||||||
// console.log("sen2 ",getSensors())
|
|
||||||
// },200)
|
|
||||||
// const setPower = io.setPower.bind(this, true)
|
|
||||||
// const togglePower = io.togglePower.bind(this)
|
|
||||||
const httpServer = new HttpServer(HTTP_PORT, STATIC_ROOT, io);
|
|
||||||
const videoSocket = new VideoSocket(WS_PORT, TV_DEV_0, getSensors);
|
|
||||||
|
|
||||||
httpServer.start();
|
httpServer.start();
|
||||||
|
|
||||||
@ -42,36 +33,17 @@ httpServer.start();
|
|||||||
process.stdin.setEncoding("utf8");
|
process.stdin.setEncoding("utf8");
|
||||||
process.stdin.resume();
|
process.stdin.resume();
|
||||||
|
|
||||||
console.log("Menu:\n1)Lights Power off\n2) Lights Power on\n3)Heat Power Off\n4) Heat power on\n5)Read Sensors");
|
console.log("Menu:");
|
||||||
|
|
||||||
process.stdin.on("data", async (data: string) => {
|
process.stdin.on("data", async (data: string) => {
|
||||||
const input = data.trim();
|
const input = data.trim();
|
||||||
console.log(`Received: "${input}"`);
|
console.log(`Received: "${input}"`);
|
||||||
const val = parseInt(input);
|
// await zap.zapTo(input).then((zap: IZap) => {
|
||||||
switch(val) {
|
// console.log(`Tuned ${zap.adapter} to ${zap.channel}`)
|
||||||
case 0:
|
|
||||||
|
|
||||||
break;
|
// }).catch((err: Error) => {
|
||||||
case 1:
|
// console.error(err.message);
|
||||||
io.setPower(false,17);
|
// });
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
io.setPower(true,17);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
io.setPower(false,22);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
io.setPower(true,22);
|
|
||||||
break;
|
|
||||||
|
|
||||||
|
|
||||||
case 5:
|
|
||||||
console.log("a ", getSensors())
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.log("No option for "+input)
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -18,18 +18,12 @@
|
|||||||
<video id="video0" autoplay playsinline controls></video>
|
<video id="video0" autoplay playsinline controls></video>
|
||||||
<div id="channel-container-0" class="channel-group"></div>
|
<div id="channel-container-0" class="channel-group"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="data-container">
|
|
||||||
<!-- <p id="power"></p>
|
|
||||||
<p id="moisture"></p>
|
|
||||||
<p id="temperature"></p>
|
|
||||||
<p id="humidity"></p> -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="js/video.js"></script>
|
<script src="js/video.js"></script>
|
||||||
<script src="js/api.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
@ -1,9 +0,0 @@
|
|||||||
const getSensors = () =>{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const poll = () =>{
|
|
||||||
fetch("/api/sensors").then(r=>r.json()).then(data =>{
|
|
||||||
console.log("data ",data)
|
|
||||||
})
|
|
||||||
}
|
|
@ -2,14 +2,6 @@ const isSecure = window.location.protocol === 'https:';
|
|||||||
const host = window.location.hostname;
|
const host = window.location.hostname;
|
||||||
const ws0builder = isSecure ? `wss://${host}/ws3` : `ws://${host}:3003`;
|
const ws0builder = isSecure ? `wss://${host}/ws3` : `ws://${host}:3003`;
|
||||||
const ws0 = new WebSocket(ws0builder);
|
const ws0 = new WebSocket(ws0builder);
|
||||||
|
|
||||||
interface IData {
|
|
||||||
power: boolean,
|
|
||||||
moisture: number,
|
|
||||||
temperature: number,
|
|
||||||
humidity: number
|
|
||||||
}
|
|
||||||
const dataContainer = document.getElementById('data-container') as HTMLDivElement
|
|
||||||
const config = {
|
const config = {
|
||||||
iceServers: [
|
iceServers: [
|
||||||
{
|
{
|
||||||
@ -19,7 +11,7 @@ const config = {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
const pc0 = new RTCPeerConnection(isSecure ? config : {});
|
const pc0 = new RTCPeerConnection(config);
|
||||||
const video0 = document.getElementById('video0') as HTMLVideoElement;
|
const video0 = document.getElementById('video0') as HTMLVideoElement;
|
||||||
|
|
||||||
|
|
||||||
@ -34,27 +26,6 @@ pc0.onicecandidate = ({ candidate }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Create the data channel (client initiates)
|
|
||||||
const dataChannel = pc0.createDataChannel('sensors');
|
|
||||||
console.log("📡 Data channel created by client");
|
|
||||||
|
|
||||||
dataChannel.onopen = () => {
|
|
||||||
console.log('📬 Client: Data channel opened');
|
|
||||||
};
|
|
||||||
|
|
||||||
dataChannel.onmessage = (event) => {
|
|
||||||
// console.log("📦 Client received message:", event.data);
|
|
||||||
const json = JSON.parse(event.data) ;
|
|
||||||
|
|
||||||
dataContainer.innerHTML = Object.entries(json).map(([k,v])=>`<p>${k}:${v}</p>`).join('')
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
dataChannel.onclose = () => {
|
|
||||||
console.log("📴 Client: Data channel closed");
|
|
||||||
};
|
|
||||||
|
|
||||||
ws0.onopen = async () => {
|
ws0.onopen = async () => {
|
||||||
pc0.addTransceiver('video', { direction: 'recvonly' });
|
pc0.addTransceiver('video', { direction: 'recvonly' });
|
||||||
pc0.addTransceiver('audio', { direction: 'recvonly' })
|
pc0.addTransceiver('audio', { direction: 'recvonly' })
|
||||||
@ -66,7 +37,6 @@ ws0.onopen = async () => {
|
|||||||
ws0.onmessage = async (message) => {
|
ws0.onmessage = async (message) => {
|
||||||
const msg = JSON.parse(message.data);
|
const msg = JSON.parse(message.data);
|
||||||
if (msg.type === 'answer') {
|
if (msg.type === 'answer') {
|
||||||
// pc0.data
|
|
||||||
await pc0.setRemoteDescription(msg.data);
|
await pc0.setRemoteDescription(msg.data);
|
||||||
}
|
}
|
||||||
else if (msg.type === 'ice-candidate') {
|
else if (msg.type === 'ice-candidate') {
|
||||||
@ -74,4 +44,3 @@ ws0.onmessage = async (message) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
51
src/ws.ts
51
src/ws.ts
@ -1,7 +1,6 @@
|
|||||||
import { MediaStream, MediaStreamTrack, nonstandard, RTCPeerConnection, RTCDataChannel } from '@roamhq/wrtc';
|
import { MediaStream, MediaStreamTrack, nonstandard, RTCPeerConnection } from '@roamhq/wrtc';
|
||||||
import { ChildProcessWithoutNullStreams, spawn } from 'child_process';
|
import { ChildProcessWithoutNullStreams, spawn } from 'child_process';
|
||||||
import * as ws from 'ws';
|
import * as ws from 'ws';
|
||||||
import { ISensors } from './io';
|
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
const WIDTH = 640; // Video width
|
const WIDTH = 640; // Video width
|
||||||
@ -10,7 +9,7 @@ const FRAME_SIZE = WIDTH * HEIGHT * 1.5; // YUV420P frame size
|
|||||||
|
|
||||||
export default class VideoSocket {
|
export default class VideoSocket {
|
||||||
videoDevice: string;
|
videoDevice: string;
|
||||||
public constructor(port: number, videoDevice, getSensors: () => ISensors) {
|
public constructor(port: number, videoDevice) {
|
||||||
this.videoDevice = videoDevice
|
this.videoDevice = videoDevice
|
||||||
const ffmpegProcess = this.startFFmpeg();
|
const ffmpegProcess = this.startFFmpeg();
|
||||||
const videoTrack = this.createVideoTrack(ffmpegProcess);
|
const videoTrack = this.createVideoTrack(ffmpegProcess);
|
||||||
@ -26,35 +25,11 @@ export default class VideoSocket {
|
|||||||
|
|
||||||
wss.on('connection', async (ws: ws.WebSocket) => {
|
wss.on('connection', async (ws: ws.WebSocket) => {
|
||||||
const peerConnection: RTCPeerConnection = this.createPeerConnection(videoTrack, audioTrack);
|
const peerConnection: RTCPeerConnection = this.createPeerConnection(videoTrack, audioTrack);
|
||||||
// The client created the data channel. The server should access it as follows:
|
|
||||||
peerConnection.ondatachannel = (event) => {
|
|
||||||
const dataChannel = event.channel; // This is the data channel created by the client
|
|
||||||
|
|
||||||
dataChannel.onopen = () => {
|
|
||||||
console.log('📬 Server: Data channel opened');
|
|
||||||
// Now you can send data through the channel
|
|
||||||
setInterval(() => {
|
|
||||||
const sensorData = getSensors(); // Example function to fetch data
|
|
||||||
dataChannel.send(JSON.stringify(sensorData)); // Send data to the client
|
|
||||||
}, 1000);
|
|
||||||
};
|
|
||||||
|
|
||||||
dataChannel.onmessage = (event) => {
|
|
||||||
console.log("📦 Server received message:", event.data);
|
|
||||||
};
|
|
||||||
|
|
||||||
dataChannel.onclose = () => {
|
|
||||||
console.log("📴 Server: Data channel closed");
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ws.on('message', async (message: Buffer) => {
|
ws.on('message', async (message: Buffer) => {
|
||||||
const { type, data } = JSON.parse(message.toString());
|
const { type, data } = JSON.parse(message.toString());
|
||||||
|
|
||||||
if (type == 'offer') {
|
if (type == 'offer') {
|
||||||
|
|
||||||
await peerConnection.setRemoteDescription(data);
|
await peerConnection.setRemoteDescription(data);
|
||||||
const answer = await peerConnection.createAnswer();
|
const answer = await peerConnection.createAnswer();
|
||||||
await peerConnection.setLocalDescription(answer);
|
await peerConnection.setLocalDescription(answer);
|
||||||
@ -206,28 +181,6 @@ export default class VideoSocket {
|
|||||||
return audioSource.createTrack();
|
return audioSource.createTrack();
|
||||||
}
|
}
|
||||||
|
|
||||||
createDataChannel = (peerConnection: RTCPeerConnection, getSensors: () => any) => {
|
|
||||||
const dataChannel = peerConnection.createDataChannel('sensors')
|
|
||||||
console.log("create data channel");
|
|
||||||
dataChannel.onopen = () => {
|
|
||||||
console.log('✅ Data channel is open');
|
|
||||||
// Send dummy JSON for testing
|
|
||||||
setInterval(() => {
|
|
||||||
const sensorData = getSensors(); // Assuming getSensors returns JSON
|
|
||||||
dataChannel.send(JSON.stringify(sensorData));
|
|
||||||
}, 1000);
|
|
||||||
};
|
|
||||||
|
|
||||||
dataChannel.onerror = (error) => {
|
|
||||||
console.error('❌ DataChannel error:', error);
|
|
||||||
};
|
|
||||||
|
|
||||||
dataChannel.onclose = () => {
|
|
||||||
console.log('❎ DataChannel closed');
|
|
||||||
};
|
|
||||||
return peerConnection;
|
|
||||||
}
|
|
||||||
|
|
||||||
createPeerConnection = (videoTrack: MediaStreamTrack, audioTrack: MediaStreamTrack): RTCPeerConnection => {
|
createPeerConnection = (videoTrack: MediaStreamTrack, audioTrack: MediaStreamTrack): RTCPeerConnection => {
|
||||||
const peerConnection = new RTCPeerConnection({
|
const peerConnection = new RTCPeerConnection({
|
||||||
iceServers: [
|
iceServers: [
|
||||||
|
Loading…
Reference in New Issue
Block a user