diff --git a/src/Domain/Stream/StreamProcess.php b/src/Domain/Stream/StreamProcess.php index 4b7dceb1..65c7aa85 100644 --- a/src/Domain/Stream/StreamProcess.php +++ b/src/Domain/Stream/StreamProcess.php @@ -443,6 +443,31 @@ class StreamProcess { return array_values($rSources); } + /** + * Append an extra HTTP header line to a stream's ffmpeg argument list. + * + * If the list already carries a 'headers' entry, the line is appended to it + * (CRLF-separated); otherwise a new 'headers' fetch argument is added. Mirrors + * the X-XC_VM-* header injection repeated in startStream. + * + * @param array $rArguments Argument list (each entry an assoc array). + * @param string $rHeaderLine e.g. 'X-XC_VM-Detect:1'. + * @return array The argument list with the header applied. + */ + private static function appendHeaderArgument($rArguments, $rHeaderLine) { + $rApplied = false; + foreach (array_keys($rArguments) as $rID) { + if ($rArguments[$rID]['argument_key'] == 'headers') { + $rArguments[$rID]['value'] .= "\r\n" . $rHeaderLine; + $rApplied = true; + } + } + if (!$rApplied) { + $rArguments[] = array('value' => $rHeaderLine, 'argument_key' => 'headers', 'argument_cat' => 'fetch', 'argument_wprotocol' => 'http', 'argument_type' => 'text', 'argument_cmd' => "-headers '%s" . "\r\n" . "'"); + } + return $rArguments; + } + public static function createChannelItem($rStreamID, $rSource) { global $rSettings, $rServers, $rFFMPEG_CPU, $rFFMPEG_GPU; $db = self::db(); @@ -916,7 +941,6 @@ class StreamProcess { $rProtocol = ''; $rFFProbeOutput = array(); foreach ($rSources as $rSource) { - $rProcessed = false; $rRealSource = $rSource; $rStreamSource = StreamUtils::parseStreamURL($rSource); echo 'Checking source: ' . $rSource . "\n"; @@ -924,43 +948,16 @@ class StreamProcess { $rIsXC_VM = ($rLoopback ? true : StreamUtils::detectXC_VM($rStreamSource)); if ($rIsXC_VM && !$rLoopback && $rSettings['send_xc_vm_header']) { - foreach (array_keys($rStream['stream_arguments']) as $rID) { - if ($rStream['stream_arguments'][$rID]['argument_key'] == 'headers') { - $rStream['stream_arguments'][$rID]['value'] .= "\r\n" . 'X-XC_VM-Detect:1'; - $rProcessed = true; - } - } - - if (!$rProcessed) { - $rStream['stream_arguments'][] = array('value' => 'X-XC_VM-Detect:1', 'argument_key' => 'headers', 'argument_cat' => 'fetch', 'argument_wprotocol' => 'http', 'argument_type' => 'text', 'argument_cmd' => "-headers '%s" . "\r\n" . "'"); - } + $rStream['stream_arguments'] = self::appendHeaderArgument($rStream['stream_arguments'], 'X-XC_VM-Detect:1'); } $rProbeArguments = $rStream['stream_arguments']; if ($rIsXC_VM && $rStream['server_info']['on_demand'] == 1 && $rSettings['request_prebuffer'] == 1) { - foreach (array_keys($rStream['stream_arguments']) as $rID) { - if ($rStream['stream_arguments'][$rID]['argument_key'] == 'headers') { - $rStream['stream_arguments'][$rID]['value'] .= "\r\n" . 'X-XC_VM-Prebuffer:1'; - $rProcessed = true; - } - } - - if (!$rProcessed) { - $rStream['stream_arguments'][] = array('value' => 'X-XC_VM-Prebuffer:1', 'argument_key' => 'headers', 'argument_cat' => 'fetch', 'argument_wprotocol' => 'http', 'argument_type' => 'text', 'argument_cmd' => "-headers '%s" . "\r\n" . "'"); - } + $rStream['stream_arguments'] = self::appendHeaderArgument($rStream['stream_arguments'], 'X-XC_VM-Prebuffer:1'); } - foreach (array_keys($rProbeArguments) as $rID) { - if ($rProbeArguments[$rID]['argument_key'] == 'headers') { - $rProbeArguments[$rID]['value'] .= "\r\n" . 'X-XC_VM-Prebuffer:1'; - $rProcessed = true; - } - } - - if (!$rProcessed) { - $rProbeArguments[] = array('value' => 'X-XC_VM-Prebuffer:1', 'argument_key' => 'headers', 'argument_cat' => 'fetch', 'argument_wprotocol' => 'http', 'argument_type' => 'text', 'argument_cmd' => "-headers '%s" . "\r\n" . "'"); - } + $rProbeArguments = self::appendHeaderArgument($rProbeArguments, 'X-XC_VM-Prebuffer:1'); $rProtocol = strtolower(substr($rStreamSource, 0, strpos($rStreamSource, '://'))); $rProbeOptions = implode(' ', StreamUtils::getArguments($rProbeArguments, $rProtocol, 'fetch')); diff --git a/tests/Unit/StreamProcessMovieOutputTest.php b/tests/Unit/StreamProcessMovieOutputTest.php index 3b352827..4f85f0c0 100644 --- a/tests/Unit/StreamProcessMovieOutputTest.php +++ b/tests/Unit/StreamProcessMovieOutputTest.php @@ -185,4 +185,47 @@ final class StreamProcessMovieOutputTest extends TestCase { public function testCurrentLastLeavesOrderUnchanged(): void { $this->assertSame(['a', 'b', 'c', 'd'], $this->call('rotateSourcesPastCurrent', ['a', 'b', 'c', 'd'], 0, 'd')); } + + // ── appendHeaderArgument (startStream header injection) ───── + + public function testHeaderAppendedToExistingHeadersArg(): void { + $args = [ + ['argument_key' => 'user_agent', 'value' => 'VLC'], + ['argument_key' => 'headers', 'value' => 'X-A:1'], + ]; + $out = $this->call('appendHeaderArgument', $args, 'X-XC_VM-Detect:1'); + $this->assertCount(2, $out, 'appended, not added'); + $this->assertSame("X-A:1\r\nX-XC_VM-Detect:1", $out[1]['value']); + $this->assertSame('VLC', $out[0]['value'], 'other args untouched'); + } + + public function testHeaderArgCreatedWhenAbsent(): void { + $args = [['argument_key' => 'user_agent', 'value' => 'VLC']]; + $out = $this->call('appendHeaderArgument', $args, 'X-XC_VM-Prebuffer:1'); + $this->assertCount(2, $out); + $new = $out[array_key_last($out)]; + $this->assertSame('headers', $new['argument_key']); + $this->assertSame('X-XC_VM-Prebuffer:1', $new['value']); + $this->assertSame('fetch', $new['argument_cat']); + $this->assertSame("-headers '%s\r\n'", $new['argument_cmd']); + } + + public function testHeaderAppendedToEveryHeadersArg(): void { + // mirrors the original loop: every 'headers' entry receives the line. + $args = [ + ['argument_key' => 'headers', 'value' => 'A'], + ['argument_key' => 'headers', 'value' => 'B'], + ]; + $out = $this->call('appendHeaderArgument', $args, 'X:1'); + $this->assertSame("A\r\nX:1", $out[0]['value']); + $this->assertSame("B\r\nX:1", $out[1]['value']); + $this->assertCount(2, $out, 'no new arg added'); + } + + public function testEmptyArgumentsGetsNewHeaderArg(): void { + $out = $this->call('appendHeaderArgument', [], 'X:1'); + $this->assertCount(1, $out); + $this->assertSame('headers', $out[0]['argument_key']); + $this->assertSame('X:1', $out[0]['value']); + } }