From 7ad51e51db1e6abc28d47147cfd486d7fafc35e6 Mon Sep 17 00:00:00 2001 From: Roger Hardiman Date: Thu, 15 Jan 2026 00:52:47 +0000 Subject: [PATCH 1/3] Add -t udp and -t tcp for UDP and TCP RTP transport to demo example --- examples/demo.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/demo.js b/examples/demo.js index 6b878bb..74a9de8 100644 --- a/examples/demo.js +++ b/examples/demo.js @@ -23,6 +23,7 @@ program.description("Yellowstone RTSP Client Test Software"); program.option('-u, --username ', 'Optional RTSP Username'); program.option('-p, --password ', 'Optional RTSP Password'); program.option('-o, --outfile ', 'Optional Output File with no File Extension for captured H264/H265/AV1/AAC'); +program.option('-t, --transport ', 'Optional RTP Transport - UDP or TCP'); program.argument(''); @@ -36,7 +37,10 @@ let password = ""; if ('username' in options) username = options.username; if ('password' in options) password = options.password; -const filename = "outfile" +let transport = "tcp"; +if ('transport' in options) transport = options.transport.toString().toLowerCase(); + + const filename = "outfile" console.log("Connecting to " + url); @@ -48,7 +52,7 @@ const client = new RTSPClient(username, password); // "keepAlive" option is set to true by default // "connection" option is set to "udp" by default and defines the method the RTP media packets are set to Yellowstone. Options are "udp" or "tcp" (where RTP media packets are sent down the RTSP connection) // "secure" option is set to true when connecting with TLS to the RTSP Server (eg for RTSPS) -client.connect(url, { connection: "tcp", secure: false }) +client.connect(url, { connection: transport, secure: false }) .then(async (detailsArray) => { console.log("Connected"); From 700e8ba9a89b64b8a836ffa1a6392273735f80db Mon Sep 17 00:00:00 2001 From: Roger Hardiman Date: Thu, 15 Jan 2026 00:55:09 +0000 Subject: [PATCH 2/3] Handle SETUP reply with RTP/AVP/UDP (sent by my Panasonic camera) --- lib/RTSPClient.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/RTSPClient.ts b/lib/RTSPClient.ts index 6ad039d..1603d37 100644 --- a/lib/RTSPClient.ts +++ b/lib/RTSPClient.ts @@ -496,11 +496,15 @@ export default class RTSPClient extends EventEmitter { const transport = parseTransport(headers.Transport); if ( + // TCP transport.protocol !== "RTP/AVP/TCP" && - transport.protocol !== "RTP/AVP" + // UDP + transport.protocol !== "RTP/AVP" && + // UDP + transport.protocol !== "RTP/AVP/UDP" // Panasonic cameras send this ) { throw new Error( - "Only RTSP servers supporting RTP/AVP(unicast) or RTP/AVP/TCP are supported at this time." + "Only RTSP servers supporting RTP/AVP or RTP/AVP/UDP or RTP/AVP/TCP are supported at this time." ); } From 55a68375521c2a541e528ac7281be38749393db0 Mon Sep 17 00:00:00 2001 From: Roger Hardiman Date: Thu, 15 Jan 2026 00:55:56 +0000 Subject: [PATCH 3/3] If ther are multiple WWW-Authenticate lines, pick Digest over Basic. (Seen on my really old HikVision camera) --- lib/RTSPClient.ts | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/RTSPClient.ts b/lib/RTSPClient.ts index 1603d37..4c02874 100644 --- a/lib/RTSPClient.ts +++ b/lib/RTSPClient.ts @@ -908,10 +908,30 @@ export default class RTSPClient extends EventEmitter { const key = line.substring(0, indexOf).trim(); const data = line.substring(indexOf + 1).trim(); - this.rtspHeaders[key] = - key != "Session" && data.match(/^[0-9]+$/) + if (key == "Session") this.rtspHeaders[key] = data; + + else if (key == "WWW-Authenticate") { + // Handle multiple WWW-Authenticate entries and pick the 'best' + // We prefer 'Digest' over 'Basic' + // We preger 'Digest SHAxxx' over 'Digest MD5' or 'Digest with no algorithm (defaults to MD5) (STILL TODO) + if (key in this.rtspHeaders) { + console.log("Duplicate WWW-Authenticate keys") + if (data.startsWith("Digest") && this.rtspHeaders[key]?.startsWith("Basic")) { + this.rtspHeaders[key] = data; // Replace Basic with Digest + } + console.log("Keeping WWW-Authenticate: " + this.rtspHeaders[key]); + } else { + this.rtspHeaders[key] = data; + } + } + + else { + // Store the result as either a String type or a Number type + this.rtspHeaders[key] = + data.match(/^[0-9]+$/) ? parseInt(data, 10) : data; + } // workaround for buggy Hipcam RealServer/V1.0 camera which returns Content-length and not Content-Length if (key.toLowerCase() == "content-length") {