diff --git a/plugins/prebuffer-mixin/package-lock.json b/plugins/prebuffer-mixin/package-lock.json index 612da602a..2f9fcaf0f 100644 --- a/plugins/prebuffer-mixin/package-lock.json +++ b/plugins/prebuffer-mixin/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scrypted/prebuffer-mixin", - "version": "0.1.188", + "version": "0.1.189", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@scrypted/prebuffer-mixin", - "version": "0.1.188", + "version": "0.1.189", "license": "Apache-2.0", "dependencies": { "@scrypted/common": "file:../../common", diff --git a/plugins/prebuffer-mixin/package.json b/plugins/prebuffer-mixin/package.json index 0f48e50b3..5462bb0b2 100644 --- a/plugins/prebuffer-mixin/package.json +++ b/plugins/prebuffer-mixin/package.json @@ -1,6 +1,6 @@ { "name": "@scrypted/prebuffer-mixin", - "version": "0.1.188", + "version": "0.1.189", "description": "Rebroadcast and Prebuffer for VideoCameras.", "author": "Scrypted", "license": "Apache-2.0", diff --git a/plugins/prebuffer-mixin/src/main.ts b/plugins/prebuffer-mixin/src/main.ts index ffbd1837c..53ce705c4 100644 --- a/plugins/prebuffer-mixin/src/main.ts +++ b/plugins/prebuffer-mixin/src/main.ts @@ -31,6 +31,9 @@ const TRANSCODE_AUDIO_DESCRIPTION = `${TRANSCODE_AUDIO} (Transcode)`; const COMPATIBLE_AUDIO_CODECS = ['aac', 'mp3', 'mp2', 'opus']; const DEFAULT_FFMPEG_INPUT_ARGUMENTS = '-fflags +genpts'; +const SCRYPTED_PARSER = 'Scrypted'; +const FFMPEG_PARSER = 'FFmpeg'; + const VALID_AUDIO_CONFIGS = [ AAC_AUDIO, COMPATIBLE_AUDIO, @@ -77,6 +80,7 @@ class PrebufferSession { ffmpegInputArgumentsKey: string; lastDetectedAudioCodecKey: string; rebroadcastModeKey: string; + rtspParserKey: string; constructor(public mixin: PrebufferMixin, public streamName: string, public streamId: string, public stopInactive: boolean) { this.storage = mixin.storage; @@ -86,6 +90,7 @@ class PrebufferSession { this.ffmpegInputArgumentsKey = 'ffmpegInputArguments-' + this.streamId; this.rebroadcastModeKey = 'rebroadcastMode-' + this.streamId; this.lastDetectedAudioCodecKey = 'lastDetectedAudioCodec-' + this.streamId; + this.rtspParserKey = 'rtspParser-' + this.streamId; } clearPrebuffers() { @@ -130,7 +135,7 @@ class PrebufferSession { let total = 0; let start = 0; - const { mp4Mode } = this.getRebroadcastMode(); + const { mp4Mode, rtspMode } = this.getRebroadcastMode(); for (const prebuffer of (mp4Mode ? this.prebuffers.mp4 : this.prebuffers.rtsp)) { start = start || prebuffer.time; for (const chunk of prebuffer.chunk.chunks) { @@ -172,9 +177,9 @@ class PrebufferSession { combobox: true, }, { - title: 'Rebroadcast Mode', + title: 'Rebroadcast Container', group, - description: 'THIS FEATURE IS IN TESTING. DO NOT CHANGE THIS FROM MPEG-TS. The stream format to use when rebroadcasting.', + description: 'Experimental: The container format to use when rebroadcasting. MPEG-TS is stable. RTSP may have lower latency and better responsiveness.', placeholder: 'MPEG-TS', choices: [ 'MPEG-TS', @@ -187,6 +192,24 @@ class PrebufferSession { ); if (session) { + // The RTSP demuxer can only be used if also muxing RTSP. + if (rtspMode && session.mediaStreamOptions?.container === 'rtsp') { + const value = this.getParser(rtspMode, session.mediaStreamOptions); + settings.push( + { + key: this.rtspParserKey, + group, + title: 'RTSP Parser', + description: 'Experimental: The RTSP Parser used to read the stream. FFmpeg is stable. The Scrypted parser may have lower latency and better responsiveness.', + value, + choices: [ + FFMPEG_PARSER, + SCRYPTED_PARSER, + ], + } + ); + } + settings.push( { key: 'detectedResolution', @@ -230,9 +253,18 @@ class PrebufferSession { return settings; } + getParser(rtspMode: boolean, mediaStreamOptions: MediaStreamOptions) { + const defaultValue = rtspMode && mediaStreamOptions?.tool === 'scrypted' ? + SCRYPTED_PARSER : FFMPEG_PARSER; + const rtspParser = this.storage.getItem(this.rtspParserKey); + const value = !rtspParser ? defaultValue : rtspParser === SCRYPTED_PARSER ? SCRYPTED_PARSER : FFMPEG_PARSER; + return value; + } + getRebroadcastMode() { const mode = this.storage.getItem(this.rebroadcastModeKey); const rtspMode = mode?.startsWith('RTSP'); + return { rtspMode: mode?.startsWith('RTSP'), mp4Mode: !rtspMode || mode?.includes('MP4'), @@ -444,10 +476,9 @@ class PrebufferSession { // before launching the parser session, clear out the last detected codec. // an erroneous cached codec could cause ffmpeg to fail to start. this.storage.removeItem(this.lastDetectedAudioCodecKey); - const canUseScryptedParser = rtspMode;// && !mp4Mode; let usingScryptedParser = false; - if (canUseScryptedParser && isRfc4571) { + if (rtspMode && isRfc4571) { usingScryptedParser = true; this.console.log('bypassing ffmpeg: using scrypted rfc4571 parser') const json = await mediaManager.convertMediaObjectToJSON(mo, 'x-scrypted/x-rfc4571'); @@ -461,9 +492,8 @@ class PrebufferSession { const ffmpegInput = JSON.parse(moBuffer.toString()) as FFMpegInput; sessionMso = ffmpegInput.mediaStreamOptions; - if (canUseScryptedParser - && ffmpegInput.mediaStreamOptions?.container === 'rtsp' - && ffmpegInput.mediaStreamOptions?.tool === 'scrypted') { + const parser = this.getParser(rtspMode, ffmpegInput.mediaStreamOptions); + if (parser === SCRYPTED_PARSER) { usingScryptedParser = true; this.console.log('bypassing ffmpeg: using scrypted rtsp/rfc4571 parser') const rtspClient = new RtspClient(ffmpegInput.url); @@ -550,6 +580,9 @@ class PrebufferSession { this.parserSession = session; + // settings ui refresh + deviceManager.onMixinEvent(this.mixin.id, this.mixin.mixinProviderNativeId, ScryptedInterface.Settings, undefined); + // cloud streams need a periodic token refresh. if (sessionMso?.refreshAt) { let mso = sessionMso;