From 84c5a62e6cb26c0282423b0185be7375d33643d7 Mon Sep 17 00:00:00 2001 From: Ahmed Date: Fri, 23 Jan 2026 15:16:25 +0300 Subject: [PATCH 1/2] added live indicator --- custom_components/webrtc/www/webrtc-camera.js | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/custom_components/webrtc/www/webrtc-camera.js b/custom_components/webrtc/www/webrtc-camera.js index f65b239d..6a328f50 100644 --- a/custom_components/webrtc/www/webrtc-camera.js +++ b/custom_components/webrtc/www/webrtc-camera.js @@ -7,6 +7,25 @@ class WebRTCCamera extends VideoRTC { * Step 1. Called by the Hass, when config changed. * @param {Object} config */ + + constructor() { + super(); + this._lastFrameTime = 0; // Timestamp of the last received video frame + this._liveCheckInterval = null; // Interval for checking video playback + } + + /** + * Called to update the timestamp of the last received video frame. + */ + updateHeartbeat() { + this._lastFrameTime = Date.now(); + + // if the video is still there, ask for the next frame notification + if (this.video && this.video.requestVideoFrameCallback) { + this.video.requestVideoFrameCallback(() => this.updateHeartbeat()); + } + } + setConfig(config) { if (!config.url && !config.entity && !config.streams) throw new Error('Missing `url` or `entity` or `streams`'); @@ -112,6 +131,20 @@ class WebRTCCamera extends VideoRTC { nextStream(reload) { this.streamID = (this.streamID + 1) % this.config.streams.length; + // when stream has changed (substream), reset timer and stop heartbeat + if (this.streamID > 0) { // ensures video element exists + // Reset the time for the watchdog + this._lastFrameTime = 0; + + // Also instantly reset the live dot to red + if (this.shadowRoot) { + const dot = this.shadowRoot.querySelector('.live-dot'); + if (dot) { + dot.classList.remove('live'); + } + } + } + const stream = this.config.streams[this.streamID]; this.config.url = stream.url; this.config.entity = stream.entity; @@ -178,6 +211,38 @@ class WebRTCCamera extends VideoRTC { }).catch(er => { this.setStatus('error', er); }); + + // when stream is connected, hook the video frame callback to track live indicator + if (this.video && this.video.requestVideoFrameCallback) { + this.video.requestVideoFrameCallback(() => this.updateHeartbeat()); + } + + // start watchdog timer to update live indicator + if (!this._liveCheckInterval) { + this._liveCheckInterval = setInterval(() => { + const dot = this.shadowRoot.querySelector('.live-dot'); + if (!dot) return; + + const now = Date.now(); + // Check if the last frame arrived less than 500ms ago + const isPlaying = (now - this._lastFrameTime) < 500; + + if (isPlaying) { + dot.classList.add('live'); + } else { + dot.classList.remove('live'); + } + }, 500); + } + } + + ondisconnect() { + // when stream is disconnected, cleanup the live indicator watchdog timer + if (this._liveCheckInterval) { + clearInterval(this._liveCheckInterval); + this._liveCheckInterval = null; + } + super.ondisconnect(); } onopen() { @@ -249,6 +314,22 @@ class WebRTCCamera extends VideoRTC { opacity: 0.6; pointer-events: auto; } + .live-dot-wrapper { + display: flex; + flex-grow: 1; + justify-content: end; + } + .live-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background-color: #D2122E; + align-self: center; + transition: background-color 0.3s, box-shadow 0.3s; + } + .live-dot.live { + background-color: #90EE90; + }
@@ -256,6 +337,9 @@ class WebRTCCamera extends VideoRTC {
+
+
+
From e7f23e782ee928bd0311ab711e87fcbb478e253e Mon Sep 17 00:00:00 2001 From: Ahmed Date: Fri, 23 Jan 2026 15:52:30 +0300 Subject: [PATCH 2/2] added live_indicator option in config --- custom_components/webrtc/www/webrtc-camera.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/custom_components/webrtc/www/webrtc-camera.js b/custom_components/webrtc/www/webrtc-camera.js index 6a328f50..d2628ba2 100644 --- a/custom_components/webrtc/www/webrtc-camera.js +++ b/custom_components/webrtc/www/webrtc-camera.js @@ -57,6 +57,7 @@ class WebRTCCamera extends VideoRTC { * ui: boolean, * style: string, * background: boolean, + * live_indicator: boolean, * * server: string, * @@ -132,7 +133,7 @@ class WebRTCCamera extends VideoRTC { this.streamID = (this.streamID + 1) % this.config.streams.length; // when stream has changed (substream), reset timer and stop heartbeat - if (this.streamID > 0) { // ensures video element exists + if (this.streamID > 0 && this.config.live_indicator == true) { // ensures video element exists // Reset the time for the watchdog this._lastFrameTime = 0; @@ -213,12 +214,12 @@ class WebRTCCamera extends VideoRTC { }); // when stream is connected, hook the video frame callback to track live indicator - if (this.video && this.video.requestVideoFrameCallback) { + if (this.video && this.video.requestVideoFrameCallback && this.config.live_indicator == true) { this.video.requestVideoFrameCallback(() => this.updateHeartbeat()); } // start watchdog timer to update live indicator - if (!this._liveCheckInterval) { + if (!this._liveCheckInterval && this.config.live_indicator == true) { this._liveCheckInterval = setInterval(() => { const dot = this.shadowRoot.querySelector('.live-dot'); if (!dot) return; @@ -337,9 +338,12 @@ class WebRTCCamera extends VideoRTC {
-
-
-
+ ${ this.config.live_indicator == true ? ` +
+
+
+ ` : '' + }