allow new file while already streaming pnm
This commit is contained in:
@@ -123,7 +123,18 @@ class WTVPNM {
|
|||||||
this.debugLog('rx', session.id, 'len', data.length, ascii.slice(0, 120));
|
this.debugLog('rx', session.id, 'len', data.length, ascii.slice(0, 120));
|
||||||
this.debugLog('rx hex', session.id, data.toString('hex'));
|
this.debugLog('rx hex', session.id, data.toString('hex'));
|
||||||
|
|
||||||
if (data.includes(Buffer.from('PNA\x00\x0a', 'latin1'))) {
|
const hasPnaHello = data.includes(Buffer.from('PNA\x00\x0a', 'latin1'));
|
||||||
|
const hasGetA = ascii.includes('GET /a');
|
||||||
|
|
||||||
|
// Some clients retune on the same TCP socket without first sending a
|
||||||
|
// full stop/teardown. When that happens, treat a new hello marker as
|
||||||
|
// a fresh session start and clear prior stream/control state.
|
||||||
|
if ((hasPnaHello || hasGetA) && (session.helloSent || session.descriptorSent)) {
|
||||||
|
this.debugLog('retune detected, resetting session state', session.id);
|
||||||
|
this.resetSessionForRetune(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasPnaHello) {
|
||||||
session.pnaFields = this.parsePnaMessage(data);
|
session.pnaFields = this.parsePnaMessage(data);
|
||||||
|
|
||||||
// Dump all parsed PNA fields for debugging
|
// Dump all parsed PNA fields for debugging
|
||||||
@@ -185,7 +196,7 @@ class WTVPNM {
|
|||||||
|
|
||||||
if (session.notFoundSent) return;
|
if (session.notFoundSent) return;
|
||||||
|
|
||||||
if (!session.helloSent && (ascii.includes('GET /a') || data.includes(Buffer.from('PNA\x00\x0a', 'latin1')))) {
|
if (!session.helloSent && (hasGetA || hasPnaHello)) {
|
||||||
this.sendHelloSequence(socket, session);
|
this.sendHelloSequence(socket, session);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -222,6 +233,27 @@ class WTVPNM {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetSessionForRetune(session) {
|
||||||
|
if (!session) return;
|
||||||
|
this.clearDescriptorTimer(session);
|
||||||
|
this.stopUdpStream(session);
|
||||||
|
|
||||||
|
session.helloSent = false;
|
||||||
|
session.descriptorSent = false;
|
||||||
|
session.notFoundSent = false;
|
||||||
|
session.capabilitiesLogged = false;
|
||||||
|
session.capabilities = [];
|
||||||
|
session.clientChallenge = null;
|
||||||
|
session.requestedMedia = null;
|
||||||
|
session.mediaPath = null;
|
||||||
|
session.pnaFields = null;
|
||||||
|
session.ctrlBuf = Buffer.alloc(0);
|
||||||
|
session.paused = false;
|
||||||
|
session.eosSent = false;
|
||||||
|
session.hashVerified = false;
|
||||||
|
session.sessionNumber = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
// Parse the post-descriptor TCP control stream sent by RealPlayer during
|
// Parse the post-descriptor TCP control stream sent by RealPlayer during
|
||||||
// and after playback. Observed opcodes (multi_seek.pcap, wtv2.pcap):
|
// and after playback. Observed opcodes (multi_seek.pcap, wtv2.pcap):
|
||||||
// 0x21 ('!') — 1 byte — periodic keepalive during playback
|
// 0x21 ('!') — 1 byte — periodic keepalive during playback
|
||||||
@@ -237,6 +269,7 @@ class WTVPNM {
|
|||||||
? Buffer.concat([session.ctrlBuf, data])
|
? Buffer.concat([session.ctrlBuf, data])
|
||||||
: Buffer.from(data);
|
: Buffer.from(data);
|
||||||
|
|
||||||
|
const knownOps = new Set([0x21, 0x42, 0x50, 0x53, 0x67]);
|
||||||
let off = 0;
|
let off = 0;
|
||||||
const buf = session.ctrlBuf;
|
const buf = session.ctrlBuf;
|
||||||
while (off < buf.length) {
|
while (off < buf.length) {
|
||||||
@@ -267,10 +300,29 @@ class WTVPNM {
|
|||||||
this.debugLog('ctrl stats', session.id, `len=${slen}`, txt.slice(0, 120));
|
this.debugLog('ctrl stats', session.id, `len=${slen}`, txt.slice(0, 120));
|
||||||
off += 3 + slen;
|
off += 3 + slen;
|
||||||
} else {
|
} else {
|
||||||
// Unknown byte — log once and skip to resync.
|
// Some clients send opaque binary blobs during retune/teardown.
|
||||||
this.debugLog('ctrl unknown', session.id, `op=0x${op.toString(16)}`,
|
// Skip to the next known opcode in one step to avoid byte-by-byte
|
||||||
'hex', buf.slice(off, off + 16).toString('hex'));
|
// desync spam and excessive parser churn.
|
||||||
off += 1;
|
let nextKnown = -1;
|
||||||
|
for (let i = off + 1; i < buf.length; i++) {
|
||||||
|
if (knownOps.has(buf[i])) {
|
||||||
|
nextKnown = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextKnown === -1) {
|
||||||
|
this.debugLog('ctrl opaque blob', session.id,
|
||||||
|
`len=${buf.length - off}`,
|
||||||
|
'hex', buf.slice(off, off + 24).toString('hex'));
|
||||||
|
off = buf.length;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.debugLog('ctrl unknown block skipped', session.id,
|
||||||
|
`len=${nextKnown - off}`,
|
||||||
|
'hex', buf.slice(off, Math.min(nextKnown, off + 24)).toString('hex'));
|
||||||
|
off = nextKnown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Preserve any trailing incomplete command for next receive.
|
// Preserve any trailing incomplete command for next receive.
|
||||||
|
|||||||
Reference in New Issue
Block a user