From d1ad73fbf06b43dce6446f0f819020a16fd9b391 Mon Sep 17 00:00:00 2001 From: david Date: Tue, 1 Apr 2025 18:07:45 -0700 Subject: [PATCH] more support for dual tuners --- src/server.ts | 7 ++-- src/static/js/video.ts | 75 ++++++++++++++++++++++++++---------------- src/ws.ts | 8 ++--- 3 files changed, 53 insertions(+), 37 deletions(-) diff --git a/src/server.ts b/src/server.ts index 1bb12a8..3c8c5be 100644 --- a/src/server.ts +++ b/src/server.ts @@ -2,13 +2,11 @@ import HttpServer from './http'; import TVWebSocket from './ws'; import Zap, { IZap } from './zap'; -import * as readline from 'readline'; - const HTTP_PORT = process.env.HTTP_PORT ? parseInt(process.env.HTTP_PORT, 10) : 8080; const WS_PORT = process.env.WS_PORT ? parseInt(process.env.WS_PORT, 10) : 3001; const STATIC_ROOT = process.cwd() + "/dist/static"; const TV_DEV_0 = process.env.TV_DEV_0 ?? '/dev/dvb/adapter0/dvr0' -const TV_DEV_1 = process.env.TV_DEV_1 ? '/dev/dvb/adapter0/dvr1' : null; +const TV_DEV_1 = process.env.TV_DEV_1 ?? '/dev/dvb/adapter0/dvr1'; const zap = new Zap(); @@ -26,7 +24,8 @@ const getChannels = () => zap.getChannels(); const httpServer = new HttpServer(HTTP_PORT, STATIC_ROOT, tune, getChannels); -const tvWebSocket = new TVWebSocket(WS_PORT); +const tvWebSocket0 = new TVWebSocket(WS_PORT, TV_DEV_0); +// const tvWebSocket1 = new TVWebSocket(WS_PORT + 1, TV_DEV_1); httpServer.start(); diff --git a/src/static/js/video.ts b/src/static/js/video.ts index 69312de..1b6e5d3 100644 --- a/src/static/js/video.ts +++ b/src/static/js/video.ts @@ -1,50 +1,67 @@ const host = window.location.hostname -const ws = new WebSocket(`ws://${host}:3001`); -const pc = new RTCPeerConnection({ iceServers: [] }); +const ws0 = new WebSocket(`ws://${host}:3001`); +const ws1 = new WebSocket(`ws://${host}:3002`); +const pc0 = new RTCPeerConnection({ iceServers: [] }); +const pc1 = new RTCPeerConnection({ iceServers: [] }); const video0 = document.getElementById('video0') as HTMLVideoElement; const video1 = document.getElementById('video1') as HTMLVideoElement; -pc.onconnectionstatechange = (event) => { - console.log("onconnectionstatechange ", event) -} - -pc.ondatachannel = (event) => { - console.log("ondatachannel ", event) -} - -pc.ontrack = (event) => { +// 0 +pc0.ontrack = (event) => { console.log("Received track event", event.streams); video0.srcObject = event.streams[0]; +}; + +pc0.onicecandidate = ({ candidate }) => { + if (candidate) { + ws0.send(JSON.stringify({ type: 'ice-candidate', data: candidate })); + } +}; + +ws0.onopen = async () => { + pc0.addTransceiver('video', { direction: 'recvonly' }); + pc0.addTransceiver('audio', { direction: 'recvonly' }) + const offer = await pc0.createOffer(); + await pc0.setLocalDescription(offer); + ws0.send(JSON.stringify({ type: 'offer', data: offer })); +} + +ws0.onmessage = async (message) => { + const msg = JSON.parse(message.data); + if (msg.type === 'answer') { + await pc0.setRemoteDescription(msg.data); + } + else if (msg.type === 'ice-candidate') { + await pc0.addIceCandidate(msg.data); + } +}; + +// 1 +pc1.ontrack = (event) => { + console.log("Received track event", event.streams); video1.srcObject = event.streams[0]; }; -pc.onicecandidate = ({ candidate }) => { +pc1.onicecandidate = ({ candidate }) => { if (candidate) { - ws.send(JSON.stringify({ type: 'ice-candidate', data: candidate })); // Use 'candidate' instead of 'ice-candidate' + ws1.send(JSON.stringify({ type: 'ice-candidate', data: candidate })); } }; -pc.onicegatheringstatechange = () => { - // console.log('ICE state:', pc.iceGatheringState); -}; -ws.onopen = async () => { - pc.addTransceiver('video', { direction: 'recvonly' }); - pc.addTransceiver('audio', { direction: 'recvonly' }) - const offer = await pc.createOffer(); - await pc.setLocalDescription(offer); - ws.send(JSON.stringify({ type: 'offer', data: offer })); +ws1.onopen = async () => { + pc1.addTransceiver('video', { direction: 'recvonly' }); + pc1.addTransceiver('audio', { direction: 'recvonly' }) + const offer = await pc1.createOffer(); + await pc1.setLocalDescription(offer); + ws1.send(JSON.stringify({ type: 'offer', data: offer })); } -ws.onmessage = async (message) => { +ws1.onmessage = async (message) => { const msg = JSON.parse(message.data); - if (msg.type === 'answer') { - await pc.setRemoteDescription(msg.data); + await pc1.setRemoteDescription(msg.data); } - else if (msg.type === 'ice-candidate') { - await pc.addIceCandidate(msg.data); + await pc1.addIceCandidate(msg.data); } }; - -; \ No newline at end of file diff --git a/src/ws.ts b/src/ws.ts index 1cb1ce9..6c3c416 100644 --- a/src/ws.ts +++ b/src/ws.ts @@ -3,14 +3,14 @@ import { ChildProcessWithoutNullStreams, spawn } from 'child_process'; import * as ws from 'ws'; // Constants -const VIDEO_DEVICE = '/dev/dvb/adapter0/dvr0'; // Video source device const WIDTH = 640; // Video width const HEIGHT = 480; // Video height const FRAME_SIZE = WIDTH * HEIGHT * 1.5; // YUV420p frame size (460800 bytes) export default class TVWebSocket { - - public constructor(port: number) { + videoDevice: string; + public constructor(port: number, videoDevice) { + this.videoDevice = videoDevice const ffmpegProcess = this.startFFmpeg(); const videoTrack = this.createVideoTrack(ffmpegProcess); const audioTrack = this.createAudioTrack(ffmpegProcess); @@ -62,7 +62,7 @@ export default class TVWebSocket { startFFmpeg = (): ChildProcessWithoutNullStreams => { const p = spawn('ffmpeg', [ '-loglevel', 'debug', - '-i', VIDEO_DEVICE, + '-i', this.videoDevice, // Video '-map', '0:v:0',