@torrent-tv/proxy 2.7.0 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@torrent-tv/proxy",
3
- "version": "2.7.0",
3
+ "version": "2.8.0",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "publishConfig": {
@@ -24,12 +24,6 @@ const DEFAULT_SEGMENT_DURATION_SEC = 4;
24
24
  // is allowed to be before we restart ffmpeg at that position (server-side seek).
25
25
  // Requests within the window are served by waiting for the running encode.
26
26
  const MAX_LOOKAHEAD_SEGMENTS = 8;
27
- // Just-in-time pacing: pause the encoder once it is this many segments ahead of
28
- // the player's current position, and resume once the lead shrinks to the resume
29
- // threshold. This keeps a comfortable buffer for smooth playback while capping
30
- // CPU near real-time instead of racing through the whole file at once.
31
- const PACING_PAUSE_AHEAD_SEGMENTS = 8;
32
- const PACING_RESUME_AHEAD_SEGMENTS = 4;
33
27
  // Idle TTL: a session is disposed this long after the last segment/playlist
34
28
  // access. Kept short so an ffmpeg process does not keep burning CPU after the
35
29
  // viewer stops or navigates away. Active playback refreshes the timer on every
@@ -498,11 +492,6 @@ export class HlsSessionManager {
498
492
  encodeStartIndex: 0,
499
493
  // Guards against repeatedly restarting to the same seek position.
500
494
  pendingRestartIndex: -1,
501
- // Just-in-time pacing: highest segment index the player has requested
502
- // (≈ playhead) and whether the encoder is currently paused for running
503
- // far enough ahead.
504
- playheadIndex: 0,
505
- paused: false,
506
495
  progress: {
507
496
  state: "starting",
508
497
  processedSeconds: 0,
@@ -590,19 +579,14 @@ export class HlsSessionManager {
590
579
 
591
580
  // Terminate any existing encode process before starting a new one. The
592
581
  // old process's exit handler no-ops because session.ffmpeg is reassigned
593
- // below (it checks identity). Resume first in case it was paused, so the
594
- // termination signal is actually delivered.
582
+ // below (it checks identity).
595
583
  if (session.ffmpeg && !session.ffmpeg.killed) {
596
584
  try {
597
- if (session.paused) {
598
- session.ffmpeg.kill("SIGCONT");
599
- }
600
585
  session.ffmpeg.kill("SIGTERM");
601
586
  } catch (_error) {
602
587
  // Best effort.
603
588
  }
604
589
  }
605
- session.paused = false;
606
590
 
607
591
  const videoCodecArgs = session.transcodeVideo
608
592
  ? [
@@ -669,7 +653,6 @@ export class HlsSessionManager {
669
653
  session.ffmpeg = ffmpeg;
670
654
  session.encodeStartIndex = safeIndex;
671
655
  session.pendingRestartIndex = -1;
672
- session.playheadIndex = safeIndex;
673
656
  session.state = session.state === "disposed" ? "disposed" : "starting";
674
657
  session.progress.state = "running";
675
658
  session.progress.processedSeconds = startSeconds;
@@ -742,8 +725,6 @@ export class HlsSessionManager {
742
725
  ` speed=${session.progress.speed || "n/a"}`
743
726
  );
744
727
  }
745
- // Just-in-time pacing: pause once we are far enough ahead of the player.
746
- this.#maybePauseEncoder(session);
747
728
  }
748
729
  });
749
730
 
@@ -819,79 +800,6 @@ export class HlsSessionManager {
819
800
  this.#startEncodeRun(session, index);
820
801
  }
821
802
 
822
- /**
823
- * Segment index ffmpeg has encoded up to so far, derived from the output
824
- * timestamp reported via -progress.
825
- *
826
- * @param {HlsSession} session
827
- * @returns {number}
828
- */
829
- #producedSegmentIndex(session) {
830
- const processed = Number(session.progress?.processedSeconds);
831
- if (!Number.isFinite(processed) || processed <= 0) {
832
- return session.encodeStartIndex;
833
- }
834
- return Math.floor(processed / this.segmentDurationSec);
835
- }
836
-
837
- /**
838
- * Pause the encoder once it has produced enough segments ahead of the
839
- * player's current position (just-in-time pacing). No-op on platforms
840
- * without POSIX job-control signals (fail-open: the encoder keeps running).
841
- *
842
- * @param {HlsSession} session
843
- * @returns {void}
844
- */
845
- #maybePauseEncoder(session) {
846
- if (session.paused || !session.ffmpeg || session.ffmpeg.killed) {
847
- return;
848
- }
849
- if (process.platform === "win32") {
850
- return;
851
- }
852
- const ahead = this.#producedSegmentIndex(session) - session.playheadIndex;
853
- if (ahead < PACING_PAUSE_AHEAD_SEGMENTS) {
854
- return;
855
- }
856
- try {
857
- session.ffmpeg.kill("SIGSTOP");
858
- session.paused = true;
859
- logger.info(
860
- `transcode ${session.id} paused (buffered ${ahead} segments ahead) "${session.fileName}"`
861
- );
862
- } catch (_error) {
863
- // Fail-open: if we cannot pause, let the encoder keep running.
864
- }
865
- }
866
-
867
- /**
868
- * Resume a paused encoder when the player has caught up enough that the
869
- * buffered lead has shrunk to the resume threshold.
870
- *
871
- * @param {HlsSession} session
872
- * @returns {void}
873
- */
874
- #maybeResumeEncoder(session) {
875
- if (!session.paused || !session.ffmpeg || session.ffmpeg.killed) {
876
- return;
877
- }
878
- const ahead = this.#producedSegmentIndex(session) - session.playheadIndex;
879
- if (ahead > PACING_RESUME_AHEAD_SEGMENTS) {
880
- return;
881
- }
882
- try {
883
- session.ffmpeg.kill("SIGCONT");
884
- session.paused = false;
885
- session.progress.updatedAt = Date.now();
886
- logger.info(`transcode ${session.id} resumed (lead ${ahead} segments) "${session.fileName}"`);
887
- } catch (_error) {
888
- // If resume fails, force a restart from the player's position so it does
889
- // not stall (fail-safe toward smooth playback).
890
- session.paused = false;
891
- this.#startEncodeRun(session, session.playheadIndex);
892
- }
893
- }
894
-
895
803
  #buildVideoFilter(targetWidth, targetHeight) {
896
804
  const safeWidth = Number.isInteger(targetWidth) && targetWidth > 0 ? targetWidth : 1280;
897
805
  const safeHeight = Number.isInteger(targetHeight) && targetHeight > 0 ? targetHeight : 720;
@@ -970,16 +878,6 @@ export class HlsSessionManager {
970
878
  }
971
879
  session.lastAccessedAt = Date.now();
972
880
 
973
- // Track the player position (≈ playhead) from segment requests and resume
974
- // a paused encoder as the player approaches the buffered edge.
975
- if (fileName !== PLAYLIST_FILE_NAME) {
976
- const requestedIndex = segmentIndexFromName(fileName);
977
- if (requestedIndex >= 0) {
978
- session.playheadIndex = Math.max(session.playheadIndex, requestedIndex);
979
- this.#maybeResumeEncoder(session);
980
- }
981
- }
982
-
983
881
  // Serve the synthetic VOD playlist (full duration, terminated with
984
882
  // #EXT-X-ENDLIST) so the player gets the correct total length and a fully
985
883
  // seekable timeline up-front, independent of how far ffmpeg has encoded.
@@ -1127,16 +1025,6 @@ export class HlsSessionManager {
1127
1025
  this.sessionIdBySource.delete(session.sourceMapKey);
1128
1026
 
1129
1027
  if (session.ffmpeg && !session.ffmpeg.killed) {
1130
- // Resume first if paused, otherwise SIGTERM is not delivered to a
1131
- // stopped process.
1132
- if (session.paused) {
1133
- try {
1134
- session.ffmpeg.kill("SIGCONT");
1135
- } catch (_error) {
1136
- // Best effort.
1137
- }
1138
- session.paused = false;
1139
- }
1140
1028
  session.ffmpeg.kill("SIGTERM");
1141
1029
  await waitForChildExit(session.ffmpeg);
1142
1030
  }