@torrent-tv/proxy 2.100.10 → 2.100.11

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.100.10",
3
+ "version": "2.100.11",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
@@ -36,7 +36,15 @@ export class Renditions {
36
36
  #host;
37
37
 
38
38
  /**
39
- * @param {object} host - `viewerSecondsOn`, `audioStartSecondsFor`, `activeOutputFor`, `viewersOf`, `audioRenditionName`, `logger`, `placeViewer`, `viewerLeaves`, `createOrGetSession`, `planEncodersSoon`, `disposeSession`, `viewerPositionOf`, `encodeRuns`, `fileStartTimeReads`, `getCachedAudioTracks`, `getCachedMediaInfo`, `getContainerMediaInfo`, `localBaseUrl`, `outputTimes`, `outputs`, `quality`, `qualityOffer`, `segmentDurationSec`, `sourceFiles`, `viewers`, `generationOfRequest`, `givenOutputOf`, `noteGivenOutput`, `chosenOutputOf`, `chooseOutput`, `storedPieceReady`, `headersCompatible`, `audioChoiceOf`, `linkMbpsOf`, `qualityModeOf`, `noteServingVerdict`, `limitsFor`, `heightsChosenAs`, `highestGivenSegmentOf`, `sameHeightSwitchOf`, `noteSameHeightSwitch`, `switchingOnto`, `segmentClosed`, `bufferedSecondsOf`, `minimumBufferSecondsFor`
39
+ * The segment each viewer's move was last said to wait for on the output
40
+ * they are leaving, so that a wait is said once per segment.
41
+ *
42
+ * @type {Map<string, number>}
43
+ */
44
+ #moveWaitsSaid = new Map();
45
+
46
+ /**
47
+ * @param {object} host - `viewerSecondsOn`, `audioStartSecondsFor`, `activeOutputFor`, `viewersOf`, `audioRenditionName`, `logger`, `placeViewer`, `viewerLeaves`, `createOrGetSession`, `planEncodersSoon`, `disposeSession`, `viewerPositionOf`, `encodeRuns`, `fileStartTimeReads`, `getCachedAudioTracks`, `getCachedMediaInfo`, `getContainerMediaInfo`, `localBaseUrl`, `outputTimes`, `outputs`, `quality`, `qualityOffer`, `segmentDurationSec`, `sourceFiles`, `viewers`, `generationOfRequest`, `givenOutputOf`, `noteGivenOutput`, `chosenOutputOf`, `chooseOutput`, `storedPieceReady`, `headersCompatible`, `audioChoiceOf`, `linkMbpsOf`, `qualityModeOf`, `noteServingVerdict`, `limitsFor`, `heightsChosenAs`, `highestGivenSegmentOf`, `sameHeightSwitchOf`, `noteSameHeightSwitch`, `switchingOnto`, `switchingFrom`, `segmentClosed`, `bufferedSecondsOf`, `minimumBufferSecondsFor`
40
48
  */
41
49
  constructor(host) {
42
50
  this.#host = host;
@@ -836,6 +844,14 @@ export class Renditions {
836
844
  for (const consumerId of this.#host.switchingOnto(output)) {
837
845
  this.#reconsiderSameHeightSwitch(base, consumerId, index);
838
846
  }
847
+ // A segment closed on the output a viewer is LEAVING may be the one their
848
+ // move waits for (`#givenAndOpenOn`). It is behind the segment the move
849
+ // begins at by construction, so it is asked about with no index.
850
+ for (const consumerId of this.#host.switchingFrom?.(output) ?? []) {
851
+ if (this.#host.activeOutputFor({ base, consumerId, outputs: this.#host.outputs }) === output) {
852
+ this.#reconsiderSameHeightSwitch(base, consumerId, null);
853
+ }
854
+ }
839
855
  }
840
856
  }
841
857
 
@@ -894,6 +910,28 @@ export class Renditions {
894
910
  }
895
911
  }
896
912
 
913
+ /**
914
+ * The segment just before the one a move would begin at, when it was given
915
+ * to this viewer from the output on their screen and is not closed there:
916
+ * the segment their held request waits for. Null otherwise.
917
+ *
918
+ * @param {HlsSession | null} current - The output on their screen.
919
+ * @param {string} consumerId
920
+ * @param {number[]} askedHeights
921
+ * @param {number} next - The segment the move would begin at.
922
+ * @returns {number | null}
923
+ */
924
+ #givenAndOpenOn(current, consumerId, askedHeights, next) {
925
+ const segment = next - 1;
926
+ if (!current?.outputKey || segment < 0) {
927
+ return null;
928
+ }
929
+ const generation = this.#host.generationOfRequest(consumerId, Number.NaN);
930
+ const given = askedHeights.some((height) =>
931
+ this.#host.givenOutputOf(consumerId, generation, height, segment) === current.outputKey);
932
+ return given && !this.#host.segmentClosed(current.outputKey, segment) ? segment : null;
933
+ }
934
+
897
935
  /**
898
936
  * The segment this viewer will ask for next at these heights.
899
937
  *
@@ -967,6 +1005,24 @@ export class Renditions {
967
1005
  if (!this.#host.segmentClosed(prepared.outputKey ?? "", next)) {
968
1006
  return;
969
1007
  }
1008
+ // THE SEGMENT ALREADY GIVEN FROM THE OUTPUT ON SCREEN. A request is held by
1009
+ // the output it was given from, and leaving that output stops its encoder:
1010
+ // a segment given and not yet closed there would then be made by nobody.
1011
+ // On Home Assistant a viewer waiting for #2176 was moved from #2177 and
1012
+ // the picture stood for good (r71, torrent-tv/meta#178). The move waits for
1013
+ // that segment to be closed; an output whose encoding has failed for good
1014
+ // closes nothing, and its held request is the serving path's to answer.
1015
+ const open = this.#givenAndOpenOn(current, consumerId, move.askedHeights, next);
1016
+ if (open !== null && !this.#host.encodeRuns.hasFailed(current)) {
1017
+ if (this.#moveWaitsSaid.get(consumerId) !== open) {
1018
+ this.#moveWaitsSaid.set(consumerId, open);
1019
+ this.#host.logger.info(
1020
+ `transcode ${base.id} ${consumerId}'s move to ${move.outputKey} waits for #${open} on ` +
1021
+ `${current.outputKey}, given and not yet closed there`
1022
+ );
1023
+ }
1024
+ return;
1025
+ }
970
1026
  // THE CUSHION, for a move that is not urgent (roadmap item 98): a move up
971
1027
  // is made once the viewer holds the cushion this file needs, and their
972
1028
  // next report asks again until they do. A move DOWN is made at once — it
@@ -1026,6 +1082,7 @@ export class Renditions {
1026
1082
  * @returns {void}
1027
1083
  */
1028
1084
  #completeSameHeightSwitch(base, consumerId, move, prepared, current, next) {
1085
+ this.#moveWaitsSaid.delete(consumerId);
1029
1086
  for (const height of move.askedHeights) {
1030
1087
  this.#choose(consumerId, height, prepared, base);
1031
1088
  }
@@ -1063,6 +1120,7 @@ export class Renditions {
1063
1120
  * @returns {void}
1064
1121
  */
1065
1122
  #cancelSameHeightSwitch(base, consumerId, reason) {
1123
+ this.#moveWaitsSaid.delete(consumerId);
1066
1124
  const move = this.#host.sameHeightSwitchOf(consumerId);
1067
1125
  if (!move) {
1068
1126
  return;
@@ -60,6 +60,7 @@ import {
60
60
  stepBeingWarmedOf,
61
61
  stepBeingWarmedSinceOf,
62
62
  stepOnScreenOf,
63
+ switchingFrom,
63
64
  switchingOnto,
64
65
  watches
65
66
  } from "../viewer/choices.js";
@@ -450,6 +451,7 @@ export function wireOutputs({
450
451
  sameHeightSwitchOf: (consumerId) => sameHeightSwitchOf(parts.viewers, consumerId),
451
452
  noteSameHeightSwitch: (consumerId, value) => noteSameHeightSwitch(parts.viewers, consumerId, value),
452
453
  switchingOnto: (output) => switchingOnto(parts.viewers, output),
454
+ switchingFrom: (output) => switchingFrom(parts.viewers, output),
453
455
  // What a viewer holds, and the cushion this file needs before a move that
454
456
  // is not urgent is made (roadmap item 98).
455
457
  bufferedSecondsOf: (consumerId) => bufferedSecondsOf(parts.viewers, consumerId),
@@ -565,6 +565,25 @@ export function switchingOnto(viewers, output) {
565
565
  return found;
566
566
  }
567
567
 
568
+ /**
569
+ * The present viewers on this output for whom a move onto ANOTHER output is
570
+ * being prepared: the ones a segment closed here may let go.
571
+ *
572
+ * @param {object} viewers
573
+ * @param {object} output
574
+ * @returns {string[]}
575
+ */
576
+ export function switchingFrom(viewers, output) {
577
+ const found = [];
578
+ for (const [consumerId, viewer] of viewers.forOutput(output)) {
579
+ const move = viewer.sameHeightSwitch;
580
+ if (viewer.isPresent() && move && move.outputKey !== output.outputKey) {
581
+ found.push(consumerId);
582
+ }
583
+ }
584
+ return found;
585
+ }
586
+
568
587
  /**
569
588
  * Every output a present viewer is being prepared onto, as ids.
570
589
  *