jeroenterheerdt / ring-hassio

A Home Assistant add-on for live streaming from Ring devices.
MIT License
131 stars 93 forks source link

Improvement to add-on with on demand configuration #51

Open gilliginsisland opened 2 years ago

gilliginsisland commented 2 years ago

I saw your add-on and thought it can be improved using a setup that I have on my home assistant server.

It uses https://github.com/aler9/rtsp-simple-server which is an awesome golang based rtsp server / client in combination with https://github.com/dgreif/ring.

It supports on demand automatically and only uses one ring stream no matter how many clients connect, and by not using HLS and using only rtsp it avoids the 10 second minimum lag that HLS has.

My nodejs script that calls dgreif/ring forwards all its arguments to ffmpeg and looks like this

import { dirname } from 'path';
import { fileURLToPath } from 'url';
import { readFile, writeFile } from 'fs/promises';
import ringClientApi from 'ring-client-api';

const CONFIG_PATH = process.env.CONFIG_PATH || '/etc/ring-ffmpeg.json';

async function updateSavedToken({ newRefreshToken, oldRefreshToken }) {
    console.log('Refresh Token Updated: ', newRefreshToken);
    if (!oldRefreshToken) return;
    const currentConfig = await readFile(CONFIG_PATH, 'utf8');
    const updatedConfig = currentConfig.replace(oldRefreshToken, newRefreshToken);
    await writeFile(CONFIG_PATH, updatedConfig);
}

const ffmpegArgs = { 
    audio: [], video: [],
    output: process.argv.slice(2),
};

async function main() {
    const { refreshToken, ffmpegPath } = JSON.parse(await readFile(CONFIG_PATH, 'utf8'));
    const ringApi = new ringClientApi.RingApi({ ffmpegPath, refreshToken });
    ringApi.onRefreshTokenUpdated.subscribe(updateSavedToken);
    const [camera] = await ringApi.getCameras();
    const session = await camera.createSipSession({ skipFfmpegCheck: true });
    await session.start(ffmpegArgs);
}

// handle exit signals
for (const signal of ['SIGINT','SIGTERM','SIGQUIT']) {
    process.on(signal, () => {
        console.log(`Received ${signal}, exiting...`);
        process.exit();
    });
}

main();

The script can be easily adapted to take in refreshToken from the environment instead of a file.

Then in rtsp-simple-server I take advantage of an option where it can launch on demand only when someone actually tries to request an rtsp stream and it will stop streaming as soon as all clients disconnect, it will also duplicate the stream if multiple connections are made, and so only one connection will be made to ring even if many connections are made to rtsp-simple-server. My config looks like this:

rtspDisable: no
protocols: [tcp]
rtspAddress: 127.0.0.1:8554

rtmpDisable: yes

hlsDisable: no
hlsAddress: 127.0.0.1:8888
hlsSegmentCount: 4
hlsSegmentDuration: 2s

paths:
  all:
    fallback:
    disablePublisherOverride: no

  ring:
    source: record
    runOnDemand: /usr/local/lib/ring-ffmpeg/index.mjs -acodec aac -vcodec copy -f rtsp rtsp://localhost:$RTSP_PORT/$RTSP_PATH
    runOnDemandRestart: yes
    runOnDemandStartTimeout: 20s
    runOnDemandCloseAfter: 5s

The key part is that when using runOnDemand rtsp-simple-server will do the work of running the binary and closing it when home assistant disconnects, and it will dynamically open a port and tell the node script what port to send out to.

Combining these two items we can have a true on demand rtsp stream very easily.

caraar12345 commented 2 years ago

Hey - this is really cool!

How does it work with more than one Ring camera? 👀

gilliginsisland commented 2 years ago

The sample code I wrote doesn’t do more than one camera in its current form but it should be trivial to add that ability. rtsp-simple-server allows multiple paths so u can add the camera as the first parameter and change the node script to take the camera number as the first argument and then use that to pull the corresponding camera.

patsch9 commented 2 years ago

Hey, very cool!

can you explain how you configured your environment to use this with homeassistant?

Thanks!

patsch9 commented 2 years ago

Hi, in this fork of ring-hassio i have implemented your script with the newest version of rtsp-simple-server.

gilliginsisland commented 2 years ago

@patsch9 Thats awesome. I use home assistant core so I couldn't make changes to the add-on and test it. Happy you implemented it!

I saw the update to rtsp-simple-server and I noticed that the yaml file for the configuration supports regex paths, so it should be possible to support multiple cameras with one path definition, something like ~ring([0-9]+) and then use that path to know which camera to stream.

Also for home assistant configuration I use the ffmpeg platform which doesn't require a still_image_url since the live stream doesn't have snapshot feature

patsch9 commented 2 years ago

@gilliginsisland do you have also the problem that when you access the livestream there are no ding or motion events on other devices also home assistant? When i cancel the stream than the events are shown.

gilliginsisland commented 2 years ago

I dont actually know that answer, I only use it for the stick up cam and I dont use the regular integration. I only use it as an on demand camera. But from what I googled it seems that when a live stream is active that there is no motion or ding events, and that there doesn't seem to be a way to fix that.

patsch9 commented 2 years ago

ok, that's exactly my level of knowledge. It's a shame for Amazon that the connection to external services is so poorly represented. Thanks for your input and feel free to edit some things in my forked repo :-)

gilliginsisland commented 2 years ago

Yeah I agree.

A $20 camera on amazon has PTZ and a local RTSP stream that doesn't require workarounds to get into home assistant. But a $150 camera can come without PTZ and without RTSP and u need to pay a subscription to get recording which only last 30 seconds and isnt always on.

I only have a ring because I got the stick up cam for free when I signed up for Verizon Fios lol

tsightler commented 2 years ago

@gilliginsisland Thank you for sharing this. I'm the maintainer of ring-mqtt as well as the ring-mqtt addon. which is quite popular with Home Assistant users. I've had numerous request to add livestreaming to the addon but, as the current integration is based on MQTT, it's not really built for livestreaming, although I do support other camera functions and also have support for snapshots based on interval and motion.

I had looked at this script previously, but I wasn't too excited with the HLS implementation and didn't really have time to dig into RTSP support. However, due to continued requests, I've been seriously considering integrating streaming into the addon to provide a single addon with all Ring functionality and had been researching rtsp simple server, so seeing a working example is great.

One question, how reliable have you found this setup? My experience with livestreams using ring-client-api has been a little spotty, and my big concern is the support issues it would open up due to this so I'm curious what your experience has been.

tsightler commented 2 years ago

Just FYI for folks here, not trying to take users away from this project, but I've implemented streaming support in ring-mqtt using a concept similar to the one posted in this thread (rtsp-simple-server, but using ring-mqtt to trigger stream start/stops). It supports mulitple cameras, external viewers on-demand, ability to manually start a stream/record session, etc. There's a few more features to come, like playback of past recorded videos, but so far it seems pretty usable.