From 7cd97dc277c0d46a8a3f8bb8ef64054206d3b703 Mon Sep 17 00:00:00 2001 From: GLDuval Date: Thu, 4 May 2023 14:20:28 -0400 Subject: [PATCH 1/8] Start rtsp viewer --- package-lock.json | 41 ++++++++++++++++++- package.json | 3 ++ src/main/RtspServer/script.ts | 12 ++++++ src/main/index.ts | 1 + src/main/preload.ts | 3 ++ src/main/preload/api.ts | 6 +++ src/main/rtspServer.ts | 34 +++++++++++++++ .../components/Feed/Feeds/CameraFeed.tsx | 5 ++- .../Feed/Feeds/RTSPFeed/RTSPFeed.tsx | 32 +++++++++++++++ .../components/Feed/Feeds/RTSPFeed/index.tsx | 0 src/renderer/store/modules/feed.ts | 1 + src/renderer/types/jsmpeg.d.ts | 13 ++++++ src/renderer/types/nodeRtspStream.d.ts | 13 ++++++ 13 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 src/main/RtspServer/script.ts create mode 100644 src/main/rtspServer.ts create mode 100644 src/renderer/components/Feed/Feeds/RTSPFeed/RTSPFeed.tsx create mode 100644 src/renderer/components/Feed/Feeds/RTSPFeed/index.tsx create mode 100644 src/renderer/types/jsmpeg.d.ts create mode 100644 src/renderer/types/nodeRtspStream.d.ts diff --git a/package-lock.json b/package-lock.json index 40196950..e1f905fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "2.6.1", "hasInstallScript": true, "dependencies": { + "@cycjimmy/jsmpeg-player": "^6.0.5", "@reduxjs/toolkit": "^1.8.0", "@xstate/react": "^1.6.3", "chalk": "^5.0.1", @@ -17,8 +18,10 @@ "date-fns": "^2.28.0", "electron-log": "^4.4.6", "execa": "^6.1.0", + "jsmpeg": "^1.0.0", "lodash": "^4.17.21", "nanoid": "^3.3.1", + "node-rtsp-stream": "^0.0.9", "polished": "^4.1.4", "qr-scanner": "^1.4.1", "react": "^17.0.2", @@ -1344,6 +1347,11 @@ "node": ">=12" } }, + "node_modules/@cycjimmy/jsmpeg-player": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@cycjimmy/jsmpeg-player/-/jsmpeg-player-6.0.5.tgz", + "integrity": "sha512-bVNHQ7VN9ecKT5AI/6RC7zpW/y4ca68a9txeR5Wiin+jKpUn/7buMe+5NPub89A8NNeNnKPQfrD2+c76ch36mA==" + }, "node_modules/@develar/schema-utils": { "version": "2.6.5", "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", @@ -10826,6 +10834,11 @@ "node": ">=4" } }, + "node_modules/jsmpeg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jsmpeg/-/jsmpeg-1.0.0.tgz", + "integrity": "sha512-wlBKWVJ93NRJaCfrJ1KAgpMvZBLzpZxH3wnC1Yj7DudMDa/5hHeL1HfvW48ndR8GlI4irrqCXuOGhgayP9EbHw==" + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -11659,6 +11672,14 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==" }, + "node_modules/node-rtsp-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/node-rtsp-stream/-/node-rtsp-stream-0.0.9.tgz", + "integrity": "sha512-ynSkdHL4fuhctl1GeK890De7n8Dw+37D6IAZGrzsFSrd4TYho6neFQpMS1t0ZRDGsAegKh2p6kl1l9Vo3pJk8w==", + "dependencies": { + "ws": "^7.0.0" + } + }, "node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -15580,7 +15601,6 @@ "version": "7.5.5", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", - "dev": true, "engines": { "node": ">=8.3.0" }, @@ -16693,6 +16713,11 @@ "@cspotcode/source-map-consumer": "0.8.0" } }, + "@cycjimmy/jsmpeg-player": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@cycjimmy/jsmpeg-player/-/jsmpeg-player-6.0.5.tgz", + "integrity": "sha512-bVNHQ7VN9ecKT5AI/6RC7zpW/y4ca68a9txeR5Wiin+jKpUn/7buMe+5NPub89A8NNeNnKPQfrD2+c76ch36mA==" + }, "@develar/schema-utils": { "version": "2.6.5", "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", @@ -24004,6 +24029,11 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, + "jsmpeg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jsmpeg/-/jsmpeg-1.0.0.tgz", + "integrity": "sha512-wlBKWVJ93NRJaCfrJ1KAgpMvZBLzpZxH3wnC1Yj7DudMDa/5hHeL1HfvW48ndR8GlI4irrqCXuOGhgayP9EbHw==" + }, "json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -24657,6 +24687,14 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==" }, + "node-rtsp-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/node-rtsp-stream/-/node-rtsp-stream-0.0.9.tgz", + "integrity": "sha512-ynSkdHL4fuhctl1GeK890De7n8Dw+37D6IAZGrzsFSrd4TYho6neFQpMS1t0ZRDGsAegKh2p6kl1l9Vo3pJk8w==", + "requires": { + "ws": "^7.0.0" + } + }, "nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -27626,7 +27664,6 @@ "version": "7.5.5", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", - "dev": true, "requires": {} }, "xml-name-validator": { diff --git a/package.json b/package.json index de490af0..fc055448 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "extends": "electron-snowpack/config/electron-builder.js" }, "dependencies": { + "@cycjimmy/jsmpeg-player": "^6.0.5", "@reduxjs/toolkit": "^1.8.0", "@xstate/react": "^1.6.3", "chalk": "^5.0.1", @@ -53,8 +54,10 @@ "date-fns": "^2.28.0", "electron-log": "^4.4.6", "execa": "^6.1.0", + "jsmpeg": "^1.0.0", "lodash": "^4.17.21", "nanoid": "^3.3.1", + "node-rtsp-stream": "^0.0.9", "polished": "^4.1.4", "qr-scanner": "^1.4.1", "react": "^17.0.2", diff --git a/src/main/RtspServer/script.ts b/src/main/RtspServer/script.ts new file mode 100644 index 00000000..f6157fb6 --- /dev/null +++ b/src/main/RtspServer/script.ts @@ -0,0 +1,12 @@ +import RTSPServer from 'node-rtsp-stream'; + +new RTSPServer.Stream({ + name: 'name', + streamUrl: 'rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4', + wsPort: 9999, + ffmpegOptions: { + // options ffmpeg flags + '-stats': '', // an option with no neccessary value uses a blank string + '-r': 30, // options with required values specify the value after the key + }, +}); diff --git a/src/main/index.ts b/src/main/index.ts index 0ad30773..7035c089 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,3 +1,4 @@ +import '@/main/rtspServer'; import '@/main/audio'; import { log } from '@/main/logger'; import { APP_INFO_QUERY, APP_INFO_TYPE, AUDIO_STOP } from '@/main/preload'; diff --git a/src/main/preload.ts b/src/main/preload.ts index 97e5e72a..d6212c97 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -18,6 +18,9 @@ export type AUDIO_MSG_TYPE = { stdout: string; }; +export const RTSP_START = 'rtsp_start'; +export const RTSP_STOP = 'rtsp_stop'; + if (contextBridge) { // This is done this way because otherwise tests would try to import preload and it wouldn't work // It's possible the issue is mostly in the import order. diff --git a/src/main/preload/api.ts b/src/main/preload/api.ts index 06af67fb..f8237e5b 100644 --- a/src/main/preload/api.ts +++ b/src/main/preload/api.ts @@ -7,6 +7,8 @@ import { AUDIO_STOP, LOG_MSG, LOG_MSG_TYPE, + RTSP_START, + RTSP_STOP, } from '@/main/preload'; import { ipcRenderer } from 'electron'; @@ -29,4 +31,8 @@ export const preload = { ipcRenderer.on(AUDIO_MSG, (_event, args) => cb(args as AUDIO_MSG_TYPE)); }, }, + rtsp: { + start: (url: string) => ipcRenderer.invoke(RTSP_START, url), + stop: () => ipcRenderer.send(RTSP_STOP), + }, }; diff --git a/src/main/rtspServer.ts b/src/main/rtspServer.ts new file mode 100644 index 00000000..1f527ac8 --- /dev/null +++ b/src/main/rtspServer.ts @@ -0,0 +1,34 @@ +import { log } from '@/main/logger'; +import { ipcMain } from 'electron'; +import { execaNode, ExecaChildProcess } from 'execa'; + +interface RtspProcess { + process: ExecaChildProcess; + wsPort: number; +} + +const rtspServers: Map = new Map(); +let nextPort = 9000; + +ipcMain.handle('rtsp_start', (event, url: string) => { + const process = execaNode('script.ts'); + + log.info('starting rtsp process'); + rtspServers.set(url, { + process, + wsPort: nextPort, + }); + + return nextPort++; +}); + +ipcMain.on('rtsp_stop', (event, url: string) => { + const rtspProcess = rtspServers.get(url); + + if (!rtspProcess) { + return; + } + + rtspProcess.process.kill(); + rtspServers.delete(url); +}); diff --git a/src/renderer/components/Feed/Feeds/CameraFeed.tsx b/src/renderer/components/Feed/Feeds/CameraFeed.tsx index 4eaabe0b..d3b999a8 100644 --- a/src/renderer/components/Feed/Feeds/CameraFeed.tsx +++ b/src/renderer/components/Feed/Feeds/CameraFeed.tsx @@ -9,6 +9,7 @@ import * as React from 'react'; import { FC, useEffect, useRef, useState } from 'react'; import { log } from '@/renderer/logger'; import { QRFeed } from './QRFeed/QRFeed'; +import { RTSPFeed } from './RTSPFeed/RTSPFeed'; interface Props { feed: ICameraFeed; @@ -145,6 +146,8 @@ const View: FC = ({ feed }) => { /> ); + case CameraType.RTSP: + return ; default: return ; } @@ -153,7 +156,7 @@ const View: FC = ({ feed }) => { export const CameraFeed: FC = ({ feed }) => { const [state] = useActor(rosService); const connected = - state.matches('connected') || feed.camera.type === CameraType.WEBCAM; + !state.matches('connected') || feed.camera.type === CameraType.WEBCAM; useEffect(() => { log.debug('mounting camera', feed.camera.name); return () => { diff --git a/src/renderer/components/Feed/Feeds/RTSPFeed/RTSPFeed.tsx b/src/renderer/components/Feed/Feeds/RTSPFeed/RTSPFeed.tsx new file mode 100644 index 00000000..f17608ac --- /dev/null +++ b/src/renderer/components/Feed/Feeds/RTSPFeed/RTSPFeed.tsx @@ -0,0 +1,32 @@ +import React, { useEffect, useRef } from 'react'; +import JSMpeg from '@cycjimmy/jsmpeg-player'; +import { log } from '@/renderer/logger'; + +export const RTSPFeed = () => { + // Execute rtspServer node script with stream url as argument + const videoRef = useRef(null); + useEffect(() => { + const startServer = async () => { + const port = (await window.preloadApi.rtsp.start('rtsp://')) as number; + log.info(`RTSP server started on port ${port}`); + }; + startServer().catch((e) => log.error(e)); + }, []); + + // Create video element + useEffect(() => { + const videoWrapper = videoRef.current; + if (videoWrapper) { + new JSMpeg.VideoElement(videoWrapper, 'ws://localhost:9999', { + autoplay: true, + audio: false, + }); + } + }, [videoRef]); + + return ( + <> +
+ + ); +}; diff --git a/src/renderer/components/Feed/Feeds/RTSPFeed/index.tsx b/src/renderer/components/Feed/Feeds/RTSPFeed/index.tsx new file mode 100644 index 00000000..e69de29b diff --git a/src/renderer/store/modules/feed.ts b/src/renderer/store/modules/feed.ts index a23ca883..2b996d83 100644 --- a/src/renderer/store/modules/feed.ts +++ b/src/renderer/store/modules/feed.ts @@ -31,6 +31,7 @@ export enum CameraType { VP8 = 'vp8', WEBCAM = 'webcam', QR_CODE = 'qr_code', + RTSP = 'rtsp', } export type FeedType = diff --git a/src/renderer/types/jsmpeg.d.ts b/src/renderer/types/jsmpeg.d.ts new file mode 100644 index 00000000..5dfae4a9 --- /dev/null +++ b/src/renderer/types/jsmpeg.d.ts @@ -0,0 +1,13 @@ +declare module '@cycjimmy/jsmpeg-player' { + class VideoElement { + constructor( + videoWrapper: HTMLDivElement, + url: string, + options: { + canvas?: HTMLCanvasElement; + autoplay?: boolean; + audio?: boolean; + } + ); + } +} diff --git a/src/renderer/types/nodeRtspStream.d.ts b/src/renderer/types/nodeRtspStream.d.ts new file mode 100644 index 00000000..b5a4e5e3 --- /dev/null +++ b/src/renderer/types/nodeRtspStream.d.ts @@ -0,0 +1,13 @@ +declare module 'node-rtsp-stream' { + class Stream { + constructor(params: { + name: string; + streamUrl: string; + wsPort: number; + ffmpegOptions: { + '-stats': string; + '-r': number; + }; + }); + } +} From 9e6461d7308ce8d744ca94bacd5db94dfc9f1f39 Mon Sep 17 00:00:00 2001 From: GLDuval Date: Fri, 5 May 2023 10:46:36 -0400 Subject: [PATCH 2/8] Working rtsp stream viewer draft --- package-lock.json | 48 ++++++++++++++++- package.json | 1 - .../script.ts => script/rtspServer.js | 13 +++-- snowpack.config.js | 3 +- src/main/preload/api.ts | 2 +- src/main/rtspServer.ts | 25 ++++++--- .../components/Feed/Feeds/CameraFeed.tsx | 4 +- .../components/Feed/Feeds/RTSPFeed.tsx | 51 +++++++++++++++++++ .../Feed/Feeds/RTSPFeed/RTSPFeed.tsx | 32 ------------ .../components/Feed/Feeds/RTSPFeed/index.tsx | 0 src/renderer/types/jsmpeg.d.ts | 2 + src/renderer/types/nodeRtspStream.d.ts | 13 ----- 12 files changed, 131 insertions(+), 63 deletions(-) rename src/main/RtspServer/script.ts => script/rtspServer.js (53%) create mode 100644 src/renderer/components/Feed/Feeds/RTSPFeed.tsx delete mode 100644 src/renderer/components/Feed/Feeds/RTSPFeed/RTSPFeed.tsx delete mode 100644 src/renderer/components/Feed/Feeds/RTSPFeed/index.tsx delete mode 100644 src/renderer/types/nodeRtspStream.d.ts diff --git a/package-lock.json b/package-lock.json index e1f905fb..a26ceafa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,10 +18,10 @@ "date-fns": "^2.28.0", "electron-log": "^4.4.6", "execa": "^6.1.0", - "jsmpeg": "^1.0.0", "lodash": "^4.17.21", "nanoid": "^3.3.1", "node-rtsp-stream": "^0.0.9", + "node-rtsp-stream-es6": "^1.0.7", "polished": "^4.1.4", "qr-scanner": "^1.4.1", "react": "^17.0.2", @@ -4616,6 +4616,11 @@ "node": ">=0.12.0" } }, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -11680,6 +11685,23 @@ "ws": "^7.0.0" } }, + "node_modules/node-rtsp-stream-es6": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/node-rtsp-stream-es6/-/node-rtsp-stream-es6-1.0.7.tgz", + "integrity": "sha512-UsOt0Ouj6ZY6AG4QMsDBtttUq2+0JfwdptW/bCjOKh3a1FmGmBLLgmMYZIi1V3uu++MBkZT7NWarJ0tKw+YdYw==", + "dependencies": { + "jsmpeg": "^1.0.0", + "ws": "7.1.2" + } + }, + "node_modules/node-rtsp-stream-es6/node_modules/ws": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.1.2.tgz", + "integrity": "sha512-gftXq3XI81cJCgkUiAVixA0raD9IVmXqsylCrjRygw4+UOOGzPoxnQ6r/CnVL9i+mDncJo94tSkyrtuuQVBmrg==", + "dependencies": { + "async-limiter": "^1.0.0" + } + }, "node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -19317,6 +19339,11 @@ "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", "dev": true }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -24695,6 +24722,25 @@ "ws": "^7.0.0" } }, + "node-rtsp-stream-es6": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/node-rtsp-stream-es6/-/node-rtsp-stream-es6-1.0.7.tgz", + "integrity": "sha512-UsOt0Ouj6ZY6AG4QMsDBtttUq2+0JfwdptW/bCjOKh3a1FmGmBLLgmMYZIi1V3uu++MBkZT7NWarJ0tKw+YdYw==", + "requires": { + "jsmpeg": "^1.0.0", + "ws": "7.1.2" + }, + "dependencies": { + "ws": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.1.2.tgz", + "integrity": "sha512-gftXq3XI81cJCgkUiAVixA0raD9IVmXqsylCrjRygw4+UOOGzPoxnQ6r/CnVL9i+mDncJo94tSkyrtuuQVBmrg==", + "requires": { + "async-limiter": "^1.0.0" + } + } + } + }, "nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", diff --git a/package.json b/package.json index fc055448..bade374e 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,6 @@ "date-fns": "^2.28.0", "electron-log": "^4.4.6", "execa": "^6.1.0", - "jsmpeg": "^1.0.0", "lodash": "^4.17.21", "nanoid": "^3.3.1", "node-rtsp-stream": "^0.0.9", diff --git a/src/main/RtspServer/script.ts b/script/rtspServer.js similarity index 53% rename from src/main/RtspServer/script.ts rename to script/rtspServer.js index f6157fb6..8eed879d 100644 --- a/src/main/RtspServer/script.ts +++ b/script/rtspServer.js @@ -1,9 +1,12 @@ -import RTSPServer from 'node-rtsp-stream'; +const Stream = require('node-rtsp-stream'); -new RTSPServer.Stream({ - name: 'name', - streamUrl: 'rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4', - wsPort: 9999, +// Get node args +const args = process.argv.slice(2); + +new Stream({ + name: 'RTSP-Stream', + streamUrl: args[0], + wsPort: parseInt(args[1]), ffmpegOptions: { // options ffmpeg flags '-stats': '', // an option with no neccessary value uses a blank string diff --git a/snowpack.config.js b/snowpack.config.js index 726b3dc8..8819a19a 100644 --- a/snowpack.config.js +++ b/snowpack.config.js @@ -2,6 +2,7 @@ module.exports = { extends: 'electron-snowpack/config/snowpack.js', mount: { 'src/shared': '/shared', + script: '/script', }, alias: { '@/': './src/', @@ -12,4 +13,4 @@ module.exports = { packageOptions: { knownEntrypoints: ['date-fns/fp/format', 'react-is'], }, -} +}; diff --git a/src/main/preload/api.ts b/src/main/preload/api.ts index f8237e5b..4283cbec 100644 --- a/src/main/preload/api.ts +++ b/src/main/preload/api.ts @@ -33,6 +33,6 @@ export const preload = { }, rtsp: { start: (url: string) => ipcRenderer.invoke(RTSP_START, url), - stop: () => ipcRenderer.send(RTSP_STOP), + stop: (url: string) => ipcRenderer.send(RTSP_STOP, url), }, }; diff --git a/src/main/rtspServer.ts b/src/main/rtspServer.ts index 1f527ac8..933e2a3b 100644 --- a/src/main/rtspServer.ts +++ b/src/main/rtspServer.ts @@ -1,6 +1,8 @@ import { log } from '@/main/logger'; -import { ipcMain } from 'electron'; -import { execaNode, ExecaChildProcess } from 'execa'; +import { app, ipcMain } from 'electron'; +import { ExecaChildProcess, execa } from 'execa'; +import path from 'path'; +import { isDev } from '@/main/isDev'; interface RtspProcess { process: ExecaChildProcess; @@ -8,18 +10,26 @@ interface RtspProcess { } const rtspServers: Map = new Map(); -let nextPort = 9000; + +// Stack array of ports from (9000 to 9060) to use for rtsp servers +const ports = Array.from({ length: 61 }, (_, i) => i + 9000); ipcMain.handle('rtsp_start', (event, url: string) => { - const process = execaNode('script.ts'); + const nextPort = ports.shift() ?? 9000; + const process = execa('node', [ + isDev + ? './script/rtspServer.js' + : path.join(app.getAppPath(), '../renderer/script/rtspServer.js'), + url, + nextPort.toString(), + ]); - log.info('starting rtsp process'); rtspServers.set(url, { process, wsPort: nextPort, }); - return nextPort++; + return nextPort; }); ipcMain.on('rtsp_stop', (event, url: string) => { @@ -28,7 +38,8 @@ ipcMain.on('rtsp_stop', (event, url: string) => { if (!rtspProcess) { return; } - + log.info('stopping rtsp process'); + ports.push(rtspProcess.wsPort); rtspProcess.process.kill(); rtspServers.delete(url); }); diff --git a/src/renderer/components/Feed/Feeds/CameraFeed.tsx b/src/renderer/components/Feed/Feeds/CameraFeed.tsx index d3b999a8..d52165d8 100644 --- a/src/renderer/components/Feed/Feeds/CameraFeed.tsx +++ b/src/renderer/components/Feed/Feeds/CameraFeed.tsx @@ -9,7 +9,7 @@ import * as React from 'react'; import { FC, useEffect, useRef, useState } from 'react'; import { log } from '@/renderer/logger'; import { QRFeed } from './QRFeed/QRFeed'; -import { RTSPFeed } from './RTSPFeed/RTSPFeed'; +import { RTSPFeed } from './RTSPFeed'; interface Props { feed: ICameraFeed; @@ -147,7 +147,7 @@ const View: FC = ({ feed }) => { ); case CameraType.RTSP: - return ; + return ; default: return ; } diff --git a/src/renderer/components/Feed/Feeds/RTSPFeed.tsx b/src/renderer/components/Feed/Feeds/RTSPFeed.tsx new file mode 100644 index 00000000..eb4d668f --- /dev/null +++ b/src/renderer/components/Feed/Feeds/RTSPFeed.tsx @@ -0,0 +1,51 @@ +import React, { useEffect, useRef } from 'react'; +import JSMpeg from '@cycjimmy/jsmpeg-player'; +import { log } from '@/renderer/logger'; + +interface Props { + url: string; +} + +export const RTSPFeed = ({ url }: Props) => { + const videoRef = useRef(null); + const canvasRef = useRef(null); + + useEffect(() => { + log.info(`Starting RTSP server for ${url}`); + const videoWrapper = videoRef.current; + const canvas = canvasRef.current; + let videoElement: JSMpeg.VideoElement | null = null; + const startServer = async () => { + const port = (await window.preloadApi.rtsp.start(url)) as number; + log.info(`RTSP server started on port ${port}`); + if (videoWrapper && canvas) { + videoElement = new JSMpeg.VideoElement( + videoWrapper, + `ws://localhost:${port}`, + { + canvas, + autoplay: true, + audio: false, + } + ); + } + }; + startServer().catch((e) => log.error(e)); + + return () => { + log.info('Stopping RTSP server'); + window.preloadApi.rtsp.stop(url); + if (videoElement) { + videoElement.destroy(); + } + }; + }, [url]); + + return ( + <> +
+ +
+ + ); +}; diff --git a/src/renderer/components/Feed/Feeds/RTSPFeed/RTSPFeed.tsx b/src/renderer/components/Feed/Feeds/RTSPFeed/RTSPFeed.tsx deleted file mode 100644 index f17608ac..00000000 --- a/src/renderer/components/Feed/Feeds/RTSPFeed/RTSPFeed.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React, { useEffect, useRef } from 'react'; -import JSMpeg from '@cycjimmy/jsmpeg-player'; -import { log } from '@/renderer/logger'; - -export const RTSPFeed = () => { - // Execute rtspServer node script with stream url as argument - const videoRef = useRef(null); - useEffect(() => { - const startServer = async () => { - const port = (await window.preloadApi.rtsp.start('rtsp://')) as number; - log.info(`RTSP server started on port ${port}`); - }; - startServer().catch((e) => log.error(e)); - }, []); - - // Create video element - useEffect(() => { - const videoWrapper = videoRef.current; - if (videoWrapper) { - new JSMpeg.VideoElement(videoWrapper, 'ws://localhost:9999', { - autoplay: true, - audio: false, - }); - } - }, [videoRef]); - - return ( - <> -
- - ); -}; diff --git a/src/renderer/components/Feed/Feeds/RTSPFeed/index.tsx b/src/renderer/components/Feed/Feeds/RTSPFeed/index.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/src/renderer/types/jsmpeg.d.ts b/src/renderer/types/jsmpeg.d.ts index 5dfae4a9..ad338839 100644 --- a/src/renderer/types/jsmpeg.d.ts +++ b/src/renderer/types/jsmpeg.d.ts @@ -9,5 +9,7 @@ declare module '@cycjimmy/jsmpeg-player' { audio?: boolean; } ); + + destroy(): void; } } diff --git a/src/renderer/types/nodeRtspStream.d.ts b/src/renderer/types/nodeRtspStream.d.ts deleted file mode 100644 index b5a4e5e3..00000000 --- a/src/renderer/types/nodeRtspStream.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -declare module 'node-rtsp-stream' { - class Stream { - constructor(params: { - name: string; - streamUrl: string; - wsPort: number; - ffmpegOptions: { - '-stats': string; - '-r': number; - }; - }); - } -} From 72ed7037a21959963da81526055251018548e03a Mon Sep 17 00:00:00 2001 From: GLDuval Date: Fri, 5 May 2023 15:47:39 -0400 Subject: [PATCH 3/8] Remove debug change --- src/renderer/components/Feed/Feeds/CameraFeed.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/components/Feed/Feeds/CameraFeed.tsx b/src/renderer/components/Feed/Feeds/CameraFeed.tsx index d52165d8..2051209e 100644 --- a/src/renderer/components/Feed/Feeds/CameraFeed.tsx +++ b/src/renderer/components/Feed/Feeds/CameraFeed.tsx @@ -156,7 +156,7 @@ const View: FC = ({ feed }) => { export const CameraFeed: FC = ({ feed }) => { const [state] = useActor(rosService); const connected = - !state.matches('connected') || feed.camera.type === CameraType.WEBCAM; + state.matches('connected') || feed.camera.type === CameraType.WEBCAM; useEffect(() => { log.debug('mounting camera', feed.camera.name); return () => { From 423a6d08315a32597dce7b667006503ece2794d6 Mon Sep 17 00:00:00 2001 From: GLDuval Date: Sat, 6 May 2023 15:33:04 -0400 Subject: [PATCH 4/8] Improve quality and add flipped and rotated configs --- package-lock.json | 57 --------------- script/rtspServer.js | 2 +- src/main/preload/api.ts | 2 +- src/main/rtspServer.ts | 10 +-- .../components/Feed/Feeds/CameraFeed.tsx | 8 +- .../components/Feed/Feeds/RTSPFeed.tsx | 73 +++++++++++++------ src/renderer/types/jsmpeg.d.ts | 6 +- 7 files changed, 69 insertions(+), 89 deletions(-) diff --git a/package-lock.json b/package-lock.json index a26ceafa..b755e1a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,6 @@ "lodash": "^4.17.21", "nanoid": "^3.3.1", "node-rtsp-stream": "^0.0.9", - "node-rtsp-stream-es6": "^1.0.7", "polished": "^4.1.4", "qr-scanner": "^1.4.1", "react": "^17.0.2", @@ -4616,11 +4615,6 @@ "node": ">=0.12.0" } }, - "node_modules/async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -10839,11 +10833,6 @@ "node": ">=4" } }, - "node_modules/jsmpeg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/jsmpeg/-/jsmpeg-1.0.0.tgz", - "integrity": "sha512-wlBKWVJ93NRJaCfrJ1KAgpMvZBLzpZxH3wnC1Yj7DudMDa/5hHeL1HfvW48ndR8GlI4irrqCXuOGhgayP9EbHw==" - }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -11685,23 +11674,6 @@ "ws": "^7.0.0" } }, - "node_modules/node-rtsp-stream-es6": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/node-rtsp-stream-es6/-/node-rtsp-stream-es6-1.0.7.tgz", - "integrity": "sha512-UsOt0Ouj6ZY6AG4QMsDBtttUq2+0JfwdptW/bCjOKh3a1FmGmBLLgmMYZIi1V3uu++MBkZT7NWarJ0tKw+YdYw==", - "dependencies": { - "jsmpeg": "^1.0.0", - "ws": "7.1.2" - } - }, - "node_modules/node-rtsp-stream-es6/node_modules/ws": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.1.2.tgz", - "integrity": "sha512-gftXq3XI81cJCgkUiAVixA0raD9IVmXqsylCrjRygw4+UOOGzPoxnQ6r/CnVL9i+mDncJo94tSkyrtuuQVBmrg==", - "dependencies": { - "async-limiter": "^1.0.0" - } - }, "node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -19339,11 +19311,6 @@ "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", "dev": true }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" - }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -24056,11 +24023,6 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, - "jsmpeg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/jsmpeg/-/jsmpeg-1.0.0.tgz", - "integrity": "sha512-wlBKWVJ93NRJaCfrJ1KAgpMvZBLzpZxH3wnC1Yj7DudMDa/5hHeL1HfvW48ndR8GlI4irrqCXuOGhgayP9EbHw==" - }, "json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -24722,25 +24684,6 @@ "ws": "^7.0.0" } }, - "node-rtsp-stream-es6": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/node-rtsp-stream-es6/-/node-rtsp-stream-es6-1.0.7.tgz", - "integrity": "sha512-UsOt0Ouj6ZY6AG4QMsDBtttUq2+0JfwdptW/bCjOKh3a1FmGmBLLgmMYZIi1V3uu++MBkZT7NWarJ0tKw+YdYw==", - "requires": { - "jsmpeg": "^1.0.0", - "ws": "7.1.2" - }, - "dependencies": { - "ws": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.1.2.tgz", - "integrity": "sha512-gftXq3XI81cJCgkUiAVixA0raD9IVmXqsylCrjRygw4+UOOGzPoxnQ6r/CnVL9i+mDncJo94tSkyrtuuQVBmrg==", - "requires": { - "async-limiter": "^1.0.0" - } - } - } - }, "nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", diff --git a/script/rtspServer.js b/script/rtspServer.js index 8eed879d..6f8383e6 100644 --- a/script/rtspServer.js +++ b/script/rtspServer.js @@ -9,7 +9,7 @@ new Stream({ wsPort: parseInt(args[1]), ffmpegOptions: { // options ffmpeg flags - '-stats': '', // an option with no neccessary value uses a blank string '-r': 30, // options with required values specify the value after the key + '-q': 0, // quality video in scale [0, 32] }, }); diff --git a/src/main/preload/api.ts b/src/main/preload/api.ts index 4283cbec..652ba973 100644 --- a/src/main/preload/api.ts +++ b/src/main/preload/api.ts @@ -33,6 +33,6 @@ export const preload = { }, rtsp: { start: (url: string) => ipcRenderer.invoke(RTSP_START, url), - stop: (url: string) => ipcRenderer.send(RTSP_STOP, url), + stop: (port: number) => ipcRenderer.send(RTSP_STOP, port), }, }; diff --git a/src/main/rtspServer.ts b/src/main/rtspServer.ts index 933e2a3b..ab65fa2b 100644 --- a/src/main/rtspServer.ts +++ b/src/main/rtspServer.ts @@ -9,7 +9,7 @@ interface RtspProcess { wsPort: number; } -const rtspServers: Map = new Map(); +const rtspServers: Map = new Map(); // Stack array of ports from (9000 to 9060) to use for rtsp servers const ports = Array.from({ length: 61 }, (_, i) => i + 9000); @@ -24,7 +24,7 @@ ipcMain.handle('rtsp_start', (event, url: string) => { nextPort.toString(), ]); - rtspServers.set(url, { + rtspServers.set(nextPort, { process, wsPort: nextPort, }); @@ -32,8 +32,8 @@ ipcMain.handle('rtsp_start', (event, url: string) => { return nextPort; }); -ipcMain.on('rtsp_stop', (event, url: string) => { - const rtspProcess = rtspServers.get(url); +ipcMain.on('rtsp_stop', (event, port: number) => { + const rtspProcess = rtspServers.get(port); if (!rtspProcess) { return; @@ -41,5 +41,5 @@ ipcMain.on('rtsp_stop', (event, url: string) => { log.info('stopping rtsp process'); ports.push(rtspProcess.wsPort); rtspProcess.process.kill(); - rtspServers.delete(url); + rtspServers.delete(port); }); diff --git a/src/renderer/components/Feed/Feeds/CameraFeed.tsx b/src/renderer/components/Feed/Feeds/CameraFeed.tsx index 2051209e..e7df6b83 100644 --- a/src/renderer/components/Feed/Feeds/CameraFeed.tsx +++ b/src/renderer/components/Feed/Feeds/CameraFeed.tsx @@ -147,7 +147,13 @@ const View: FC = ({ feed }) => { ); case CameraType.RTSP: - return ; + return ( + + ); default: return ; } diff --git a/src/renderer/components/Feed/Feeds/RTSPFeed.tsx b/src/renderer/components/Feed/Feeds/RTSPFeed.tsx index eb4d668f..111cccac 100644 --- a/src/renderer/components/Feed/Feeds/RTSPFeed.tsx +++ b/src/renderer/components/Feed/Feeds/RTSPFeed.tsx @@ -1,51 +1,78 @@ -import React, { useEffect, useRef } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import JSMpeg from '@cycjimmy/jsmpeg-player'; import { log } from '@/renderer/logger'; interface Props { url: string; + flipped: boolean; + rotated: boolean; } -export const RTSPFeed = ({ url }: Props) => { +export const RTSPFeed = ({ url, flipped, rotated }: Props) => { const videoRef = useRef(null); const canvasRef = useRef(null); + const [port, setPort] = useState(null); + const [videoElement, setVideoElement] = + useState(); useEffect(() => { - log.info(`Starting RTSP server for ${url}`); const videoWrapper = videoRef.current; const canvas = canvasRef.current; - let videoElement: JSMpeg.VideoElement | null = null; const startServer = async () => { const port = (await window.preloadApi.rtsp.start(url)) as number; log.info(`RTSP server started on port ${port}`); if (videoWrapper && canvas) { - videoElement = new JSMpeg.VideoElement( - videoWrapper, - `ws://localhost:${port}`, - { - canvas, - autoplay: true, - audio: false, - } + setVideoElement( + new JSMpeg.VideoElement( + videoWrapper, + `ws://localhost:${port}`, + { + canvas, + autoplay: true, + audio: false, + }, + { videoBufferSize: 2048 * 2048 } + ) ); + setPort(port); } }; - startServer().catch((e) => log.error(e)); + if (!port && !videoElement) { + startServer().catch((e) => log.error(e)); + } return () => { - log.info('Stopping RTSP server'); - window.preloadApi.rtsp.stop(url); - if (videoElement) { - videoElement.destroy(); + if (port) { + log.info(`Stopping RTSP server for ${url}`); + window.preloadApi.rtsp.stop(port); + setPort(null); + if (videoElement) { + videoElement.destroy(); + } } }; - }, [url]); + }, [port, url, videoElement]); return ( - <> -
- -
- +
+ +
); }; diff --git a/src/renderer/types/jsmpeg.d.ts b/src/renderer/types/jsmpeg.d.ts index ad338839..7e181db1 100644 --- a/src/renderer/types/jsmpeg.d.ts +++ b/src/renderer/types/jsmpeg.d.ts @@ -7,9 +7,13 @@ declare module '@cycjimmy/jsmpeg-player' { canvas?: HTMLCanvasElement; autoplay?: boolean; audio?: boolean; + autoSetWrapperSize?: boolean; + }, + overlayOptions?: { + videoBufferSize?: number; } ); - + paused: boolean; destroy(): void; } } From 112c2287e7645b9acf4415b7bfa938e501eebc99 Mon Sep 17 00:00:00 2001 From: GLDuval Date: Sat, 6 May 2023 15:49:57 -0400 Subject: [PATCH 5/8] Update readme --- README.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8edce02b..5ee139d1 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,27 @@ roslaunch web_video_server web_video_server.launch roslaunch rosbridge_server rosbridge_websocket.launch ``` -### Audio +## System dependencies + +To view RTSP streams your system requires FFmpeg to be installed. + +### On Windows + +With Chocolatey: + +`choco install ffmpeg` + +### On Linux (Debian) + +`sudo apt install ffmpeg` + +### On Mac + +With homebrew: + +`brew install ffmpeg` + +## Audio Currently, the audio IO is handled by the [capra_audio](https://github.com/clubcapra/capra_audio_common) node. The UI will simply launch the node as a child process. This only works on linux. Windows support should work with wsl but is not currently implemented. For audio to work, you also need to make sure that the capra_audio launch files are sourced in the terminal you are using to launch the UI. From 6bd074717548a94f2ea4776178483b44039ffaa9 Mon Sep 17 00:00:00 2001 From: GLDuval Date: Sun, 7 May 2023 14:15:31 -0400 Subject: [PATCH 6/8] Small fixes --- script/rtspServer.js | 12 +++++++++++- src/main/rtspServer.ts | 5 +++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/script/rtspServer.js b/script/rtspServer.js index 6f8383e6..76a166b9 100644 --- a/script/rtspServer.js +++ b/script/rtspServer.js @@ -3,13 +3,23 @@ const Stream = require('node-rtsp-stream'); // Get node args const args = process.argv.slice(2); +// Check if required arguments are present +if (args.length < 2) { + const filename = __filename.split('/').pop(); + // eslint-disable-next-line no-console + console.error( + `Missing Arguments! Usage: node ${filename} ` + ); + process.exit(1); +} + new Stream({ name: 'RTSP-Stream', streamUrl: args[0], wsPort: parseInt(args[1]), ffmpegOptions: { // options ffmpeg flags - '-r': 30, // options with required values specify the value after the key + '-re': '', // read input at native frame rate '-q': 0, // quality video in scale [0, 32] }, }); diff --git a/src/main/rtspServer.ts b/src/main/rtspServer.ts index ab65fa2b..d59af8cd 100644 --- a/src/main/rtspServer.ts +++ b/src/main/rtspServer.ts @@ -3,6 +3,7 @@ import { app, ipcMain } from 'electron'; import { ExecaChildProcess, execa } from 'execa'; import path from 'path'; import { isDev } from '@/main/isDev'; +import { RTSP_START, RTSP_STOP } from './preload'; interface RtspProcess { process: ExecaChildProcess; @@ -14,7 +15,7 @@ const rtspServers: Map = new Map(); // Stack array of ports from (9000 to 9060) to use for rtsp servers const ports = Array.from({ length: 61 }, (_, i) => i + 9000); -ipcMain.handle('rtsp_start', (event, url: string) => { +ipcMain.handle(RTSP_START, (_, url: string) => { const nextPort = ports.shift() ?? 9000; const process = execa('node', [ isDev @@ -32,7 +33,7 @@ ipcMain.handle('rtsp_start', (event, url: string) => { return nextPort; }); -ipcMain.on('rtsp_stop', (event, port: number) => { +ipcMain.on(RTSP_STOP, (_, port: number) => { const rtspProcess = rtspServers.get(port); if (!rtspProcess) { From 06aeb386213f33275cf49fde6b8708258399cda8 Mon Sep 17 00:00:00 2001 From: GLDuval Date: Sun, 7 May 2023 15:22:49 -0400 Subject: [PATCH 7/8] Cleanup code --- script/rtspServer.js | 2 +- .../components/Feed/Feeds/RTSPFeed.tsx | 47 ++++++++----------- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/script/rtspServer.js b/script/rtspServer.js index 76a166b9..d2d9b52e 100644 --- a/script/rtspServer.js +++ b/script/rtspServer.js @@ -19,7 +19,7 @@ new Stream({ wsPort: parseInt(args[1]), ffmpegOptions: { // options ffmpeg flags - '-re': '', // read input at native frame rate + '-r': 30, '-q': 0, // quality video in scale [0, 32] }, }); diff --git a/src/renderer/components/Feed/Feeds/RTSPFeed.tsx b/src/renderer/components/Feed/Feeds/RTSPFeed.tsx index 111cccac..bc0945fc 100644 --- a/src/renderer/components/Feed/Feeds/RTSPFeed.tsx +++ b/src/renderer/components/Feed/Feeds/RTSPFeed.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef } from 'react'; import JSMpeg from '@cycjimmy/jsmpeg-player'; import { log } from '@/renderer/logger'; @@ -11,47 +11,40 @@ interface Props { export const RTSPFeed = ({ url, flipped, rotated }: Props) => { const videoRef = useRef(null); const canvasRef = useRef(null); - const [port, setPort] = useState(null); - const [videoElement, setVideoElement] = - useState(); useEffect(() => { const videoWrapper = videoRef.current; const canvas = canvasRef.current; + let videoElement: JSMpeg.VideoElement | null = null; + let port: number | null = null; const startServer = async () => { - const port = (await window.preloadApi.rtsp.start(url)) as number; - log.info(`RTSP server started on port ${port}`); + port = (await window.preloadApi.rtsp.start(url)) as number; + if (videoWrapper && canvas) { - setVideoElement( - new JSMpeg.VideoElement( - videoWrapper, - `ws://localhost:${port}`, - { - canvas, - autoplay: true, - audio: false, - }, - { videoBufferSize: 2048 * 2048 } - ) + log.info(`RTSP server started on port ${port}`); + videoElement = new JSMpeg.VideoElement( + videoWrapper, + `ws://localhost:${port}`, + { + canvas, + autoplay: true, + audio: false, + }, + { videoBufferSize: 2048 * 2048 } ); - setPort(port); } }; - if (!port && !videoElement) { - startServer().catch((e) => log.error(e)); - } + + startServer().catch((e) => log.error(e)); return () => { - if (port) { + if (videoElement && port) { log.info(`Stopping RTSP server for ${url}`); window.preloadApi.rtsp.stop(port); - setPort(null); - if (videoElement) { - videoElement.destroy(); - } + videoElement.destroy(); } }; - }, [port, url, videoElement]); + }, [url]); return (
Date: Tue, 9 May 2023 10:57:00 -0400 Subject: [PATCH 8/8] Fix build --- package.json | 5 ++++- snowpack.config.js | 1 - src/main/rtspServer.ts | 11 +++++------ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index bade374e..c4ce204a 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,10 @@ "output": "dist" }, "artifactName": "capra_web_ui_setup.${ext}", - "extends": "electron-snowpack/config/electron-builder.js" + "extends": "electron-snowpack/config/electron-builder.js", + "extraFiles": [ + "script/**" + ] }, "dependencies": { "@cycjimmy/jsmpeg-player": "^6.0.5", diff --git a/snowpack.config.js b/snowpack.config.js index 8819a19a..5ca7247f 100644 --- a/snowpack.config.js +++ b/snowpack.config.js @@ -2,7 +2,6 @@ module.exports = { extends: 'electron-snowpack/config/snowpack.js', mount: { 'src/shared': '/shared', - script: '/script', }, alias: { '@/': './src/', diff --git a/src/main/rtspServer.ts b/src/main/rtspServer.ts index d59af8cd..6dbd17ac 100644 --- a/src/main/rtspServer.ts +++ b/src/main/rtspServer.ts @@ -2,8 +2,8 @@ import { log } from '@/main/logger'; import { app, ipcMain } from 'electron'; import { ExecaChildProcess, execa } from 'execa'; import path from 'path'; -import { isDev } from '@/main/isDev'; import { RTSP_START, RTSP_STOP } from './preload'; +import process from 'process'; interface RtspProcess { process: ExecaChildProcess; @@ -17,16 +17,15 @@ const ports = Array.from({ length: 61 }, (_, i) => i + 9000); ipcMain.handle(RTSP_START, (_, url: string) => { const nextPort = ports.shift() ?? 9000; - const process = execa('node', [ - isDev + const rtspProcess = execa('node', [ + !app.isPackaged ? './script/rtspServer.js' - : path.join(app.getAppPath(), '../renderer/script/rtspServer.js'), + : path.resolve(`${process.resourcesPath}/../script/rtspServer.js`), url, nextPort.toString(), ]); - rtspServers.set(nextPort, { - process, + process: rtspProcess, wsPort: nextPort, });