mirror of
https://github.com/Vateron-Media/XC_VM.git
synced 2026-09-30 20:02:06 +02:00
refactor(streaming): extract delay resume helpers from startStream
The delayed-HLS branch inline-computed two things: the resume segment number parsed from the existing delay playlist (last-or-previous line's _<n>.ts index), and the delay sleep seconds (delay_minutes*60 reduced ~10s per already-produced segment, floored at 0). Pull both into pure resolveDelaySegmentStart() and resolveDelaySleepTime() helpers with unit tests. The playlist merge and the short-playlist abort stay inline. No behaviour change. Verified: phpstan level 5 green, phpunit 391/391, make gates green.
This commit is contained in:
@@ -540,6 +540,44 @@ class StreamProcess {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume segment number for a delayed HLS stream, parsed from the existing
|
||||
* delay playlist lines. Reads the last (or previous) line's "_<n>.ts" index
|
||||
* and returns n+1; 0 when no index is found. Extracted from startStream.
|
||||
*
|
||||
* @param array $rLines Playlist lines (>= 2, newest last).
|
||||
* @param mixed $rStreamID Stream id; its "<id>_" marks the stream's own segment line.
|
||||
* @return int Next segment number, or 0.
|
||||
*/
|
||||
private static function resolveDelaySegmentStart($rLines, $rStreamID) {
|
||||
$rLast = $rLines[count($rLines) - 1];
|
||||
$rPrev = $rLines[count($rLines) - 2];
|
||||
$rTarget = stristr($rLast, $rStreamID . '_') ? $rLast : $rPrev;
|
||||
if (preg_match('/_(.*?)\.ts/', $rTarget, $rMatches)) {
|
||||
return intval($rMatches[1]) + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delay sleep seconds for a stream: delay_minutes*60, reduced by ~10s per
|
||||
* already-produced segment (never below 0). Extracted from startStream.
|
||||
*
|
||||
* @param mixed $rDelayMinutes Configured delay in minutes.
|
||||
* @param int $rSegmentStart Resume segment number (0 = fresh).
|
||||
* @return int Seconds to sleep.
|
||||
*/
|
||||
private static function resolveDelaySleepTime($rDelayMinutes, $rSegmentStart) {
|
||||
$rSleepTime = $rDelayMinutes * 60;
|
||||
if ($rSegmentStart > 0) {
|
||||
$rSleepTime -= ($rSegmentStart - 1) * 10;
|
||||
if ($rSleepTime <= 0) {
|
||||
$rSleepTime = 0;
|
||||
}
|
||||
}
|
||||
return $rSleepTime;
|
||||
}
|
||||
|
||||
public static function createChannelItem($rStreamID, $rSource) {
|
||||
global $rSettings, $rServers, $rFFMPEG_CPU, $rFFMPEG_GPU;
|
||||
$db = self::db();
|
||||
@@ -1257,18 +1295,7 @@ class StreamProcess {
|
||||
return;
|
||||
}
|
||||
|
||||
$lastLine = $rFile[count($rFile) - 1];
|
||||
$prevLine = $rFile[count($rFile) - 2];
|
||||
|
||||
if (stristr($lastLine, $rStreamID . '_')) {
|
||||
if (preg_match('/_(.*?)\.ts/', $lastLine, $rMatches)) {
|
||||
$rSegmentStart = intval($rMatches[1]) + 1;
|
||||
}
|
||||
} else {
|
||||
if (preg_match('/_(.*?)\.ts/', $prevLine, $rMatches)) {
|
||||
$rSegmentStart = intval($rMatches[1]) + 1;
|
||||
}
|
||||
}
|
||||
$rSegmentStart = self::resolveDelaySegmentStart($rFile, $rStreamID);
|
||||
|
||||
if (file_exists($oldM3u8File)) {
|
||||
file_put_contents($oldM3u8File, file_get_contents($oldM3u8File) . file_get_contents($m3u8File));
|
||||
@@ -1281,15 +1308,7 @@ class StreamProcess {
|
||||
$rFFMPEG .= implode(' ', StreamUtils::parseTranscode($rStream['stream_info']['transcode_attributes'])) . ' ';
|
||||
$rFFMPEG .= '{MAP} -individual_header_trailer 0 -f hls -hls_time ' . intval($rSegmentSettings['seg_time']) . ' -hls_list_size ' . intval($rStream['stream_info']['delay_minutes']) * 6 . ' -hls_delete_threshold 4 -start_number ' . $rSegmentStart . ' -hls_flags delete_segments+discont_start+omit_endlist -hls_segment_type mpegts -hls_segment_filename "' . DELAY_PATH . intval($rStreamID) . '_%d.ts" "' . DELAY_PATH . intval($rStreamID) . '_.m3u8" ';
|
||||
|
||||
$rSleepTime = $rStream['stream_info']['delay_minutes'] * 60;
|
||||
|
||||
if ($rSegmentStart > 0) {
|
||||
$rSleepTime -= ($rSegmentStart - 1) * 10;
|
||||
|
||||
if ($rSleepTime <= 0) {
|
||||
$rSleepTime = 0;
|
||||
}
|
||||
}
|
||||
$rSleepTime = self::resolveDelaySleepTime($rStream['stream_info']['delay_minutes'], $rSegmentStart);
|
||||
}
|
||||
|
||||
$rFFMPEG .= ' >/dev/null 2>>' . STREAMS_PATH . intval($rStreamID) . '.errors & echo $! > ' . STREAMS_PATH . intval($rStreamID) . '_.pid';
|
||||
|
||||
@@ -322,4 +322,33 @@ final class StreamProcessMovieOutputTest extends TestCase {
|
||||
$this->assertSame(1080, $out['codecs']['video']['height']);
|
||||
$this->assertSame('aac', $out['codecs']['audio']['codec_name']);
|
||||
}
|
||||
|
||||
// ── resolveDelaySegmentStart / resolveDelaySleepTime (delay) ─
|
||||
|
||||
public function testDelaySegmentStartFromLastLine(): void {
|
||||
// last line is this stream's segment: _4.ts -> resume at 5.
|
||||
$this->assertSame(5, $this->call('resolveDelaySegmentStart', ['#EXTINF:6,', '77_4.ts'], 77));
|
||||
}
|
||||
|
||||
public function testDelaySegmentStartFromPrevLineWhenLastNotOwn(): void {
|
||||
// last line isn't a segment of this stream -> fall back to prev line.
|
||||
$this->assertSame(9, $this->call('resolveDelaySegmentStart', ['77_8.ts', '#EXTINF:6,'], 77));
|
||||
}
|
||||
|
||||
public function testDelaySegmentStartZeroWhenNoTsIndex(): void {
|
||||
$this->assertSame(0, $this->call('resolveDelaySegmentStart', ['#EXTM3U', '#EXT-X-VERSION:3'], 77));
|
||||
}
|
||||
|
||||
public function testDelaySleepTimeFullWhenFresh(): void {
|
||||
$this->assertSame(300, $this->call('resolveDelaySleepTime', 5, 0));
|
||||
}
|
||||
|
||||
public function testDelaySleepTimeReducedPerSegment(): void {
|
||||
$this->assertSame(270, $this->call('resolveDelaySleepTime', 5, 4)); // 300 - (4-1)*10
|
||||
$this->assertSame(300, $this->call('resolveDelaySleepTime', 5, 1)); // (1-1)*10 = 0
|
||||
}
|
||||
|
||||
public function testDelaySleepTimeClampedToZero(): void {
|
||||
$this->assertSame(0, $this->call('resolveDelaySleepTime', 1, 100)); // 60 - 990 -> 0
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user