From 9bb04cfe35eff791bf96a3abf9303f3cd331d333 Mon Sep 17 00:00:00 2001 From: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Date: Tue, 21 Nov 2023 18:49:59 +0100 Subject: [PATCH 01/11] Replace node-fetch by internal fetch API --- node_helper.js | 1 - package.json | 1 - 2 files changed, 2 deletions(-) diff --git a/node_helper.js b/node_helper.js index b46bd6b..24c439f 100644 --- a/node_helper.js +++ b/node_helper.js @@ -7,7 +7,6 @@ var NodeHelper = require("node_helper"); var Stream = require("node-rtsp-stream-es6"); -const fetch = require("node-fetch"); const fs = require("fs"); const path = require("path"); const DataURI = require("datauri"); diff --git a/package.json b/package.json index 8258f02..de2ad43 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "datauri": "^1.0.5", - "node-fetch": "^2.6.7", "node-rtsp-stream-es6": "github:shbatm/node-rtsp-stream-es6", "pm2": "^5.1.2", "ps-tree": "^1.1.0", From 9b8f454a4d2471e8806bc8bc607eddcb29093b2d Mon Sep 17 00:00:00 2001 From: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Date: Tue, 21 Nov 2023 18:51:55 +0100 Subject: [PATCH 02/11] Add keywords --- package.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package.json b/package.json index de2ad43..2ba779a 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,11 @@ "name": "mmm-rtspstream", "version": "2.0.5", "description": "Magic Mirror 2 module to stream video from an RTSP stream", + "keywords": [ + "magic mirror", + "rtsp", + "camera" + ], "main": "MMM-RTSPStream.js", "author": "shbatm", "license": "MIT", From ed5d707b62d321aef81cf39816853907e85b4129 Mon Sep 17 00:00:00 2001 From: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Date: Tue, 21 Nov 2023 18:55:00 +0100 Subject: [PATCH 03/11] =?UTF-8?q?MagicMirror=C2=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MMM-RTSPStream.js | 2 +- README.md | 4 ++-- node_helper.js | 2 +- package.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/MMM-RTSPStream.js b/MMM-RTSPStream.js index bf02e94..118ca95 100644 --- a/MMM-RTSPStream.js +++ b/MMM-RTSPStream.js @@ -1,6 +1,6 @@ /* global Module */ -/* Magic Mirror +/* MagicMirror² * Module: MMM-RTSPStream * * By shbatm diff --git a/README.md b/README.md index 6815d1d..aa95c64 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This is a module for the [MagicMirror²](https://github.com/MichMich/MagicMirror/). -This module will show a live RTSP video stream and/or periodic snapshots on the Magic Mirror from any IP Security Camera which supports the [RTSP protocol](https://github.com/shbatm/MMM-RTSPStream/wiki/Stream-URLs-for-Various-Cameras) and/or can serve a snapshot periodically. +This module will show a live RTSP video stream and/or periodic snapshots on the MagicMirror² from any IP Security Camera which supports the [RTSP protocol](https://github.com/shbatm/MMM-RTSPStream/wiki/Stream-URLs-for-Various-Cameras) and/or can serve a snapshot periodically. > :warning: This module is no longer being actively developed. I will accept PRs and leave the repo active, but will not be directly supporting any issues. If anyone is interested in assuming ownership of the module, please contact @shbatm. :warning: > @@ -43,7 +43,7 @@ This module will show a live RTSP video stream and/or periodic snapshots on the ### Quick install -If you followed the default installation instructions for the [Magic Mirror²](https://github.com/MichMich/MagicMirror) project, you should be able to use the automatic installer. +If you followed the default installation instructions for the [MagicMirror²](https://github.com/MichMich/MagicMirror) project, you should be able to use the automatic installer. The following command will download the installer and execute it: ```bash bash -c "$(curl -s https://raw.githubusercontent.com/shbatm/MMM-RTSPStream/master/scripts/installer.sh)" diff --git a/node_helper.js b/node_helper.js index 24c439f..85c9fac 100644 --- a/node_helper.js +++ b/node_helper.js @@ -1,4 +1,4 @@ -/* Magic Mirror +/* MagicMirror² * Node Helper: MMM-RTSPStream * * By shbatm diff --git a/package.json b/package.json index 2ba779a..ea602bd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mmm-rtspstream", "version": "2.0.5", - "description": "Magic Mirror 2 module to stream video from an RTSP stream", + "description": "MagicMirror² module to stream video from an RTSP stream", "keywords": [ "magic mirror", "rtsp", From d58cbe08b756c2ecb6e91fc23a3777373d17cbd1 Mon Sep 17 00:00:00 2001 From: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Date: Tue, 21 Nov 2023 19:02:48 +0100 Subject: [PATCH 04/11] Fix markdown format --- CHANGELOG.md | 7 ++-- README.md | 101 +++++++++++++++++++++++++++------------------------ 2 files changed, 58 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87ce871..a10c43d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,15 @@ -## :warning: Refer to GitHub Releases page for Change Logs post [2.0.2-dev] +# :warning: Refer to GitHub Releases page for Change Logs post [2.0.2-dev] ## [2.0.2-dev] - Attempted fix for OMXPlayer with OpenGL (Fake KMS) enabled Changed: + * Added "--no-osd" command line switch to omxplayer command. Per [this thread](https://www.raspberrypi.org/forums/viewtopic.php?t=159853), "omxplayer uses OpenVG for subtitles and status messsages which is not compatible with [the OpenGL (Fake KMS)] driver." ## [2.0.1-dev] - Major OMX Bugfixes Changed: + * App closing now spawns a new process to actually kill the OMX streams, it was getting cut off in the middle of closing everything due to it being an async process. * Wait for DOM to be shown before calling to start the streams--this was causing the Fullscreen on Resume problems. * Only start one stream at a time if we're in RotateStreams mode @@ -29,7 +31,7 @@ Changed: * `hideFfmpegOutput` configuration option removed from stream config in favor of global `debug` module option. * Fixed bug where transition timer was not properly reset after module resume. * Added `hwAccel` stream option for `ffmpeg` to attempt to use hardware accelerated decoding. Encoding still uses CPU unfortunatly. - - You must update the node-rtsp-stream-es6 package too. This is most easily done by deleting your node_modules folder and re-running `npm install` on the module. + * You must update the node-rtsp-stream-es6 package too. This is most easily done by deleting your node_modules folder and re-running `npm install` on the module. * `RTSP-PLAY` notification now accepts an object `{ stream: "streamX", stopOthers: true }` which will stop other streams before starting the new stream. ## [1.2.2] - Auto-restart OMX Stream every X hours (Partially addresses #29) @@ -38,7 +40,6 @@ Changes: * Added config option to schedule automatic restarts of the OMX streams. - ## [1.2.1] - Custom video window parameters Changes: diff --git a/README.md b/README.md index aa95c64..4ce8bd9 100644 --- a/README.md +++ b/README.md @@ -7,49 +7,51 @@ This module will show a live RTSP video stream and/or periodic snapshots on the > :warning: This module is no longer being actively developed. I will accept PRs and leave the repo active, but will not be directly supporting any issues. If anyone is interested in assuming ownership of the module, please contact @shbatm. :warning: > > Why? -> - I am no longer using this module on my own mirror. After several years, I found that I use the snapshots much more frequently than I streamed the actual cameras, which can be performed by much simpler modules and methods. To enable streaming, WebRTC (like [MMM-HomeAssistant-WebRTC](https://github.com/Anonym-tsk/MMM-HomeAssistant-WebRTC)) is a newer and better standard with much lower server overhead and latency for delivering RTSP Streams to the frontend than any of the options used here, in the future, this will be the method I focus on and I will not try to shoehorn another technology into this module. -> - Update 5-Oct-2022: See alternative module [MMM-RTSPtoWeb](https://github.com/shbatm/MMM-RTSPtoWeb) for a drastically simplified module relying on WebRTC and a backend server. - -### Features: - -* Supports single or multiple camera streams/snapshots -* For multiple streams: supports rotating through streams in a single window or displaying multiple windows (with customizeable layout) -* Supports fetching snapshots from a file or url when not actively streaming -* Flexible configurations to limit resource use on Raspberry Pi -- - - Stops all streams when module is hidden - - Option for AutoPlay or manual starting of stream - - Plays one or all streams (when displaying multiple) - - *Note:* 3 simultaneous streams on a RaspberryPi 3 is about the limit for usability. -* Support for [MMM-KeyBindings](https://github.com/shbatm/MMM-KeyBindings) module for Play/Pause Remote Control and navigation of multiple streams -* Hardware-Accelerated Playback on the main screen, with option to use software playback on a remote browser window. -* When using `omxplayer` or `vlc`, double-clicking the play button (or longpressing PlayPause key if using MMM-KeyBindings) will play the video fullscreen. Click anywhere once (or Pause with MMM-KeyBindings) to exit. - -### Dependencies: - -* The following packages are required for the module to function fully and the installer will attempt to install them with `apt`: - * `ffmpeg`, `omxplayer`, `vlc`, `devilspie2`, `wmctrl` -* For hardware-accelerated streaming, `vlc` or `omxplayer` is required. -* For manipulating VLC's windows, `devilspie2` and `wmctrl` are used. -* For software-decoded streaming and/or remote browser viewing: - - Requires `jsmpeg` for front-end display of stream. - - Requires `node-rtsp-stream-es6` Node.js module and `ffmpeg` for backend. - - Video flow using `'ffmpeg'`: Camera RTSP Stream → `ffmpeg` pre-processor → MM module's `node_helper.js` (via `node-rtsp-stream-es6`) → Web Socket (`ws`) → MagicMirror² (via `jsmpeg`) - -## Screenshot: +> +> - I am no longer using this module on my own mirror. After several years, I found that I use the snapshots much more frequently than I streamed the actual cameras, which can be performed by much simpler modules and methods. To enable streaming, WebRTC (like [MMM-HomeAssistant-WebRTC](https://github.com/Anonym-tsk/MMM-HomeAssistant-WebRTC)) is a newer and better standard with much lower server overhead and latency for delivering RTSP Streams to the frontend than any of the options used here, in the future, this will be the method I focus on and I will not try to shoehorn another technology into this module. +> - Update 5-Oct-2022: See alternative module [MMM-RTSPtoWeb](https://github.com/shbatm/MMM-RTSPtoWeb) for a drastically simplified module relying on WebRTC and a backend server. + +## Features + +- Supports single or multiple camera streams/snapshots +- For multiple streams: supports rotating through streams in a single window or displaying multiple windows (with customizeable layout) +- Supports fetching snapshots from a file or url when not actively streaming +- Flexible configurations to limit resource use on Raspberry Pi -- + - Stops all streams when module is hidden + - Option for AutoPlay or manual starting of stream + - Plays one or all streams (when displaying multiple) + - *Note:* 3 simultaneous streams on a RaspberryPi 3 is about the limit for usability. +- Support for [MMM-KeyBindings](https://github.com/shbatm/MMM-KeyBindings) module for Play/Pause Remote Control and navigation of multiple streams +- Hardware-Accelerated Playback on the main screen, with option to use software playback on a remote browser window. +- When using `omxplayer` or `vlc`, double-clicking the play button (or longpressing PlayPause key if using MMM-KeyBindings) will play the video fullscreen. Click anywhere once (or Pause with MMM-KeyBindings) to exit. + +## Dependencies + +- The following packages are required for the module to function fully and the installer will attempt to install them with `apt`: + - `ffmpeg`, `omxplayer`, `vlc`, `devilspie2`, `wmctrl` +- For hardware-accelerated streaming, `vlc` or `omxplayer` is required. +- For manipulating VLC's windows, `devilspie2` and `wmctrl` are used. +- For software-decoded streaming and/or remote browser viewing: + - Requires `jsmpeg` for front-end display of stream. + - Requires `node-rtsp-stream-es6` Node.js module and `ffmpeg` for backend. + - Video flow using `'ffmpeg'`: Camera RTSP Stream → `ffmpeg` pre-processor → MM module's `node_helper.js` (via `node-rtsp-stream-es6`) → Web Socket (`ws`) → MagicMirror² (via `jsmpeg`) + +## Screenshot ![](screenshot.png) -## Installation: +## Installation ### Quick install If you followed the default installation instructions for the [MagicMirror²](https://github.com/MichMich/MagicMirror) project, you should be able to use the automatic installer. The following command will download the installer and execute it: + ```bash bash -c "$(curl -s https://raw.githubusercontent.com/shbatm/MMM-RTSPStream/master/scripts/installer.sh)" ``` -## Updating after a Module Update: +## Updating after a Module Update Re-run the installation script above, or do the following: @@ -65,19 +67,21 @@ npm install 1. Install the module (see above). 2. Add the following to your config: -````shell - { - module: 'MMM-RTSPStream', - position: 'middle_center', - config: { - initialSetup: true, + + ```js + { + module: 'MMM-RTSPStream', + position: 'middle_center', + config: { + initialSetup: true, + } } - } -```` -2. Open a web-browser and navigate to: http://your-mirror-ip:8080/MMM-RTSPStream/config.html -3. Use the tool to generate your config details. -4. Copy the section you your MagicMirror `config.js` file. -5. Restart the MagicMirror + ``` + +3. Open a web-browser and navigate to: +4. Use the tool to generate your config details. +5. Copy the section you your MagicMirror `config.js` file. +6. Restart the MagicMirror ## Configuration options @@ -145,6 +149,7 @@ To test to make sure you have a working url for a camera feed: create a text fil #### Advanced Stream Configurations This module has been tested exclusively with streams for Hikvision (Swann) cameras. You may find that you need to adjust the `ffmpeg` settings that are used beyond just frame rate and size. The command line arguements for `ffmpeg` can be changed by editing Line 14 of the following file after install. The `ffmpeg` arguement list is passed as an array. + ```shell ~/MagicMirror/modules/MMM-RTSPStream/node_modules/node-rtsp-stream-es6/src/mpeg1muxer.js ``` @@ -152,6 +157,7 @@ This module has been tested exclusively with streams for Hikvision (Swann) camer ### Controlling from other modules The streams can be controlled on the main screen by sending a module notification. Examples: + ```js this.sendNotification("RTSP-PLAY", "all"); // Play all streams (or current stream if rotating) this.sendNotification("RTSP-PLAY", "streamX"); // Play a particular stream (when not rotating) @@ -184,15 +190,15 @@ keyBindings: { ## To-do -* Add better touchscreen support (use an OnTouch method to play/pause instead of OnClick). -* KNOWN ISSUE: snapshots can be stopped by another "instance" of the mirror running in a different window. Expected behavior: should only affect the local window. -* KNOWN ISSUE: `omxplayer` will only play a certain maximum number of streams at a time. On a RPi3, this appears to be a max of 3. It won't error, it just won't play another stream. To fix: adjust the memory split of the GPU/CPU using the `raspi-config` command. +- Add better touchscreen support (use an OnTouch method to play/pause instead of OnClick). +- KNOWN ISSUE: snapshots can be stopped by another "instance" of the mirror running in a different window. Expected behavior: should only affect the local window. +- KNOWN ISSUE: `omxplayer` will only play a certain maximum number of streams at a time. On a RPi3, this appears to be a max of 3. It won't error, it just won't play another stream. To fix: adjust the memory split of the GPU/CPU using the `raspi-config` command. ## Experimentation This section includes some untested options and configurations that may be useful in the future. -#### Use `ffmpeg` to capture snapshots from an RTSP Stream +### Use `ffmpeg` to capture snapshots from an RTSP Stream ```js // Grab a frame every x seconds and save as thumb.png: @@ -201,4 +207,5 @@ ffmpeg -i {RTSP_SOURCE} -f image2 -vf fps=fps=1/{x} -update 1 thumb.png // Grab the first frame from a stream and save as thumb.jpg ffmpeg -i {RTSP_SOURCE} -ss 00:00:01.500 -f image2 -vframes 1 thumb.png ``` + ([source](https://superuser.com/questions/663928/ffmpeg-to-capture-stills-from-h-264-stream)) From a184ca9c811e192f1f0ed70cc477dc30fba18a77 Mon Sep 17 00:00:00 2001 From: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Date: Tue, 21 Nov 2023 19:33:04 +0100 Subject: [PATCH 05/11] Update dependencies --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index ea602bd..c9d04ca 100644 --- a/package.json +++ b/package.json @@ -16,11 +16,11 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { - "datauri": "^1.0.5", + "datauri": "^2.0.0", "node-rtsp-stream-es6": "github:shbatm/node-rtsp-stream-es6", - "pm2": "^5.1.2", - "ps-tree": "^1.1.0", - "ws": "^5.2.3" + "pm2": "^5.3.0", + "ps-tree": "^1.2.0", + "ws": "^8.14.2" }, "repository": { "type": "git", From 3d314578df6f6c2f04da38d319df87224c8e1e24 Mon Sep 17 00:00:00 2001 From: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Date: Tue, 21 Nov 2023 19:33:19 +0100 Subject: [PATCH 06/11] Sort require order --- node_helper.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/node_helper.js b/node_helper.js index 85c9fac..504369f 100644 --- a/node_helper.js +++ b/node_helper.js @@ -5,14 +5,16 @@ * MIT Licensed. */ -var NodeHelper = require("node_helper"); -var Stream = require("node-rtsp-stream-es6"); +const child_process = require("child_process"); const fs = require("fs"); const path = require("path"); const DataURI = require("datauri"); +var NodeHelper = require("node_helper"); +var Stream = require("node-rtsp-stream-es6"); + const datauri = new DataURI(); const psTree = require("ps-tree"); -const child_process = require("child_process"); + const environ = Object.assign(process.env, { DISPLAY: ":0" }); const pm2 = require("pm2"); From 5fa708a42e5e801c522a1d3fa2e84434fb262e91 Mon Sep 17 00:00:00 2001 From: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Date: Tue, 21 Nov 2023 20:28:39 +0100 Subject: [PATCH 07/11] Install prettier --- .prettierignore | 4 ++++ .prettierrc | 4 ---- .prettierrc.json | 4 ++++ package.json | 8 ++++++-- 4 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 .prettierignore delete mode 100644 .prettierrc create mode 100644 .prettierrc.json diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..9e5c1ad --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +public/bootstrap.min.css +public/jquery-3.2.1.min.js +scripts/jsmpeg.min.js +scripts/onexit.js \ No newline at end of file diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 5a938ce..0000000 --- a/.prettierrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "tabWidth": 4, - "useTabs": false -} diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..bdf18c2 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,4 @@ +{ + "tabWidth": 4, + "trailingComma": "none" +} diff --git a/package.json b/package.json index c9d04ca..b0e2076 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,11 @@ "scripts": { "preinstall": "./scripts/preinstall.sh", "postinstall": "./scripts/postinstall.sh", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "prettier --check .", + "prettier:fix": "prettier --write ." + }, + "devDependencies": { + "prettier": "^3.1.0" }, "dependencies": { "datauri": "^2.0.0", @@ -26,4 +30,4 @@ "type": "git", "url": "git+https://github.com/shbatm/MMM-RTSPStream.git" } -} \ No newline at end of file +} From 2375e8e3a167c1a00163c0e80e787e2ca7c377a7 Mon Sep 17 00:00:00 2001 From: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Date: Tue, 21 Nov 2023 20:33:43 +0100 Subject: [PATCH 08/11] Run prettier, var -> const/let, Fix markdown --- .github/CODE_OF_CONDUCT.md | 27 +- .github/ISSUE_TEMPLATE/bug_report.md | 19 +- .github/ISSUE_TEMPLATE/feature_request.md | 3 +- .github/SUPPORT.md | 16 +- .github/workflows/stale.yml | 51 +- CHANGELOG.md | 66 +- MMM-RTSPStream.css | 125 +- MMM-RTSPStream.js | 541 ++-- README.md | 137 +- node_helper.js | 160 +- package.json | 62 +- public/config.html | 2834 ++++++++++++++------- scripts/installer.sh | 12 +- scripts/onexit.js | 13 +- 14 files changed, 2580 insertions(+), 1486 deletions(-) diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index e3cde9f..0525516 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -14,22 +14,22 @@ appearance, race, religion, or sexual identity and orientation. Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting ## Our Responsibilities @@ -74,4 +74,3 @@ available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.ht For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq - diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 6b56567..5fafcda 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -3,17 +3,17 @@ name: Bug report about: Create a report to help us improve title: "[Bug] Describe your issue here" labels: bug -assignees: '' - +assignees: "" --- **Describe the bug** -For general information on how to use this module, getting started setting up, or other questions, please visit the MagicMirror² Forum at https://forum.magicmirror.builders. To make sure the module developer is aware of your issue, please tag '@shbatm' on your forum post. +For general information on how to use this module, getting started setting up, or other questions, please visit the MagicMirror² Forum at . To make sure the module developer is aware of your issue, please tag '@shbatm' on your forum post. If you are having an actual issue, please include a clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' @@ -24,17 +24,16 @@ A clear and concise description of what you expected to happen. **Configuration** Please include: -- The configuration section for this module -- The configuration section for any affected modules -* It may be easier to post these in a [Gist](https://gist.github.com) and post the link here. +- The configuration section for this module +- The configuration section for any affected modules + +- It may be easier to post these in a [Gist](https://gist.github.com) and post the link here. **Logs** -If applicable, please include the relevant logs to help explain your problem. - + From the console or `~/.pm2/logs/` - + From the web console using DevTools (Ctrl+Shift+I) +If applicable, please include the relevant logs to help explain your problem. + From the console or `~/.pm2/logs/` + From the web console using DevTools (Ctrl+Shift+I) **Additional context** Add any other context about the problem here. -If you are not comfortable posting the logs or configuration on a public place, you may also send @shbatm a private chat on the [MagicMirror² Forum](https://forum.magicmirror.builders) or send an e-mail to support@shbatm.com. E-mails asking general questions or without an accompanying Issue will not be answered. +If you are not comfortable posting the logs or configuration on a public place, you may also send @shbatm a private chat on the [MagicMirror² Forum](https://forum.magicmirror.builders) or send an e-mail to . E-mails asking general questions or without an accompanying Issue will not be answered. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index d92b828..fc03c52 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -3,8 +3,7 @@ name: Feature request about: Suggest an idea for this project title: "[FEATURE REQUEST] Your feature request idea here" labels: enhancement -assignees: '' - +assignees: "" --- For general information on how to use this module, getting started setting up, or other questions, please visit the MagicMirror² Forum at https://forum.magicmirror.builders. To make sure the module developer is aware of your issue, please tag '@shbatm' on your forum post. diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md index 6bfe6a1..7d68e73 100644 --- a/.github/SUPPORT.md +++ b/.github/SUPPORT.md @@ -1,15 +1,15 @@ ## Support for this MagicMirror² Module -For general information on how to use this module, getting started setting up, or other questions, please visit the MagicMirror² Forum at https://forum.magicmirror.builders. +For general information on how to use this module, getting started setting up, or other questions, please visit the MagicMirror² Forum at . To make sure the module developer is aware of your issue, please tag '@shbatm' on your forum post -If you experience errors with this module, or have a suggestion for improvements, please raise an issue by clicking the ISSUE button here in the Repository. When raising an issue, please include: +If you experience errors with this module, or have a suggestion for improvements, please raise an issue by clicking the ISSUE button here in the Repository. When raising an issue, please include: -- The configuration section for this module -- The configuration section for any affected modules -- Relavent logs: - + From the console or `~/.pm2/logs/` - + From the web console using DevTools (Ctrl+Shift+I) +- The configuration section for this module +- The configuration section for any affected modules +- Relavent logs: + - From the console or `~/.pm2/logs/` + - From the web console using DevTools (Ctrl+Shift+I) -This information should be posted directly in the Issue, or in a Gist (https://gist.github.com). If you are not comfortable posting this in a public place, you may send a private chat on the MagicMirror forum, or e-mail the relavent info to support@shbatm.com. \ No newline at end of file +This information should be posted directly in the Issue, or in a Gist (). If you are not comfortable posting this in a public place, you may send a private chat on the MagicMirror forum, or e-mail the relavent info to . diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 81db5a1..7e9021b 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -6,32 +6,31 @@ name: Mark stale issues and pull requests on: - schedule: - - cron: '38 8 * * *' - workflow_dispatch: + schedule: + - cron: "38 8 * * *" + workflow_dispatch: jobs: - stale: + stale: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - - steps: - - uses: actions/stale@v4 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. - stale-pr-message: > - This PR has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. - stale-issue-label: 'stale' - stale-pr-label: 'stale' - days-before-stale: 60 - days-before-close: 14 - exempt-issue-labels: 'pinned,security' + steps: + - uses: actions/stale@v8 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. + stale-pr-message: > + This PR has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. + stale-issue-label: "stale" + stale-pr-label: "stale" + days-before-stale: 60 + days-before-close: 14 + exempt-issue-labels: "pinned,security" diff --git a/CHANGELOG.md b/CHANGELOG.md index a10c43d..7d7a8b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,84 +4,84 @@ Changed: -* Added "--no-osd" command line switch to omxplayer command. Per [this thread](https://www.raspberrypi.org/forums/viewtopic.php?t=159853), "omxplayer uses OpenVG for subtitles and status messsages which is not compatible with [the OpenGL (Fake KMS)] driver." +- Added "--no-osd" command line switch to omxplayer command. Per [this thread](https://www.raspberrypi.org/forums/viewtopic.php?t=159853), "omxplayer uses OpenVG for subtitles and status messsages which is not compatible with [the OpenGL (Fake KMS)] driver." ## [2.0.1-dev] - Major OMX Bugfixes Changed: -* App closing now spawns a new process to actually kill the OMX streams, it was getting cut off in the middle of closing everything due to it being an async process. -* Wait for DOM to be shown before calling to start the streams--this was causing the Fullscreen on Resume problems. -* Only start one stream at a time if we're in RotateStreams mode -* Can jump to a specific stream in RotateStreams mode using notifications -* Fixed and cleaned up all notifications and control from other modules -* Fixed broken key bindings after MMM-KeyBindings upgrade -* Fixed issue where module was trying to connect to PM2 while it was already connected (e.g. stop stream 1 and start stream 2 back to back). +- App closing now spawns a new process to actually kill the OMX streams, it was getting cut off in the middle of closing everything due to it being an async process. +- Wait for DOM to be shown before calling to start the streams--this was causing the Fullscreen on Resume problems. +- Only start one stream at a time if we're in RotateStreams mode +- Can jump to a specific stream in RotateStreams mode using notifications +- Fixed and cleaned up all notifications and control from other modules +- Fixed broken key bindings after MMM-KeyBindings upgrade +- Fixed issue where module was trying to connect to PM2 while it was already connected (e.g. stop stream 1 and start stream 2 back to back). ## [2.0.0] - Add VLC Streaming Support Added: -* VLC Window Overlay support added. Use `localPlayer: 'vlc',` in your module configuration. -* Module-wide debug option added for more verbose output: `debug: true,` +- VLC Window Overlay support added. Use `localPlayer: 'vlc',` in your module configuration. +- Module-wide debug option added for more verbose output: `debug: true,` Changed: -* `shutdownDelay` parameter moved from the individual stream config sections to the main module config so it only has to be provided once. It has also changed from milliseconds to seconds. Warning has been added if the timeout is less time than it takes to make it through the loop of streams (causes unnecessary restarts). -* `hideFfmpegOutput` configuration option removed from stream config in favor of global `debug` module option. -* Fixed bug where transition timer was not properly reset after module resume. -* Added `hwAccel` stream option for `ffmpeg` to attempt to use hardware accelerated decoding. Encoding still uses CPU unfortunatly. - * You must update the node-rtsp-stream-es6 package too. This is most easily done by deleting your node_modules folder and re-running `npm install` on the module. -* `RTSP-PLAY` notification now accepts an object `{ stream: "streamX", stopOthers: true }` which will stop other streams before starting the new stream. +- `shutdownDelay` parameter moved from the individual stream config sections to the main module config so it only has to be provided once. It has also changed from milliseconds to seconds. Warning has been added if the timeout is less time than it takes to make it through the loop of streams (causes unnecessary restarts). +- `hideFfmpegOutput` configuration option removed from stream config in favor of global `debug` module option. +- Fixed bug where transition timer was not properly reset after module resume. +- Added `hwAccel` stream option for `ffmpeg` to attempt to use hardware accelerated decoding. Encoding still uses CPU unfortunatly. + - You must update the node-rtsp-stream-es6 package too. This is most easily done by deleting your node_modules folder and re-running `npm install` on the module. +- `RTSP-PLAY` notification now accepts an object `{ stream: "streamX", stopOthers: true }` which will stop other streams before starting the new stream. ## [1.2.2] - Auto-restart OMX Stream every X hours (Partially addresses #29) Changes: -* Added config option to schedule automatic restarts of the OMX streams. +- Added config option to schedule automatic restarts of the OMX streams. ## [1.2.1] - Custom video window parameters Changes: -* OMX streams can be started via notification in a custom-sized window. +- OMX streams can be started via notification in a custom-sized window. Fixes: -* Bug fixes for ffmpegPort and absPosition settings. +- Bug fixes for ffmpegPort and absPosition settings. ## [1.2.0] - Use PM2 to control OMX Streams Changes: -* OMXPlayer streams are started using PM2 to allow auto-restart if the stream closes -* Better shutdown handling if the "Graceful Shutdown" patch is installed. -* Added Absolute Position option to override automatic detection of where to show the video. -* Configuration Builder now included. See instructions in README.md +- OMXPlayer streams are started using PM2 to allow auto-restart if the stream closes +- Better shutdown handling if the "Graceful Shutdown" patch is installed. +- Added Absolute Position option to override automatic detection of where to show the video. +- Configuration Builder now included. See instructions in README.md Fixes: -* Various minor bug fixes and code cleanup -* `port` setting changed to `ffmpegPort` for clarity +- Various minor bug fixes and code cleanup +- `port` setting changed to `ffmpegPort` for clarity ## [1.1.1] - Added OMXPlayer Offset config option -* Added `moduleOffset` config option. On some displays, the method used to find the location to draw the video does not properly line up with the screen. Entering a pixel value will shift the video location by that amount. +- Added `moduleOffset` config option. On some displays, the method used to find the location to draw the video does not properly line up with the screen. Entering a pixel value will shift the video location by that amount. ## [1.1.0] - Hardware Acceleration w/ OMXPlayer Changes: -* Option to use OMXPlayer on main server's screen to use hardware accelerated video playback. OMXPlayer will draw over top of browser window. -* Option for fullscreen playback with OMXPlayer (double-click or MMM-KeyBindings longpress play) -* Full screen mode can use a different "HD" stream by setting `hdurl` in stream config. -* Updated MMM-KeyBindings calls to match refactored functions from that module -* Implemented independent control for server and remote browser screens +- Option to use OMXPlayer on main server's screen to use hardware accelerated video playback. OMXPlayer will draw over top of browser window. +- Option for fullscreen playback with OMXPlayer (double-click or MMM-KeyBindings longpress play) +- Full screen mode can use a different "HD" stream by setting `hdurl` in stream config. +- Updated MMM-KeyBindings calls to match refactored functions from that module +- Implemented independent control for server and remote browser screens Fixes: -* JSMpeg throws error "Failed to get WebGL context." - Using option in JSMpeg call to disable WebGL. -* Audio is ignored from the streams to prevent interference with other modules. +- JSMpeg throws error "Failed to get WebGL context." - Using option in JSMpeg call to disable WebGL. +- Audio is ignored from the streams to prevent interference with other modules. ## [1.0.2] - Fixes #10 - No playback on Monitor resume from suspend diff --git a/MMM-RTSPStream.css b/MMM-RTSPStream.css index c5967e6..2b7ebd9 100644 --- a/MMM-RTSPStream.css +++ b/MMM-RTSPStream.css @@ -9,96 +9,95 @@ * */ .MMM-RTSPStream .wrapper { - position: relative; - left: 50%; - vertical-align: middle; - text-align: center; - -webkit-transform: translateX(-50%); /*translateX(-30px);*/ - transform: translateX(-50%); /* translateX(-30px);*/ + position: relative; + left: 50%; + vertical-align: middle; + text-align: center; + -webkit-transform: translateX(-50%); /* translateX(-30px); */ + transform: translateX(-50%); /* translateX(-30px); */ } .MMM-RTSPStream .wrapper br { - clear: both; + clear: both; } .MMM-RTSPStream .innerWrapper { - position: relative; - width: 352px; /* Video width + 2*border + 2*padding */ - height: 242px; - border: 1px solid white; - /*float: left;*/ - display: inline-block; - text-align: center; - margin: 2px; + position: relative; + width: 352px; /* Video width + 2*border + 2*padding */ + height: 242px; + border: 1px solid white; + /* float: left; */ + display: inline-block; + text-align: center; + margin: 2px; } .MMM-RTSPStream .canvas { - position: absolute; - z-index: 10; - left: 50%; - top: 50%; - /*width: 352px; - height: 240px;*/ - -webkit-transform: translate(-50%, -50%); - transform: translate(-50%, -50%); + position: absolute; + z-index: 10; + left: 50%; + top: 50%; + /* width: 352px; + height: 240px; */ + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); width: 100%; height: 100%; } .MMM-RTSPStream .snapshot { - position: absolute; - z-index: 1; - left: 50%; - top: 50%; - /*width: 352px; - height: 240px;*/ - -webkit-transform: translate(-50%, -50%); - transform: translate(-50%, -50%); + position: absolute; + z-index: 1; + left: 50%; + top: 50%; + /* width: 352px; + height: 240px; */ + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); width: 100%; height: 100%; } /* Next and Previous arrow */ .MMM-RTSPStream .control { - z-index: 55; - /*opacity: 0.0;*/ - display: inline-block; - position: absolute; - left: 50%; - top: 50%; - line-height: 50%; - width: 100%; - height: 100%; - vertical-align: middle; - -webkit-transform: translate(-50%, -50%); - transform: translate(-50%, -50%); + z-index: 55; + /* opacity: 0.0; */ + display: inline-block; + position: absolute; + left: 50%; + top: 50%; + line-height: 50%; + width: 100%; + height: 100%; + vertical-align: middle; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); } - /* Show button when you hover near */ .MMM-RTSPStream .control:hover label { - opacity: 0.6; - cursor: pointer !important; + opacity: 0.6; + cursor: pointer !important; } .MMM-RTSPStream .control:hover { - opacity: 0.6; - cursor: pointer !important; + opacity: 0.6; + cursor: pointer !important; } .MMM-RTSPStream .control label { - z-index: 100; - display: block; - text-align: center; - line-height: 50px; - width: 100px; - height: 100px; - position: absolute; - left: 50%; - top: 50%; - -webkit-transform: translate(-50%, -50%); - transform: translate(-50%, -50%); - font-size: 100px; - color: #FFF; - opacity: 0.0; -} \ No newline at end of file + z-index: 100; + display: block; + text-align: center; + line-height: 50px; + width: 100px; + height: 100px; + position: absolute; + left: 50%; + top: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + font-size: 100px; + color: #fff; + opacity: 0; +} diff --git a/MMM-RTSPStream.js b/MMM-RTSPStream.js index 118ca95..8cc45ea 100644 --- a/MMM-RTSPStream.js +++ b/MMM-RTSPStream.js @@ -1,4 +1,4 @@ -/* global Module */ +/* global JSMpeg KeyHandler MM Module */ /* MagicMirror² * Module: MMM-RTSPStream @@ -6,8 +6,8 @@ * By shbatm * MIT Licensed. */ -/* jshint esversion: 6*/ -var global = this; +/* jshint esversion: 6 */ +const global = this; Module.register("MMM-RTSPStream", { defaults: { @@ -26,10 +26,10 @@ Module.register("MMM-RTSPStream", { shutdownDelay: 11, // Seconds animationSpeed: 1500, stream1: { - name: 'BigBuckBunny Test', - url: 'rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov', - snapshotType: 'url', // 'url' or 'file' - snapshotUrl: '', + name: "BigBuckBunny Test", + url: "rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov", + snapshotType: "url", // 'url' or 'file' + snapshotUrl: "", snapshotRefresh: 10, // Seconds protocol: "tcp", // 'tcp' or 'udp' frameRate: "30", @@ -43,7 +43,7 @@ Module.register("MMM-RTSPStream", { // MMM-KeyBindings Settings keyBindings: { enabled: true - }, + } }, keyBindings: { @@ -65,29 +65,42 @@ Module.register("MMM-RTSPStream", { currentIndex: -1, - currentStream: '', + currentStream: "", streams: {}, // Allow for control on muliple instances - instance: (global.location && ["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined, "0.0.0.0"].indexOf(global.location.hostname) > -1) ? "SERVER" : "LOCAL", - - start: function() { - var self = this; - - //Flag for check if module is loaded + instance: + global.location && + [ + "localhost", + "127.0.0.1", + "::1", + "::ffff:127.0.0.1", + undefined, + "0.0.0.0" + ].indexOf(global.location.hostname) > -1 + ? "SERVER" + : "LOCAL", + + start() { + const self = this; + + // Flag for check if module is loaded this.loaded = false; if (!this.config.initialSetup) { - this.sendSocketNotification('CONFIG', this.config); + this.sendSocketNotification("CONFIG", this.config); - Object.keys(this.config).filter(key => key.startsWith("stream")).forEach((key) => { - self.streams[key] = { playing: false }; - }); + Object.keys(this.config) + .filter((key) => key.startsWith("stream")) + .forEach((key) => { + self.streams[key] = { playing: false }; + }); } }, - setupStreamRotation: function() { + setupStreamRotation() { this.playing = this.config.autoStart; // Reference to function for manual transitions (TODO: FUTURE) this.manualTransition = this.rotateStream.bind(this); @@ -96,11 +109,11 @@ Module.register("MMM-RTSPStream", { this.restartTimer(); }, - rotateStream: function(goToStream = undefined, goDirection = 0) { - var k = Object.keys(this.streams); - var ps = []; - var resetCurrentIndex = k.length; - var lastStream = this.currentStream; + rotateStream(goToStream = undefined, goDirection = 0) { + const k = Object.keys(this.streams); + let ps = []; + const resetCurrentIndex = k.length; + const lastStream = this.currentStream; // Update the current index if (goToStream && goToStream in this.streams) { @@ -111,7 +124,8 @@ Module.register("MMM-RTSPStream", { } else { this.currentIndex += goDirection; // Told to go a specific direction } - if (this.currentIndex >= resetCurrentIndex) { // Wrap-around back to beginning + if (this.currentIndex >= resetCurrentIndex) { + // Wrap-around back to beginning this.currentIndex = 0; } else if (this.currentIndex < 0) { this.currentIndex = resetCurrentIndex - 1; // Went too far backwards, wrap-around to end @@ -120,7 +134,9 @@ Module.register("MMM-RTSPStream", { } if (this.playing) { - if (lastStream) { this.stopStream(lastStream); } + if (lastStream) { + this.stopStream(lastStream); + } ps = this.playStream(this.currentStream); if (ps.length > 0) { if (this.config.localPlayer === "omxplayer") { @@ -129,40 +145,52 @@ Module.register("MMM-RTSPStream", { this.sendSocketNotification("PLAY_VLCSTREAM", ps); } } - } else { - if (lastStream) { this.sendSocketNotification("SNAPSHOT_STOP", lastStream); } + if (lastStream) { + this.sendSocketNotification("SNAPSHOT_STOP", lastStream); + } this.playSnapshots(this.currentStream); } }, - restartTimer: function() { - if (this.config.rotateStreams && Object.keys(this.streams).length > 1 && this.config.rotateStreamTimeout > 0) { + restartTimer() { + if ( + this.config.rotateStreams && + Object.keys(this.streams).length > 1 && + this.config.rotateStreamTimeout > 0 + ) { // Restart the timer - if (this.transitionTimer) { clearInterval(this.transitionTimer); } - this.transitionTimer = setInterval(this.manualTransition, this.config.rotateStreamTimeout * 1000); + if (this.transitionTimer) { + clearInterval(this.transitionTimer); + } + this.transitionTimer = setInterval( + this.manualTransition, + this.config.rotateStreamTimeout * 1000 + ); } }, /* suspend() * This method is called when a module is hidden. */ - suspend: function() { + suspend() { console.log(`${this.name} is suspended...`); this.suspended = true; this.stopAllStreams(false); - if (this.selectedStream) { this.selectStream(undefined, true); } + if (this.selectedStream) { + this.selectStream(undefined, true); + } }, /* resume() * This method is called when a module is shown. */ - resume: function() { + resume() {}, - }, - - resumed: function(callback) { - console.log(`${this.name} has resumed... rotateStreams: ${this.config.rotateStreams}, autoStart: ${this.config.autoStart}`); + resumed(callback) { + console.log( + `${this.name} has resumed... rotateStreams: ${this.config.rotateStreams}, autoStart: ${this.config.autoStart}` + ); this.suspended = false; if (this.loaded) { if (this.config.rotateStreams) { @@ -171,33 +199,40 @@ Module.register("MMM-RTSPStream", { this.playAll(); } else { console.log("Playing all snapshots"); - Object.keys(this.streams).forEach(s => this.playSnapshots(s)); + Object.keys(this.streams).forEach((s) => this.playSnapshots(s)); } } - if (typeof callback === "function") { callback(); } + if (typeof callback === "function") { + callback(); + } }, // Overwrite the module show method to force a callback. - show: function(speed, callback, options) { + show(speed, callback, options) { if (typeof callback === "object") { options = callback; - callback = function() {}; + callback = function () {}; } - newCallback = () => { this.resumed(callback); }; + const newCallback = () => { + this.resumed(callback); + }; options = options || {}; MM.showModule(this, speed, newCallback, options); }, - playBtnCallback: function(s) { - var ps = []; + playBtnCallback(s) { + let ps = []; if (this.config.rotateStreams) { if (this.playing) { this.stopStream(this.currentStream); this.playSnapshots(this.currentStream); } else { - this.sendSocketNotification("SNAPSHOT_STOP", this.currentStream); + this.sendSocketNotification( + "SNAPSHOT_STOP", + this.currentStream + ); ps = this.playStream(this.currentStream); if (ps.length > 0) { if (this.config.localPlayer === "omxplayer") { @@ -207,27 +242,25 @@ Module.register("MMM-RTSPStream", { } } } + } else if (this.streams[s].playing) { + this.stopStream(s); + this.playSnapshots(s); } else { - if (this.streams[s].playing) { - this.stopStream(s); - this.playSnapshots(s); - } else { - this.sendSocketNotification("SNAPSHOT_STOP", s); - ps = this.playStream(s); - if (ps.length > 0) { - if (this.config.localPlayer === "omxplayer") { - this.sendSocketNotification("PLAY_OMXSTREAM", ps); - } else if (this.config.localPlayer === "vlc") { - this.sendSocketNotification("PLAY_VLCSTREAM", ps); - } + this.sendSocketNotification("SNAPSHOT_STOP", s); + ps = this.playStream(s); + if (ps.length > 0) { + if (this.config.localPlayer === "omxplayer") { + this.sendSocketNotification("PLAY_OMXSTREAM", ps); + } else if (this.config.localPlayer === "vlc") { + this.sendSocketNotification("PLAY_VLCSTREAM", ps); } } } }, - playBtnDblClickCB: function(s) { + playBtnDblClickCB(s) { if (this.instance === "SERVER" && !this.streams[s].playing) { - var ps = this.playStream(s, true); + const ps = this.playStream(s, true); if (ps.length > 0) { if (this.config.localPlayer === "omxplayer") { this.sendSocketNotification("PLAY_OMXSTREAM", ps); @@ -240,18 +273,16 @@ Module.register("MMM-RTSPStream", { } }, - getDom: function() { - var self = this; - + getDom() { // create element wrapper for show into the module - var wrapper = document.createElement("div"); + const wrapper = document.createElement("div"); if (this.config.initialSetup) { wrapper.innerHTML = `Use config wizard at http://${global.location.hostname}:${global.location.port}/${this.name}/config.html
to generate a configuration for this moudle.`; return wrapper; } if (!this.loaded) { - wrapper.innerHTML = "Loading " + this.name + "..."; + wrapper.innerHTML = `Loading ${this.name}...`; wrapper.className = "dimmed light small"; return wrapper; } @@ -265,13 +296,13 @@ Module.register("MMM-RTSPStream", { wrapper.className = "MMM-RTSPStream wrapper"; if (this.config.rotateStreams) { - iw = this.getInnerWrapper(''); - iw.appendChild(this.getCanvas('')); - iw.appendChild(this.getPlayPauseBtn('')); + const iw = this.getInnerWrapper(""); + iw.appendChild(this.getCanvas("")); + iw.appendChild(this.getPlayPauseBtn("")); wrapper.appendChild(iw); } else { - Object.keys(this.streams).forEach(stream => { - iw = this.getInnerWrapper(stream); + Object.keys(this.streams).forEach((stream) => { + const iw = this.getInnerWrapper(stream); iw.appendChild(this.getCanvas(stream)); iw.appendChild(this.getPlayPauseBtn(stream)); wrapper.appendChild(iw); @@ -282,102 +313,124 @@ Module.register("MMM-RTSPStream", { return wrapper; }, - getCanvasSize: function(streamConfig) { - var s = ''; - if (typeof streamConfig.width !== "undefined") { s += "width: " + streamConfig.width + "px; "; } - if (typeof streamConfig.height !== "undefined") { s += "height: " + streamConfig.height + "px; line-height: " + streamConfig.height + ";"; } + getCanvasSize(streamConfig) { + let s = ""; + if (typeof streamConfig.width !== "undefined") { + s += `width: ${streamConfig.width}px; `; + } + if (typeof streamConfig.height !== "undefined") { + s += `height: ${streamConfig.height}px; line-height: ${streamConfig.height};`; + } return s; }, - getCanvas: function(stream) { - var canvas = document.createElement("canvas"); - canvas.id = "canvas_" + stream; + getCanvas(stream) { + const canvas = document.createElement("canvas"); + canvas.id = `canvas_${stream}`; canvas.className = "MMM-RTSPStream canvas"; // if (stream) { canvas.cssText = this.getCanvasSize(this.config[stream]); } return canvas; }, - getInnerWrapper: function(stream) { - var innerWrapper = document.createElement("div"); + getInnerWrapper(stream) { + const innerWrapper = document.createElement("div"); innerWrapper.className = "MMM-RTSPStream innerWrapper"; - if (!stream) { stream = "stream1" }; + if (!stream) { + stream = "stream1"; + } innerWrapper.style.cssText = this.getCanvasSize(this.config[stream]); - innerWrapper.id = "iw_" + stream; + innerWrapper.id = `iw_${stream}`; return innerWrapper; }, - getPlayPauseBtn: function(stream, force_visible = false) { - var self = this; + getPlayPauseBtn(stream) { + const self = this; function makeOnClickHandler(s) { - return function() { + return function () { self.playBtnCallback(s); }; } function makeOnDblClickHandler(s) { - return function() { + return function () { self.playBtnDblClickCB(s); }; } - var playBtnWrapper = document.createElement("div"); + const playBtnWrapper = document.createElement("div"); playBtnWrapper.className = "control"; playBtnWrapper.onclick = makeOnClickHandler(stream); playBtnWrapper.oncontextmenu = makeOnDblClickHandler(stream); - playBtnWrapper.id = "playBtnWrapper_" + stream; + playBtnWrapper.id = `playBtnWrapper_${stream}`; - var playBtnLabel = document.createElement("label"); - playBtnLabel.id = "playBtnLabel_" + stream; + const playBtnLabel = document.createElement("label"); + playBtnLabel.id = `playBtnLabel_${stream}`; playBtnLabel.innerHTML = ''; playBtnWrapper.appendChild(playBtnLabel); return playBtnWrapper; }, - updatePlayPauseBtn(stream, force_visible = false) { - var buttonId = (this.config.rotateStreams) ? "playBtnLabel_" : "playBtnLabel_" + stream; - var button = document.getElementById(buttonId); + updatePlayPauseBtn(stream, forceVisible = false) { + const buttonId = this.config.rotateStreams + ? "playBtnLabel_" + : `playBtnLabel_${stream}`; + const button = document.getElementById(buttonId); if (!button) { // If not ready yet, retry in 1 second. - setTimeout(() => this.updatePlayPauseBtn(stream, force_visible), 1000); + setTimeout( + () => this.updatePlayPauseBtn(stream, forceVisible), + 1000 + ); return; } - if (stream !== '' && this.streams[stream].playing) { + if (stream !== "" && this.streams[stream].playing) { button.innerHTML = ''; } else { button.innerHTML = ''; } - if (force_visible) { + if (forceVisible) { button.style.cssText = "opacity: 0.6;"; button.parentElement.style.cssText = "opacity: 1;"; } else { - button.style.cssText = ''; - button.parentElement.style.cssText = ''; + button.style.cssText = ""; + button.parentElement.style.cssText = ""; } }, - playStream: function(stream, fullscreen = false, absPosition = undefined) { - var canvasId = (this.config.rotateStreams) ? "canvas_" : "canvas_" + stream; - var canvas = document.getElementById(canvasId); - var omxPayload = []; + playStream(stream, fullscreen = false, absPosition = undefined) { + const canvasId = this.config.rotateStreams + ? "canvas_" + : `canvas_${stream}`; + const canvas = document.getElementById(canvasId); + const omxPayload = []; if (this.streams[stream].playing) { this.stopStream(stream); } - if (this.instance === "SERVER" && ["omxplayer", "vlc"].indexOf(this.config.localPlayer) !== -1) { - var rect = canvas.getBoundingClientRect(); - var offset = {}; - var payload = { name: stream }; + if ( + this.instance === "SERVER" && + ["omxplayer", "vlc"].indexOf(this.config.localPlayer) !== -1 + ) { + const rect = canvas.getBoundingClientRect(); + const offset = {}; + const payload = { name: stream }; if (typeof this.config.moduleOffset === "object") { - offset.left = ("left" in this.config.moduleOffset) ? this.config.moduleOffset.left : 0; - offset.top = ("top" in this.config.moduleOffset) ? this.config.moduleOffset.top : 0; + offset.left = + "left" in this.config.moduleOffset + ? this.config.moduleOffset.left + : 0; + offset.top = + "top" in this.config.moduleOffset + ? this.config.moduleOffset.top + : 0; } else { offset.left = this.config.moduleOffset; offset.top = this.config.moduleOffset; } - var box = {}; + let box = {}; if ("absPosition" in this.config[stream]) { box = this.config[stream].absPosition; } else if (typeof absPosition !== "undefined") { @@ -395,10 +448,14 @@ Module.register("MMM-RTSPStream", { payload.box = box; omxPayload.push(payload); } else { - var ctx = canvas.getContext("2d"); + const ctx = canvas.getContext("2d"); ctx.clearRect(0, 0, canvas.width, canvas.height); - var sUrl = `ws://${document.location.hostname}:${this.config[stream].ffmpegPort}`; - var player = new JSMpeg.Player(sUrl, { canvas: canvas, disableGl: true, audio: false }); + const sUrl = `ws://${document.location.hostname}:${this.config[stream].ffmpegPort}`; + const player = new JSMpeg.Player(sUrl, { + canvas, + disableGl: true, + audio: false + }); this.streams[stream].player = player; } @@ -408,12 +465,14 @@ Module.register("MMM-RTSPStream", { return omxPayload; }, - playSnapshots: function(stream) { + playSnapshots(stream) { // Show the snapshot instead of the stream - var snapUrl = this.config[stream].snapshotUrl; - var canvasId = (this.config.rotateStreams) ? "canvas_" : "canvas_" + stream; - var canvas = document.getElementById(canvasId); - var ctx = canvas.getContext("2d"); + const snapUrl = this.config[stream].snapshotUrl; + const canvasId = this.config.rotateStreams + ? "canvas_" + : `canvas_${stream}`; + const canvas = document.getElementById(canvasId); + const ctx = canvas.getContext("2d"); ctx.clearRect(0, 0, canvas.width, canvas.height); if (snapUrl && this.config.showSnapWhenPaused) { this.sendSocketNotification("SNAPSHOT_START", stream); @@ -426,11 +485,15 @@ Module.register("MMM-RTSPStream", { } }, - playAll: function() { - var ps = []; - Object.keys(this.streams).forEach(s => { - if (this.instance === "SERVER" || (this.instance === "LOCAL" && this.config.remotePlayer === "ffmpeg")) { - var res = this.playStream(s); + playAll() { + let ps = []; + Object.keys(this.streams).forEach((s) => { + if ( + this.instance === "SERVER" || + (this.instance === "LOCAL" && + this.config.remotePlayer === "ffmpeg") + ) { + const res = this.playStream(s); if (res.length > 0) { ps = ps.concat(res); } @@ -448,12 +511,23 @@ Module.register("MMM-RTSPStream", { } }, - stopStream: function(stream, omxStopAll = false) { + stopStream(stream, omxStopAll = false) { if (this.streams[stream].playing) { - if (this.instance === "SERVER" && this.config.localPlayer === "omxplayer" && !omxStopAll) { + if ( + this.instance === "SERVER" && + this.config.localPlayer === "omxplayer" && + !omxStopAll + ) { this.sendSocketNotification("STOP_OMXSTREAM", stream); - } else if (this.instance === "SERVER" && this.config.localPlayer === "vlc" && !omxStopAll) { - this.sendSocketNotification("STOP_VLCSTREAM", { name: stream, delay: this.config.shutdownDelay }); + } else if ( + this.instance === "SERVER" && + this.config.localPlayer === "vlc" && + !omxStopAll + ) { + this.sendSocketNotification("STOP_VLCSTREAM", { + name: stream, + delay: this.config.shutdownDelay + }); } else if ("player" in this.streams[stream]) { this.streams[stream].player.destroy(); delete this.streams[stream].player; @@ -461,25 +535,37 @@ Module.register("MMM-RTSPStream", { this.streams[stream].playing = false; } - if (Object.keys(this.streams).filter(s => { return s.playing; }).length === 0) { + if ( + Object.keys(this.streams).filter((s) => { + return s.playing; + }).length === 0 + ) { this.playing = false; } }, - stopAllStreams: function(startSnapshots = true) { - var omxStopAll = false; - if (this.instance === "SERVER" && this.config.localPlayer === "omxplayer") { + stopAllStreams(startSnapshots = true) { + let omxStopAll = false; + if ( + this.instance === "SERVER" && + this.config.localPlayer === "omxplayer" + ) { this.sendSocketNotification("STOP_ALL_OMXSTREAMS", ""); omxStopAll = true; } if (this.instance === "SERVER" && this.config.localPlayer === "vlc") { - this.sendSocketNotification("STOP_ALL_VLCSTREAMS", this.config.shutdownDelay); + this.sendSocketNotification( + "STOP_ALL_VLCSTREAMS", + this.config.shutdownDelay + ); omxStopAll = true; } - Object.keys(this.streams).forEach(s => { + Object.keys(this.streams).forEach((s) => { this.stopStream(s, omxStopAll); if (startSnapshots) { - if (!this.config.rotateStreams) { this.playSnapshots(s); } + if (!this.config.rotateStreams) { + this.playSnapshots(s); + } } else { this.sendSocketNotification("SNAPSHOT_STOP", s); } @@ -491,37 +577,62 @@ Module.register("MMM-RTSPStream", { } }, - toggleStreams: function(payload) { - var ps = []; + toggleStreams(payload) { + let ps = []; if (this.config.rotateStreams) { if (this.playing) { this.stopStream(this.currentStream); this.playSnapshots(this.currentStream); } else { - if ((this.instance === "SERVER" && !this.config.remoteSnaps) || - (this.instance === "LOCAL" && this.config.remotePlayer === "ffmpeg")) { - this.sendSocketNotification("SNAPSHOT_STOP", this.currentStream); + if ( + (this.instance === "SERVER" && !this.config.remoteSnaps) || + (this.instance === "LOCAL" && + this.config.remotePlayer === "ffmpeg") + ) { + this.sendSocketNotification( + "SNAPSHOT_STOP", + this.currentStream + ); } - if (this.instance === "SERVER" || (this.instance === "LOCAL" && this.config.remotePlayer === "ffmpeg")) { + if ( + this.instance === "SERVER" || + (this.instance === "LOCAL" && + this.config.remotePlayer === "ffmpeg") + ) { ps = this.playStream(this.currentStream); } } } else if (!this.selectedStream) { - if (this.playing) { this.stopAllStreams(); } else { this.playAll(); } - } else { - if (this.streams[this.selectedStream].playing) { - this.stopStream(this.selectedStream); - this.playSnapshots(this.selectedStream); + if (this.playing) { + this.stopAllStreams(); } else { - if ((this.instance === "SERVER" && !this.config.remoteSnaps) || - (this.instance === "LOCAL" && this.config.remotePlayer === "ffmpeg")) { - this.sendSocketNotification("SNAPSHOT_STOP", this.selectedStream); - } - if (this.instance === "SERVER" && payload.KeyState === "KEY_LONGPRESSED") { - ps = this.playStream(this.selectedStream, true); - } else if (this.instance === "SERVER" || (this.instance === "LOCAL" && this.config.remotePlayer === "ffmpeg")) { - ps = this.playStream(this.selectedStream); - } + this.playAll(); + } + } else if (this.streams[this.selectedStream].playing) { + this.stopStream(this.selectedStream); + this.playSnapshots(this.selectedStream); + } else { + if ( + (this.instance === "SERVER" && !this.config.remoteSnaps) || + (this.instance === "LOCAL" && + this.config.remotePlayer === "ffmpeg") + ) { + this.sendSocketNotification( + "SNAPSHOT_STOP", + this.selectedStream + ); + } + if ( + this.instance === "SERVER" && + payload.KeyState === "KEY_LONGPRESSED" + ) { + ps = this.playStream(this.selectedStream, true); + } else if ( + this.instance === "SERVER" || + (this.instance === "LOCAL" && + this.config.remotePlayer === "ffmpeg") + ) { + ps = this.playStream(this.selectedStream); } } if (ps.length > 0) { @@ -533,30 +644,43 @@ Module.register("MMM-RTSPStream", { } }, - getScripts: function() { - return [this.file('scripts/jsmpeg.min.js')]; + getScripts() { + return [this.file("scripts/jsmpeg.min.js")]; }, - getStyles: function() { - return [`${this.name}.css`, 'font-awesome.css']; + getStyles() { + return [`${this.name}.css`, "font-awesome.css"]; }, - notificationReceived: function(notification, payload, sender) { - var ps = []; + notificationReceived(notification, payload, sender) { + let ps = []; if (notification === "DOM_OBJECTS_CREATED") { // Register Key Handler - if (this.config.keyBindings.enabled && - MM.getModules().filter(kb => kb.name === "MMM-KeyBindings").length > 0) { - this.keyBindings = Object.assign({}, this.keyBindings, this.config.keyBindings); + if ( + this.config.keyBindings.enabled && + MM.getModules().filter((kb) => kb.name === "MMM-KeyBindings") + .length > 0 + ) { + this.keyBindings = { + ...this.keyBindings, + ...this.config.keyBindings + }; KeyHandler.register(this.name, { - sendNotification: (n, p) => { this.sendNotification(n, p); }, - validKeyPress: (kp) => { this.validKeyPress(kp); }, + sendNotification: (n, p) => { + this.sendNotification(n, p); + }, + validKeyPress: (kp) => { + this.validKeyPress(kp); + } }); - this.keyHandler = KeyHandler.create(this.name, this.keyBindings); + this.keyHandler = KeyHandler.create( + this.name, + this.keyBindings + ); } - let api = { + const api = { module: this.name, path: "stream", actions: { @@ -575,12 +699,17 @@ Module.register("MMM-RTSPStream", { window: { notification: "RSTP-PLAY-WINDOW", prettyName: "Play in Window" - }, + } } }; this.sendNotification("REGISTER_API", api); } - if (this.keyHandler && this.keyHandler.validate(notification, payload)) { return; } + if ( + this.keyHandler && + this.keyHandler.validate(notification, payload) + ) { + return; + } // Handle USER_PRESENCE events from the MMM-PIR-sensor Module if (notification === "USER_PRESENCE") { @@ -594,8 +723,8 @@ Module.register("MMM-RTSPStream", { this.suspendedForUserPresence = true; } } - if (notification === 'RTSP-PLAY' && this.instance === "SERVER") { - if (!payload || payload === {} || payload === 'all') { + if (notification === "RTSP-PLAY" && this.instance === "SERVER") { + if (!payload || payload === {} || payload === "all") { if (this.config.rotateStreams) { this.playing = true; this.manualTransition(undefined, 1); @@ -603,24 +732,25 @@ Module.register("MMM-RTSPStream", { } else { this.playAll(); } + } else if (this.config.rotateStreams) { + this.playing = true; + this.manualTransition(payload); + this.restartTimer(); } else { - if (this.config.rotateStreams) { - this.playing = true; - this.manualTransition(payload); - this.restartTimer(); - } else { - ps = this.playStream(payload); - } + ps = this.playStream(payload); } } - if (notification === 'RTSP-PLAY-FULLSCREEN' && this.instance === "SERVER") { + if ( + notification === "RTSP-PLAY-FULLSCREEN" && + this.instance === "SERVER" + ) { ps = this.playStream(payload, true); } - if (notification === 'RSTP-PLAY-WINDOW' && this.instance === "SERVER") { + if (notification === "RSTP-PLAY-WINDOW" && this.instance === "SERVER") { ps = this.playStream(payload.name, false, payload.box); } - if (notification === 'RTSP-STOP' && this.instance === "SERVER") { - if (!payload || payload === {} || payload === 'all') { + if (notification === "RTSP-STOP" && this.instance === "SERVER") { + if (!payload || payload === {} || payload === "all") { this.stopAllStreams(); } else { this.stopStream(payload); @@ -636,7 +766,7 @@ Module.register("MMM-RTSPStream", { } }, - validKeyPress: function(kp) { + validKeyPress(kp) { // Example for responding to "Left" and "Right" arrow console.log(kp); if (kp.keyName === this.keyHandler.config.map.Play) { @@ -659,16 +789,16 @@ Module.register("MMM-RTSPStream", { } }, - selectedStream: '', + selectedStream: "", - selectStream: function(direction = 1, clear = false) { - var k = Object.keys(this.streams); + selectStream(direction = 1, clear = false) { + const k = Object.keys(this.streams); if (!clear) { if (!this.selectedStream) { this.selectedStream = k[0]; } else { - var i = k.indexOf(this.selectedStream); - var newI = i + direction; + const i = k.indexOf(this.selectedStream); + let newI = i + direction; if (newI >= k.length) { newI = 0; } else if (newI < 0) { @@ -677,44 +807,51 @@ Module.register("MMM-RTSPStream", { this.selectedStream = k[newI]; } } else { - this.selectedStream = ''; + this.selectedStream = ""; } - k.forEach(s => { + k.forEach((s) => { if (s !== this.selectedStream) { - var iw = document.getElementById("iw_" + s); - iw.style.cssText = iw.style.cssText.replace("border-color: red;", ""); + const iw = document.getElementById(`iw_${s}`); + iw.style.cssText = iw.style.cssText.replace( + "border-color: red;", + "" + ); } else { - document.getElementById("iw_" + s).style.cssText += "border-color: red;"; + document.getElementById(`iw_${s}`).style.cssText += + "border-color: red;"; } }); }, // socketNotificationReceived from helper - socketNotificationReceived: function(notification, payload) { + socketNotificationReceived(notification, payload) { if (notification === "STARTED") { if (!this.loaded) { this.loaded = true; this.updateDom(this.config.animationSpeed); if (!this.suspended) { - setTimeout(() => this.resumed(), this.config.animationSpeed + 500); + setTimeout( + () => this.resumed(), + this.config.animationSpeed + 500 + ); } } } if (notification === "SNAPSHOT") { if (payload.image) { - var img = document.getElementById("img_" + payload.name) + let img = document.getElementById(`img_${payload.name}`); if (img === null) { - iw = document.getElementById("iw_" + payload.name) + const iw = document.getElementById(`iw_${payload.name}`); img = document.createElement("img"); img.style.width = "100%"; img.style.height = "100%"; - img.id = "img_" + payload.name; - console.log(iw, img) - img.className = "MMM-RTSPStream snapshot" - iw.appendChild(img) + img.id = `img_${payload.name}`; + console.log(iw, img); + img.className = "MMM-RTSPStream snapshot"; + iw.appendChild(img); } img.src = payload.buffer; } } - }, + } }); diff --git a/README.md b/README.md index 4ce8bd9..0fa6a5f 100644 --- a/README.md +++ b/README.md @@ -8,37 +8,37 @@ This module will show a live RTSP video stream and/or periodic snapshots on the > > Why? > -> - I am no longer using this module on my own mirror. After several years, I found that I use the snapshots much more frequently than I streamed the actual cameras, which can be performed by much simpler modules and methods. To enable streaming, WebRTC (like [MMM-HomeAssistant-WebRTC](https://github.com/Anonym-tsk/MMM-HomeAssistant-WebRTC)) is a newer and better standard with much lower server overhead and latency for delivering RTSP Streams to the frontend than any of the options used here, in the future, this will be the method I focus on and I will not try to shoehorn another technology into this module. -> - Update 5-Oct-2022: See alternative module [MMM-RTSPtoWeb](https://github.com/shbatm/MMM-RTSPtoWeb) for a drastically simplified module relying on WebRTC and a backend server. +> - I am no longer using this module on my own mirror. After several years, I found that I use the snapshots much more frequently than I streamed the actual cameras, which can be performed by much simpler modules and methods. To enable streaming, WebRTC (like [MMM-HomeAssistant-WebRTC](https://github.com/Anonym-tsk/MMM-HomeAssistant-WebRTC)) is a newer and better standard with much lower server overhead and latency for delivering RTSP Streams to the frontend than any of the options used here, in the future, this will be the method I focus on and I will not try to shoehorn another technology into this module. +> - Update 5-Oct-2022: See alternative module [MMM-RTSPtoWeb](https://github.com/shbatm/MMM-RTSPtoWeb) for a drastically simplified module relying on WebRTC and a backend server. ## Features -- Supports single or multiple camera streams/snapshots -- For multiple streams: supports rotating through streams in a single window or displaying multiple windows (with customizeable layout) -- Supports fetching snapshots from a file or url when not actively streaming -- Flexible configurations to limit resource use on Raspberry Pi -- - - Stops all streams when module is hidden - - Option for AutoPlay or manual starting of stream - - Plays one or all streams (when displaying multiple) - - *Note:* 3 simultaneous streams on a RaspberryPi 3 is about the limit for usability. -- Support for [MMM-KeyBindings](https://github.com/shbatm/MMM-KeyBindings) module for Play/Pause Remote Control and navigation of multiple streams -- Hardware-Accelerated Playback on the main screen, with option to use software playback on a remote browser window. -- When using `omxplayer` or `vlc`, double-clicking the play button (or longpressing PlayPause key if using MMM-KeyBindings) will play the video fullscreen. Click anywhere once (or Pause with MMM-KeyBindings) to exit. +- Supports single or multiple camera streams/snapshots +- For multiple streams: supports rotating through streams in a single window or displaying multiple windows (with customizeable layout) +- Supports fetching snapshots from a file or url when not actively streaming +- Flexible configurations to limit resource use on Raspberry Pi -- + - Stops all streams when module is hidden + - Option for AutoPlay or manual starting of stream + - Plays one or all streams (when displaying multiple) + - _Note:_ 3 simultaneous streams on a RaspberryPi 3 is about the limit for usability. +- Support for [MMM-KeyBindings](https://github.com/shbatm/MMM-KeyBindings) module for Play/Pause Remote Control and navigation of multiple streams +- Hardware-Accelerated Playback on the main screen, with option to use software playback on a remote browser window. +- When using `omxplayer` or `vlc`, double-clicking the play button (or longpressing PlayPause key if using MMM-KeyBindings) will play the video fullscreen. Click anywhere once (or Pause with MMM-KeyBindings) to exit. ## Dependencies -- The following packages are required for the module to function fully and the installer will attempt to install them with `apt`: - - `ffmpeg`, `omxplayer`, `vlc`, `devilspie2`, `wmctrl` -- For hardware-accelerated streaming, `vlc` or `omxplayer` is required. -- For manipulating VLC's windows, `devilspie2` and `wmctrl` are used. -- For software-decoded streaming and/or remote browser viewing: - - Requires `jsmpeg` for front-end display of stream. - - Requires `node-rtsp-stream-es6` Node.js module and `ffmpeg` for backend. - - Video flow using `'ffmpeg'`: Camera RTSP Stream → `ffmpeg` pre-processor → MM module's `node_helper.js` (via `node-rtsp-stream-es6`) → Web Socket (`ws`) → MagicMirror² (via `jsmpeg`) +- The following packages are required for the module to function fully and the installer will attempt to install them with `apt`: + - `ffmpeg`, `omxplayer`, `vlc`, `devilspie2`, `wmctrl` +- For hardware-accelerated streaming, `vlc` or `omxplayer` is required. +- For manipulating VLC's windows, `devilspie2` and `wmctrl` are used. +- For software-decoded streaming and/or remote browser viewing: + - Requires `jsmpeg` for front-end display of stream. + - Requires `node-rtsp-stream-es6` Node.js module and `ffmpeg` for backend. + - Video flow using `'ffmpeg'`: Camera RTSP Stream → `ffmpeg` pre-processor → MM module's `node_helper.js` (via `node-rtsp-stream-es6`) → Web Socket (`ws`) → MagicMirror² (via `jsmpeg`) ## Screenshot -![](screenshot.png) +![Screenshot with 3 streams](screenshot.png) ## Installation @@ -87,21 +87,21 @@ npm install It is highly recommended you use the tool included. Several sample configurations are available on [this wiki page](https://github.com/shbatm/MMM-RTSPStream/wiki/Sample-Configurations), detailed options are listed below. -| Option | Description -|----------------- |----------- -| `autoStart` | Start the stream(s) automatically
*Default:* `true` -| `rotateStreams` | `true`: Rotate through all streams in a single window
`false`: Display an individual window for each stream
*Default:* `true` -| `rotateStreamTimeout` | Time (in sec) to show each stream when `rotateStreams` is `true`.
*Default:* `10` -| `localPlayer` | *Optional:* Which player to use for local playback: `vlc`, `ffmpeg` or `omxplayer`.
*Default:* `vlc` for hardware acceleration. -| `remotePlayer` | *Optional:* Which player to use for remote browser playback: `ffmpeg` or `none`.
*Default:* `ffmpeg`. Set to `none` to disable remote playback. -| `remoteSnaps` | *Optional:* If `true`, module will continue to show snapshots for any remote browser windows while playing the stream locally. Using `false` will stop updating snapshots when playing locally. Use this option if you only use the local screen to save resources.
*Default:* `true`. -| `showSnapWhenPaused` | Whether or not to show snapshots when the stream(s) is paused.
*Default:* `true` -| `moduleWidth` | Width in `px` of the module.
*Note:* When `rotateStreams` is `false` and multiple streams are used, adjust this value to adjust the number of streams shown side by side. E.G. to show 2 streams side by side, this value should be `= 2*(Stream Width + 2*1px (border) + 2*15px (margin))`
*Default:* `354px` -| `moduleHeight` | Similar (but less critical) to `moduleWidth`. Adjust to the number of streams high to ensure other modules clear.
*Default:* `240px` -| `moduleOffset` | *Only applies when using OMXPlayer.* On some displays, the video does not properly line up with the box on the screen because of differences between JavaScript's reporting and the native display. Entering a pixel value will shift the video over by that amount.
*Default:* `0` *Values:* Any number (no units) by itself will adjust both top/left the same amount, or you can specify left & top adjustments separately (e.g. `moduleOffset: { left: 10, top: -10 }` -| `shutdownDelay` | The time delay (in sec) between when the last client disconnects and the `ffmpeg` or `vlc` stream actually stops. Once created, the websocket continues to run in the background; however, the `ffmpeg` process will only process the camera's stream while there are active connections on the socket (e.g. someone is watching the video on the frontend). When rotating through multiple streams this prevents closing the connection to a stream only to re-open a few seconds later when it comes back through the loop (which reduces the time delay when restarting a stream). To conserve resources on a slow device, you can set this to 0
*Default:* 11 (sec) -| `debug` | Set to `true` to show additional logging information. -| `streamX` | The individual stream configuration options. See table below for more details. +| Option | Description | +| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `autoStart` | Start the stream(s) automatically
_Default:_ `true` | +| `rotateStreams` | `true`: Rotate through all streams in a single window
`false`: Display an individual window for each stream
_Default:_ `true` | +| `rotateStreamTimeout` | Time (in sec) to show each stream when `rotateStreams` is `true`.
_Default:_ `10` | +| `localPlayer` | _Optional:_ Which player to use for local playback: `vlc`, `ffmpeg` or `omxplayer`.
_Default:_ `vlc` for hardware acceleration. | +| `remotePlayer` | _Optional:_ Which player to use for remote browser playback: `ffmpeg` or `none`.
_Default:_ `ffmpeg`. Set to `none` to disable remote playback. | +| `remoteSnaps` | _Optional:_ If `true`, module will continue to show snapshots for any remote browser windows while playing the stream locally. Using `false` will stop updating snapshots when playing locally. Use this option if you only use the local screen to save resources.
_Default:_ `true`. | +| `showSnapWhenPaused` | Whether or not to show snapshots when the stream(s) is paused.
_Default:_ `true` | +| `moduleWidth` | Width in `px` of the module.
_Note:_ When `rotateStreams` is `false` and multiple streams are used, adjust this value to adjust the number of streams shown side by side. E.G. to show 2 streams side by side, this value should be `= 2*(Stream Width + 2*1px (border) + 2*15px (margin))`
_Default:_ `354px` | +| `moduleHeight` | Similar (but less critical) to `moduleWidth`. Adjust to the number of streams high to ensure other modules clear.
_Default:_ `240px` | +| `moduleOffset` | _Only applies when using OMXPlayer._ On some displays, the video does not properly line up with the box on the screen because of differences between JavaScript's reporting and the native display. Entering a pixel value will shift the video over by that amount.
_Default:_ `0` _Values:_ Any number (no units) by itself will adjust both top/left the same amount, or you can specify left & top adjustments separately (e.g. `moduleOffset: { left: 10, top: -10 }` | +| `shutdownDelay` | The time delay (in sec) between when the last client disconnects and the `ffmpeg` or `vlc` stream actually stops. Once created, the websocket continues to run in the background; however, the `ffmpeg` process will only process the camera's stream while there are active connections on the socket (e.g. someone is watching the video on the frontend). When rotating through multiple streams this prevents closing the connection to a stream only to re-open a few seconds later when it comes back through the loop (which reduces the time delay when restarting a stream). To conserve resources on a slow device, you can set this to 0
_Default:_ 11 (sec) | +| `debug` | Set to `true` to show additional logging information. | +| `streamX` | The individual stream configuration options. See table below for more details. | ### Stream Configuration Options @@ -122,25 +122,25 @@ config: { } ``` -| Option | Description -|----------------- |----------- -| `name` | *Required* The name of the individual stream. Will be displayed when paused if snapshots are turned off. -| `url` | The url of the RTSP stream. See [this list](https://github.com/shbatm/MMM-RTSPStream/wiki/Stream-URLs-for-Various-Cameras) for paths for some common security cameras. Also see below for how to test for a valid url
Username and password should be passed in the url if required: `rtsp://:@:/`
*Default:* A test stream at `'rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov'`, -| `hdUrl` | *Optional:* The url for the "High-Def" stream to use when playing a full screen stream with OMXPlayer. If blank, regular url will be used. -| `protocol` | Protocol to use for receiving RTSP stream
*Default:* `"tcp"`, valid options: `"tcp"` or `"udp"`. -| `snapshotUrl` | A string with the path to the camera snapshot. This can either be a url to camera itself (if supported) or a file path to where the snapshot is stored every X seconds by the camera. Leave blank to show just the stream title when paused.
Username and password should be passed in the url if required: `http://:@:/` -| `snapshotType` | The type of snapshot path given
*Values:* `url` or `file`
*Default:* `url` -| `snapshotRefresh` | How often to refresh the snapshot image (in sec).
*Default:* 10 (seconds) -| `frameRate` | Framerate to use for the RTSP stream. Must be a string.
*Default:* `"30"` -| `width` | The width in px of the stream. -| `height` | The height in px of the stream. -| `absPosition` | *Only required for OMXPlayer* Provide an absolute potiion to show the stream. This overrides the automatic window and moduleOffset settings.
*Format:* `{ top: XX, right: XX, bottom: XX, left: XX }` where `XX` is the pixel position on the screen. -| `ffmpegPort` | *Only required for `ffmpeg`* Any available port to use for the ffmpeg websocket.
***Notes:*** **THIS IS NOT THE PORT FOR YOUR CAMERA** Camera stream's port must be included in the URL above. This port must be unqiue for each stream added and cannot be used by another service on the server. This is a separate WebSocket from the the Socket.IO connection between the module's script and it's `node_helper.js`.
*Default:* `9999` -| `hwAccel` | *Only required for `ffmpeg`* Attempt to use Hardware Accelerated Decoding with `ffmpeg`.
*Default:* `false` -| `muted` | Disable sound (*OMXPlayer and VLC only*)
*Default:* `false` -| `timeout` | Timeout for stalled file/network operations (*OMXPlayer only*)
*Default:* `10` (seconds) -| `rotateDegree` | Set orientation of video (*OMXPlayer only*)
Available values: `0`, `90`, `180` or `270`
*Default:* `0` -| `omxRestart` | Automatically restart the OMX Stream every X hours.
*Default:* `24` (hours). +| Option | Description | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `name` | _Required_ The name of the individual stream. Will be displayed when paused if snapshots are turned off. | +| `url` | The url of the RTSP stream. See [this list](https://github.com/shbatm/MMM-RTSPStream/wiki/Stream-URLs-for-Various-Cameras) for paths for some common security cameras. Also see below for how to test for a valid url
Username and password should be passed in the url if required: `rtsp://:@:/`
_Default:_ A test stream at `'rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov'`, | +| `hdUrl` | _Optional:_ The url for the "High-Def" stream to use when playing a full screen stream with OMXPlayer. If blank, regular url will be used. | +| `protocol` | Protocol to use for receiving RTSP stream
_Default:_ `"tcp"`, valid options: `"tcp"` or `"udp"`. | +| `snapshotUrl` | A string with the path to the camera snapshot. This can either be a url to camera itself (if supported) or a file path to where the snapshot is stored every X seconds by the camera. Leave blank to show just the stream title when paused.
Username and password should be passed in the url if required: `http://:@:/` | +| `snapshotType` | The type of snapshot path given
_Values:_ `url` or `file`
_Default:_ `url` | +| `snapshotRefresh` | How often to refresh the snapshot image (in sec).
_Default:_ 10 (seconds) | +| `frameRate` | Framerate to use for the RTSP stream. Must be a string.
_Default:_ `"30"` | +| `width` | The width in px of the stream. | +| `height` | The height in px of the stream. | +| `absPosition` | _Only required for OMXPlayer_ Provide an absolute potiion to show the stream. This overrides the automatic window and moduleOffset settings.
_Format:_ `{ top: XX, right: XX, bottom: XX, left: XX }` where `XX` is the pixel position on the screen. | +| `ffmpegPort` | _Only required for `ffmpeg`_ Any available port to use for the ffmpeg websocket.
**_Notes:_** **THIS IS NOT THE PORT FOR YOUR CAMERA** Camera stream's port must be included in the URL above. This port must be unqiue for each stream added and cannot be used by another service on the server. This is a separate WebSocket from the the Socket.IO connection between the module's script and it's `node_helper.js`.
_Default:_ `9999` | +| `hwAccel` | _Only required for `ffmpeg`_ Attempt to use Hardware Accelerated Decoding with `ffmpeg`.
_Default:_ `false` | +| `muted` | Disable sound (_OMXPlayer and VLC only_)
_Default:_ `false` | +| `timeout` | Timeout for stalled file/network operations (_OMXPlayer only_)
_Default:_ `10` (seconds) | +| `rotateDegree` | Set orientation of video (_OMXPlayer only_)
Available values: `0`, `90`, `180` or `270`
_Default:_ `0` | +| `omxRestart` | Automatically restart the OMX Stream every X hours.
_Default:_ `24` (hours). | #### Testing a camera feed @@ -148,7 +148,7 @@ To test to make sure you have a working url for a camera feed: create a text fil #### Advanced Stream Configurations -This module has been tested exclusively with streams for Hikvision (Swann) cameras. You may find that you need to adjust the `ffmpeg` settings that are used beyond just frame rate and size. The command line arguements for `ffmpeg` can be changed by editing Line 14 of the following file after install. The `ffmpeg` arguement list is passed as an array. +This module has been tested exclusively with streams for Hikvision (Swann) cameras. You may find that you need to adjust the `ffmpeg` settings that are used beyond just frame rate and size. The command line arguements for `ffmpeg` can be changed by editing Line 14 of the following file after install. The `ffmpeg` arguement list is passed as an array. ```shell ~/MagicMirror/modules/MMM-RTSPStream/node_modules/node-rtsp-stream-es6/src/mpeg1muxer.js @@ -156,25 +156,28 @@ This module has been tested exclusively with streams for Hikvision (Swann) camer ### Controlling from other modules -The streams can be controlled on the main screen by sending a module notification. Examples: +The streams can be controlled on the main screen by sending a module notification. Examples: ```js this.sendNotification("RTSP-PLAY", "all"); // Play all streams (or current stream if rotating) this.sendNotification("RTSP-PLAY", "streamX"); // Play a particular stream (when not rotating) this.sendNotification("RTSP-PLAY-FULLSCREEN", "streamX"); // Play a particular stream fullscreen (when using OMXPLAYER) -this.sendNotification("RTSP-PLAY-WINDOW", { name:"streamX", box: { top: XX, right: XX, bottom: XX, left: XX } }); // Play a particular stream in a custom window (when using OMXPLAYER) +this.sendNotification("RTSP-PLAY-WINDOW", { + name: "streamX", + box: { top: XX, right: XX, bottom: XX, left: XX } +}); // Play a particular stream in a custom window (when using OMXPLAYER) this.sendNotification("RTSP-STOP", "all"); // Stop the streams this.sendNotification("RTSP-STOP", "streamX"); // Stop a particular stream ``` ### KeyBindings Configuration (Requires [MMM-KeyBindings](https://github.com/shbatm/MMM-KeyBindings)) -*To change from the defaults, add changes to the end of the module's configuration section* +_To change from the defaults, add changes to the end of the module's configuration section_ -| Option | Description -|----------------- |----------- -| `mode` | *Default:* `"DEFAULT"` - Will respond to a key press if no other module has the focus.
*Note:* - To enable this module to take focus, change this value and add a `Focus` key name below. -| `map` | The map between this module's key functions and the Keyboard / MMM-KeyBinding's key name that is sent (i.e. when the "MediaPlayPause" key is pressed, it will send a `Play` action to this module).
`Previous`/`Next` actions will cycle through the streams when `rotateStreams` is enabled, and will change which stream is selected when multiple streams are shown (red border will appear around selected stream). +| Option | Description | +| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `mode` | _Default:_ `"DEFAULT"` - Will respond to a key press if no other module has the focus.
_Note:_ - To enable this module to take focus, change this value and add a `Focus` key name below. | +| `map` | The map between this module's key functions and the Keyboard / MMM-KeyBinding's key name that is sent (i.e. when the "MediaPlayPause" key is pressed, it will send a `Play` action to this module).
`Previous`/`Next` actions will cycle through the streams when `rotateStreams` is enabled, and will change which stream is selected when multiple streams are shown (red border will appear around selected stream). | ```js keyBindings: { @@ -190,9 +193,9 @@ keyBindings: { ## To-do -- Add better touchscreen support (use an OnTouch method to play/pause instead of OnClick). -- KNOWN ISSUE: snapshots can be stopped by another "instance" of the mirror running in a different window. Expected behavior: should only affect the local window. -- KNOWN ISSUE: `omxplayer` will only play a certain maximum number of streams at a time. On a RPi3, this appears to be a max of 3. It won't error, it just won't play another stream. To fix: adjust the memory split of the GPU/CPU using the `raspi-config` command. +- Add better touchscreen support (use an OnTouch method to play/pause instead of OnClick). +- KNOWN ISSUE: snapshots can be stopped by another "instance" of the mirror running in a different window. Expected behavior: should only affect the local window. +- KNOWN ISSUE: `omxplayer` will only play a certain maximum number of streams at a time. On a RPi3, this appears to be a max of 3. It won't error, it just won't play another stream. To fix: adjust the memory split of the GPU/CPU using the `raspi-config` command. ## Experimentation diff --git a/node_helper.js b/node_helper.js index 504369f..98df3bf 100644 --- a/node_helper.js +++ b/node_helper.js @@ -9,8 +9,8 @@ const child_process = require("child_process"); const fs = require("fs"); const path = require("path"); const DataURI = require("datauri"); -var NodeHelper = require("node_helper"); -var Stream = require("node-rtsp-stream-es6"); +const NodeHelper = require("node_helper"); +const Stream = require("node-rtsp-stream-es6"); const datauri = new DataURI(); const psTree = require("ps-tree"); @@ -32,24 +32,23 @@ module.exports = NodeHelper.create({ snapshots: {}, - start: function () { + start() { this.started = false; this.stopAllOmxplayers(); }, - stop: function () { + stop() { console.log( - "Shutting down MMM-RTSPStream streams that were using " + - this.config.localPlayer + `Shutting down MMM-RTSPStream streams that were using ${this.config.localPlayer}` ); // Kill any running OMX Streams if (this.config.localPlayer === "omxplayer") { child_process.spawn( - path.resolve(__dirname + "/scripts/onexit.js"), + path.resolve(`${__dirname}/scripts/onexit.js`), { stdio: "ignore", - detached: true, + detached: true } ); } @@ -76,7 +75,7 @@ module.exports = NodeHelper.create({ } }, - startListener: function (name) { + startListener(name) { if ( (this.config.localPlayer === "ffmpeg" || this.config.remotePlayer === "ffmpeg") && @@ -93,14 +92,14 @@ module.exports = NodeHelper.create({ } }, - getData: function (name) { + getData(name) { // console.log("Getting data for "+name); - var self = this; + const self = this; - var snapUrl = this.config[name].snapshotUrl; + const snapUrl = this.config[name].snapshotUrl; if (!snapUrl) { - console.log("No snapshotUrl given for " + name + ". Ignoring."); + console.log(`No snapshotUrl given for ${name}. Ignoring.`); return; } @@ -113,14 +112,14 @@ module.exports = NodeHelper.create({ throw err; } self.sendSocketNotification("SNAPSHOT", { - name: name, + name, image: true, - buffer: content, + buffer: content }); }); } else { fetch(snapUrl, { - method: "GET", + method: "GET" }) .then(async (response) => { if (response.status === 200) { @@ -128,24 +127,22 @@ module.exports = NodeHelper.create({ self.sendSocketNotification("SNAPSHOT", { name: name, image: true, - buffer: - "data:image/jpeg;base64," + buffer.toString("base64"), + buffer: `data:image/jpeg;base64,${buffer.toString( + "base64" + )}` }); - return; } else if (response.status === 401) { self.sendSocketNotification( - "DATA_ERROR_" + name, + `DATA_ERROR_${name}`, "401 Error" ); console.error(self.name, "401 Error"); - return; } else { console.error( self.name, "Could not load data.", response.statusText ); - return; } }) .catch((error) => { @@ -154,7 +151,6 @@ module.exports = NodeHelper.create({ "ERROR: Could not load data.", error ); - return; }); return; } @@ -163,15 +159,15 @@ module.exports = NodeHelper.create({ }, this.config[name].snapshotRefresh * 1000); }, - getVlcPlayer: function (payload) { - var self = this; - var opts = { + getVlcPlayer(payload) { + const self = this; + const opts = { detached: false, env: environ, - stdio: ["ignore", "ignore", "pipe"], + stdio: ["ignore", "ignore", "pipe"] }; - var vlcCmd = `vlc`; - var positions = {}; + const vlcCmd = `vlc`; + const positions = {}; let dp2Check = false; payload.forEach((s) => { @@ -185,21 +181,19 @@ module.exports = NodeHelper.create({ (error, stdout, stderr) => { if (error) { console.error(`exec error: ${error}`); - return; } } ); - return; } else { // Otherwise, Generate the VLC window - var args = [ + const args = [ "-I", "dummy", "--video-on-top", "--no-video-deco", "--no-embedded-video", `--video-title=${s.name}`, - this.config[s.name].url, + this.config[s.name].url ]; if ("fullscreen" in s && "hdUrl" in this.config[s.name]) { args.pop(); @@ -243,8 +237,8 @@ module.exports = NodeHelper.create({ if (!dp2Check) { return; } - var dp2Cmd = `devilspie2`; - var dp2Args = ["--debug", "-f", path.resolve(__dirname + "/scripts")]; + const dp2Cmd = `devilspie2`; + const dp2Args = ["--debug", "-f", path.resolve(`${__dirname}/scripts`)]; let dp2Config = ``; if (this.config.rotateStreams) { dp2Config = ` @@ -253,8 +247,8 @@ local function starts_with(str, start) end if (starts_with(get_window_name(), "stream")) then set_window_geometry(${payload[0].box.left}, ${payload[0].box.top}, ${ - payload[0].box.right - payload[0].box.left - }, ${payload[0].box.bottom - payload[0].box.top}); + payload[0].box.right - payload[0].box.left + }, ${payload[0].box.bottom - payload[0].box.top}); undecorate_window(); set_on_top(); end @@ -272,7 +266,7 @@ end }); } - var startDp2 = () => { + const startDp2 = () => { if (this.dp2) { this.dp2.stderr.removeAllListeners(); this.dp2.kill(); @@ -286,7 +280,7 @@ end }; fs.readFile( - path.resolve(__dirname + "/scripts/vlc.lua"), + path.resolve(`${__dirname}/scripts/vlc.lua`), "utf8", (err, data) => { if (err) throw err; @@ -294,7 +288,7 @@ end // Only write the new DevilsPie2 config if we need to. if (data !== dp2Config) { fs.writeFile( - path.resolve(__dirname + "/scripts/vlc.lua"), + path.resolve(`${__dirname}/scripts/vlc.lua`), dp2Config, (err) => { // throws an error, you could also catch it here @@ -321,8 +315,8 @@ end ); }, - stopVlcPlayer: function (name, delay, callback) { - let quitVlc = () => { + stopVlcPlayer(name, delay, callback) { + const quitVlc = () => { console.log(`Stopping stream ${name}`); if (name in this.vlcStream) { try { @@ -347,7 +341,6 @@ end (error, stdout, stderr) => { if (error) { console.error(`exec error: ${error}`); - return; } } ); @@ -361,11 +354,11 @@ end } }, - stopAllVlcPlayers: function (delay, callback) { + stopAllVlcPlayers(delay, callback) { if (Object.keys(this.vlcStream).length > 0) { console.log( delay - ? "Delayed exit of all VLC Streams in " + delay + " sec..." + ? `Delayed exit of all VLC Streams in ${delay} sec...` : "Killing All VLC Streams..." ); Object.keys(this.vlcStream).forEach((s) => { @@ -388,8 +381,8 @@ end } }, - getOmxplayer: function (payload) { - var self = this; + getOmxplayer(payload) { + const self = this; if (this.pm2Connected) { // Busy doing something, wait a half sec. @@ -399,34 +392,32 @@ end return; } - var opts = { detached: false, stdio: "ignore" }; + const opts = { detached: false, stdio: "ignore" }; - var omxCmd = `omxplayer`; + const omxCmd = `omxplayer`; - var namesM = []; + const namesM = []; - var argsM = []; + const argsM = []; payload.forEach((s) => { - var args = [ + const args = [ "--live", "--video_queue", "4", "--fps", "30", "--no-osd", - this.config[s.name].url, + this.config[s.name].url ]; if (!("fullscreen" in s)) { args.unshift( "--win", `${s.box.left},${s.box.top},${s.box.right},${s.box.bottom}` ); - } else { - if ("hdUrl" in this.config[s.name]) { - args.pop(); - args.push(this.config[s.name].hdUrl); - } + } else if ("hdUrl" in this.config[s.name]) { + args.pop(); + args.push(this.config[s.name].hdUrl); } if (this.config[s.name].protocol !== "udp") { args.unshift("--avdict", "rtsp_transport:tcp"); @@ -455,7 +446,7 @@ end ); argsM.push(args); - namesM.push("omx_" + s.name); + namesM.push(`omx_${s.name}`); }); // this.omxStream[payload.name] = child_process.spawn(omxCmd, args, opts); @@ -471,19 +462,18 @@ end // Stops the Daemon if it's already started pm2.list((err, list) => { - var errCB = (err, apps) => { + const errCB = (err, apps) => { if (err) { console.log(err); pm2.disconnect(); this.pm2Connected = false; - return; } }; - var startProcs = () => { + const startProcs = () => { if (namesM.length > 0) { console.log( - "Starting PM2 for " + namesM[namesM.length - 1] + `Starting PM2 for ${namesM[namesM.length - 1]}` ); pm2.start( { @@ -491,22 +481,23 @@ end name: namesM[namesM.length - 1], interpreter: "bash", out_file: "/dev/null", - //interpreterArgs: '-u', - args: argsM[namesM.length - 1], - //max_memory_restart : '100M' // Optional: Restarts your app if it reaches 100Mo + // interpreterArgs: '-u', + args: argsM[namesM.length - 1] + // max_memory_restart : '100M' // Optional: Restarts your app if it reaches 100Mo }, (err, proc) => { console.log( - "PM2 started for " + + `PM2 started for ${ namesM[namesM.length - 1] + }` ); this.omxStream[namesM[namesM.length - 1]] = namesM[namesM.length - 1]; // Automatically Restart OMX PM2 Instance every X Hours - let restartHrs = this.config.omxRestart; + const restartHrs = this.config.omxRestart; if (typeof restartHrs === "number") { - let worker = () => { + const worker = () => { pm2.restart( namesM[namesM.length - 1], function () {} @@ -540,7 +531,7 @@ end } }; - for (var proc in list) { + for (const proc in list) { if ( "name" in list[proc] && namesM.indexOf(list[proc].name) > -1 @@ -562,7 +553,7 @@ end }); }, - stopOmxplayer: function (name, callback) { + stopOmxplayer(name, callback) { if (this.pm2Connected) { // Busy doing something, wait a half sec. console.info("PM2: waiting my turn..."); @@ -581,8 +572,8 @@ end } this.pm2Connected = true; - console.log("Stopping PM2 process: omx_" + name); - pm2.stop("omx_" + name, (err2, apps) => { + console.log(`Stopping PM2 process: omx_${name}`); + pm2.stop(`omx_${name}`, (err2, apps) => { if (!err2) { clearTimeout(this.omxStreamTimeouts[name]); delete this.omxStream[name]; @@ -599,7 +590,7 @@ end }); }, - stopAllOmxplayers: function (callback) { + stopAllOmxplayers(callback) { if (this.pm2Connected) { // Busy doing something, wait a half sec. setTimeout(() => { @@ -625,9 +616,9 @@ end return; } - var toStop = []; + const toStop = []; - var stopProcs = () => { + const stopProcs = () => { if (toStop.length > 0) { pm2.stop(toStop[toStop.length - 1], (e, p) => { if (e) { @@ -647,11 +638,10 @@ end if (typeof callback === "function") { callback(); } - return; } }; - for (var proc in list) { + for (const proc in list) { if ( "name" in list[proc] && list[proc].name.startsWith("omx_") @@ -693,11 +683,11 @@ end * argument notification string - The identifier of the noitication. * argument payload mixed - The payload of the notification. */ - socketNotificationReceived: function (notification, payload) { - var self = this; + socketNotificationReceived(notification, payload) { + const self = this; if (notification === "CONFIG") { this.config = payload; - let streams = Object.keys(this.config).filter((key) => + const streams = Object.keys(this.config).filter((key) => key.startsWith("stream") ); if ( @@ -706,12 +696,10 @@ end this.config.shutdownDelay < (streams.length - 1) * this.config.rotateStreamsTimeout ) { - let suggestedDelay = + const suggestedDelay = (streams.length - 1) * this.config.rotateStreamsTimeout + 2; console.warn( - "WARNING: shutdownDelay is shorter than the time it takes to make it through the loop. Consider increasing to " + - suggestedDelay + - "s." + `WARNING: shutdownDelay is shorter than the time it takes to make it through the loop. Consider increasing to ${suggestedDelay}s.` ); } streams.forEach((name) => { @@ -752,5 +740,5 @@ end this.stopAllVlcPlayers(payload); } } - }, + } }); diff --git a/package.json b/package.json index b0e2076..cbe813e 100644 --- a/package.json +++ b/package.json @@ -1,33 +1,33 @@ { - "name": "mmm-rtspstream", - "version": "2.0.5", - "description": "MagicMirror² module to stream video from an RTSP stream", - "keywords": [ - "magic mirror", - "rtsp", - "camera" - ], - "main": "MMM-RTSPStream.js", - "author": "shbatm", - "license": "MIT", - "scripts": { - "preinstall": "./scripts/preinstall.sh", - "postinstall": "./scripts/postinstall.sh", - "test": "prettier --check .", - "prettier:fix": "prettier --write ." - }, - "devDependencies": { - "prettier": "^3.1.0" - }, - "dependencies": { - "datauri": "^2.0.0", - "node-rtsp-stream-es6": "github:shbatm/node-rtsp-stream-es6", - "pm2": "^5.3.0", - "ps-tree": "^1.2.0", - "ws": "^8.14.2" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/shbatm/MMM-RTSPStream.git" - } + "name": "mmm-rtspstream", + "version": "2.0.5", + "description": "MagicMirror² module to stream video from an RTSP stream", + "keywords": [ + "magic mirror", + "rtsp", + "camera" + ], + "main": "MMM-RTSPStream.js", + "author": "shbatm", + "license": "MIT", + "scripts": { + "preinstall": "./scripts/preinstall.sh", + "postinstall": "./scripts/postinstall.sh", + "test": "prettier --check .", + "prettier:fix": "prettier --write ." + }, + "devDependencies": { + "prettier": "^3.1.0" + }, + "dependencies": { + "datauri": "^2.0.0", + "node-rtsp-stream-es6": "github:shbatm/node-rtsp-stream-es6", + "pm2": "^5.3.0", + "ps-tree": "^1.2.0", + "ws": "^8.14.2" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/shbatm/MMM-RTSPStream.git" + } } diff --git a/public/config.html b/public/config.html index ec0fd28..0254c8f 100644 --- a/public/config.html +++ b/public/config.html @@ -1,1024 +1,1998 @@ - + + + + MMM-RTSPStream Configuration Builder + + + + - - - MMM-RTSPStream Configuration Builder - - - - - - -
-
- Step 1. MMM-RTSPStream Core Configuration Builder -
- -
- -
-
-
- -
- - Leave blank for no header. -
-
-
- -
- - -

Yes will start the streams automatically when the mirror starts, no will show - snapshots (if available) or a blank window until the stream is started by a notification from - another app or by clicking the play button.

-
-
-
- -
-
-
-
- -
- -
-
-
- -
- -
-
-
- -
- - + +
+ +
+ + Enter the name for the camera +
-
-
- -
-
- - seconds -
-
-
-
- -
-
- - px -
-
-
-
- -
-
- - px -
-
-
-
- -
- -
-
-
- -
- - - Required only for OMXPlayer or VLC. Use this to mute the stream. -
-
-
- -
- -
-
- -
-
- Step 3. Copy to MagicMirror Configuration File -
- -
- -
-
-
- -
- -

Copy the above section to your configuration file.

-
-
-
-
- - - - \ No newline at end of file + + + diff --git a/scripts/installer.sh b/scripts/installer.sh index 9767407..ec41ff6 100755 --- a/scripts/installer.sh +++ b/scripts/installer.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# This is an experimental installer script for MagicMirror2 Modules +# This is an experimental installer script for MagicMirror² Modules # assume default install location MM_HOME=$HOME/MagicMirror @@ -9,7 +9,7 @@ FORK=shbatm echo "" -echo "Installation for the MagicMirror2 $MODULE_NAME module started!" +echo "Installation for the MagicMirror² $MODULE_NAME module started!" echo "" echo "Notice: This script and the installed software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software." echo "" @@ -50,7 +50,7 @@ else # found it again echo -n "" else - echo "Could not find MagicMirror2 installation directory." + echo "Could not find MagicMirror² installation directory." echo "Please start this script again from the MagicMirror directory." exit 1 fi @@ -102,7 +102,7 @@ if [ -d "$MM_HOME/modules/$MODULE_NAME" ] ; then echo "Already installed, not upgrading." fi else - echo "MagicMirror2 detected in: $MM_HOME" + echo "MagicMirror² detected in: $MM_HOME" echo "" if check_yes "Is this correct and do you want to start installation?"; then echo "" @@ -173,8 +173,8 @@ fi echo "Have fun with the module, if you have any problems, please search for help on github or in the forum:" echo "" echo " Github : https://github.com/$FORK/$MODULE_NAME" -echo " Forum : http://forum.magicmirror.builders" +echo " Forum : https://forum.magicmirror.builders/" echo "" -echo "Do not forget to restart your MagicMirror2 to activate the module! Installation finished." +echo "Do not forget to restart your MagicMirror² to activate the module! Installation finished." echo "" exit 0 diff --git a/scripts/onexit.js b/scripts/onexit.js index feedb7a..e45908f 100755 --- a/scripts/onexit.js +++ b/scripts/onexit.js @@ -2,12 +2,9 @@ /* jshint esversion: 6 */ -const fs = require('fs'); -const path = require("path"); -const child_process = require('child_process'); const pm2 = require('pm2'); -stopAllOmxplayers = function() { +const stopAllOmxplayers = function() { console.log('PM2: Stopping all OMXPlayer Streams...'); pm2.connect((err) => { if (err) { @@ -23,9 +20,9 @@ stopAllOmxplayers = function() { return; } - var toStop = []; + const toStop = []; - var stopProcs = () => { + const stopProcs = () => { if (toStop.length > 0) { pm2.stop(toStop[toStop.length - 1], (e, p) => { if (e) { console.log(e); throw e; } @@ -35,11 +32,11 @@ stopAllOmxplayers = function() { } else { pm2.disconnect(); process.exit(0); - return; + } }; - let omxProcs = list.filter(o => o.name.startsWith("omx_")); + const omxProcs = list.filter(o => o.name.startsWith("omx_")); if (omxProcs) { omxProcs.forEach(o => { console.log(`PM2: Checking if ${o.name} is running...`); From 7c0536267eac899f1846c05b0d06c398e62ade7e Mon Sep 17 00:00:00 2001 From: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Date: Wed, 31 Jan 2024 20:52:09 +0100 Subject: [PATCH 09/11] Remove reference to MMM-RTSPtoWeb --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 0fa6a5f..01f35db 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@ This module will show a live RTSP video stream and/or periodic snapshots on the > Why? > > - I am no longer using this module on my own mirror. After several years, I found that I use the snapshots much more frequently than I streamed the actual cameras, which can be performed by much simpler modules and methods. To enable streaming, WebRTC (like [MMM-HomeAssistant-WebRTC](https://github.com/Anonym-tsk/MMM-HomeAssistant-WebRTC)) is a newer and better standard with much lower server overhead and latency for delivering RTSP Streams to the frontend than any of the options used here, in the future, this will be the method I focus on and I will not try to shoehorn another technology into this module. -> - Update 5-Oct-2022: See alternative module [MMM-RTSPtoWeb](https://github.com/shbatm/MMM-RTSPtoWeb) for a drastically simplified module relying on WebRTC and a backend server. ## Features From 519967dac0226e03d296ca85664acf454585fd09 Mon Sep 17 00:00:00 2001 From: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Date: Wed, 31 Jan 2024 20:54:59 +0100 Subject: [PATCH 10/11] Update URL --- README.md | 4 ++-- scripts/installer.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 01f35db..6e22165 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MMM-RTSPStream - Video Streaming from Live Feeds (Security Cameras) -This is a module for the [MagicMirror²](https://github.com/MichMich/MagicMirror/). +This is a module for the [MagicMirror²](https://github.com/MagicMirrorOrg/MagicMirror/). This module will show a live RTSP video stream and/or periodic snapshots on the MagicMirror² from any IP Security Camera which supports the [RTSP protocol](https://github.com/shbatm/MMM-RTSPStream/wiki/Stream-URLs-for-Various-Cameras) and/or can serve a snapshot periodically. @@ -43,7 +43,7 @@ This module will show a live RTSP video stream and/or periodic snapshots on the ### Quick install -If you followed the default installation instructions for the [MagicMirror²](https://github.com/MichMich/MagicMirror) project, you should be able to use the automatic installer. +If you followed the default installation instructions for the [MagicMirror²](https://github.com/MagicMirrorOrg/MagicMirror) project, you should be able to use the automatic installer. The following command will download the installer and execute it: ```bash diff --git a/scripts/installer.sh b/scripts/installer.sh index ec41ff6..2b40a1e 100755 --- a/scripts/installer.sh +++ b/scripts/installer.sh @@ -38,8 +38,8 @@ else exit 0 fi -# check if we are correct by searching for https://github.com/MichMich/MagicMirror in package.json -TEST_STRING="\"url\": \"git+https://github.com/MichMich/MagicMirror.git\"" +# check if we are correct by searching for https://github.com/MagicMirrorOrg/MagicMirror in package.json +TEST_STRING="\"url\": \"git+https://github.com/MagicMirrorOrg/MagicMirror.git\"" if grep -sq "$TEST_STRING" "$MM_HOME/package.json"; then # we found it echo -n "" From 52bc975ca77fcf2c1750c16cd291cb2e2b67285a Mon Sep 17 00:00:00 2001 From: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Date: Wed, 31 Jan 2024 20:55:52 +0100 Subject: [PATCH 11/11] Update prettier --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cbe813e..00662ce 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "prettier:fix": "prettier --write ." }, "devDependencies": { - "prettier": "^3.1.0" + "prettier": "^3.2.4" }, "dependencies": { "datauri": "^2.0.0",