homebridge-plugin-utils 1.35.0 → 2.1.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/README.md +130 -2
- package/build/eslint-plugin/README.md +164 -0
- package/build/eslint-plugin/config.mjs +308 -0
- package/build/eslint-plugin/index.mjs +7 -0
- package/build/eslint-plugin/plugin.mjs +46 -0
- package/build/eslint-plugin/rules/blank-line-after-open-brace.mjs +137 -0
- package/build/eslint-plugin/rules/blank-line-after-open-brace.test.mjs +112 -0
- package/build/eslint-plugin/rules/comment-style.mjs +190 -0
- package/build/eslint-plugin/rules/comment-style.test.mjs +190 -0
- package/build/eslint-plugin/rules/enforce-node-protocol.mjs +114 -0
- package/build/eslint-plugin/rules/enforce-node-protocol.test.mjs +116 -0
- package/build/eslint-plugin/rules/paren-comparisons-in-logical.mjs +94 -0
- package/build/eslint-plugin/rules/paren-comparisons-in-logical.test.mjs +107 -0
- package/build/eslint-plugin/rules/split-type-imports.mjs +354 -0
- package/build/eslint-plugin/rules/split-type-imports.test.mjs +268 -0
- package/build/eslint-plugin/test-setup.mjs +21 -0
- package/build/fs-ops.mjs +184 -0
- package/build/tsconfig.json +18 -3
- package/dist/backpressure.d.ts +94 -45
- package/dist/backpressure.js +229 -93
- package/dist/backpressure.js.map +1 -1
- package/dist/cli/index.d.ts +166 -0
- package/dist/cli/index.js +551 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/clock-double.d.ts +93 -0
- package/dist/clock-double.js +141 -0
- package/dist/clock-double.js.map +1 -0
- package/dist/clock.d.ts +39 -0
- package/dist/clock.js +34 -0
- package/dist/clock.js.map +1 -0
- package/dist/disposable-stack.d.ts +59 -0
- package/dist/disposable-stack.js +155 -0
- package/dist/disposable-stack.js.map +1 -0
- package/dist/docChrome.d.ts +260 -0
- package/dist/docChrome.js +361 -0
- package/dist/docChrome.js.map +1 -0
- package/dist/eslint-plugin/config.d.mts +193 -0
- package/dist/eslint-plugin/index.d.mts +2 -0
- package/dist/eslint-plugin/plugin.d.mts +87 -0
- package/dist/eslint-plugin/rules/blank-line-after-open-brace.d.mts +19 -0
- package/dist/eslint-plugin/rules/comment-style.d.mts +15 -0
- package/dist/eslint-plugin/rules/enforce-node-protocol.d.mts +18 -0
- package/dist/eslint-plugin/rules/paren-comparisons-in-logical.d.mts +15 -0
- package/dist/eslint-plugin/rules/split-type-imports.d.mts +16 -0
- package/dist/featureOptions-docs.d.ts +96 -0
- package/dist/featureOptions-docs.js +193 -0
- package/dist/featureOptions-docs.js.map +1 -0
- package/dist/featureOptions.d.ts +674 -0
- package/dist/featureOptions.js +870 -0
- package/dist/featureOptions.js.map +1 -0
- package/dist/ffmpeg/codecs.d.ts +256 -72
- package/dist/ffmpeg/codecs.js +477 -262
- package/dist/ffmpeg/codecs.js.map +1 -1
- package/dist/ffmpeg/dgram-util.d.ts +46 -0
- package/dist/ffmpeg/dgram-util.js +38 -0
- package/dist/ffmpeg/dgram-util.js.map +1 -0
- package/dist/ffmpeg/exec.d.ts +83 -64
- package/dist/ffmpeg/exec.js +77 -86
- package/dist/ffmpeg/exec.js.map +1 -1
- package/dist/ffmpeg/fmp4.d.ts +49 -2
- package/dist/ffmpeg/fmp4.js +47 -11
- package/dist/ffmpeg/fmp4.js.map +1 -1
- package/dist/ffmpeg/hap-enums.d.ts +214 -0
- package/dist/ffmpeg/hap-enums.js +92 -0
- package/dist/ffmpeg/hap-enums.js.map +1 -0
- package/dist/ffmpeg/index.d.ts +16 -9
- package/dist/ffmpeg/index.js +6 -0
- package/dist/ffmpeg/index.js.map +1 -1
- package/dist/ffmpeg/mp4-assembler.d.ts +161 -0
- package/dist/ffmpeg/mp4-assembler.js +424 -0
- package/dist/ffmpeg/mp4-assembler.js.map +1 -0
- package/dist/ffmpeg/mp4-parser.d.ts +94 -0
- package/dist/ffmpeg/mp4-parser.js +130 -0
- package/dist/ffmpeg/mp4-parser.js.map +1 -0
- package/dist/ffmpeg/options.d.ts +62 -149
- package/dist/ffmpeg/options.js +608 -499
- package/dist/ffmpeg/options.js.map +1 -1
- package/dist/ffmpeg/process.d.ts +142 -96
- package/dist/ffmpeg/process.js +406 -278
- package/dist/ffmpeg/process.js.map +1 -1
- package/dist/ffmpeg/record.d.ts +325 -186
- package/dist/ffmpeg/record.js +418 -565
- package/dist/ffmpeg/record.js.map +1 -1
- package/dist/ffmpeg/recording-process-double.d.ts +157 -0
- package/dist/ffmpeg/recording-process-double.js +190 -0
- package/dist/ffmpeg/recording-process-double.js.map +1 -0
- package/dist/ffmpeg/rtp-parser.d.ts +70 -0
- package/dist/ffmpeg/rtp-parser.js +77 -0
- package/dist/ffmpeg/rtp-parser.js.map +1 -0
- package/dist/ffmpeg/rtp.d.ts +198 -141
- package/dist/ffmpeg/rtp.js +474 -251
- package/dist/ffmpeg/rtp.js.map +1 -1
- package/dist/ffmpeg/settings.d.ts +5 -2
- package/dist/ffmpeg/settings.js +20 -5
- package/dist/ffmpeg/settings.js.map +1 -1
- package/dist/ffmpeg/stream.d.ts +57 -107
- package/dist/ffmpeg/stream.js +121 -150
- package/dist/ffmpeg/stream.js.map +1 -1
- package/dist/formatters.d.ts +106 -0
- package/dist/formatters.js +174 -0
- package/dist/formatters.js.map +1 -0
- package/dist/homebridge-enums.d.ts +30 -0
- package/dist/homebridge-enums.js +17 -0
- package/dist/homebridge-enums.js.map +1 -0
- package/dist/index.d.ts +13 -6
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/logclient/auth.d.ts +114 -0
- package/dist/logclient/auth.js +199 -0
- package/dist/logclient/auth.js.map +1 -0
- package/dist/logclient/cli-run.d.ts +76 -0
- package/dist/logclient/cli-run.js +639 -0
- package/dist/logclient/cli-run.js.map +1 -0
- package/dist/logclient/cli.d.ts +3 -0
- package/dist/logclient/cli.js +97 -0
- package/dist/logclient/cli.js.map +1 -0
- package/dist/logclient/client.d.ts +145 -0
- package/dist/logclient/client.js +600 -0
- package/dist/logclient/client.js.map +1 -0
- package/dist/logclient/config.d.ts +173 -0
- package/dist/logclient/config.js +199 -0
- package/dist/logclient/config.js.map +1 -0
- package/dist/logclient/endpoints.d.ts +54 -0
- package/dist/logclient/endpoints.js +73 -0
- package/dist/logclient/endpoints.js.map +1 -0
- package/dist/logclient/filter.d.ts +45 -0
- package/dist/logclient/filter.js +51 -0
- package/dist/logclient/filter.js.map +1 -0
- package/dist/logclient/frame.d.ts +93 -0
- package/dist/logclient/frame.js +203 -0
- package/dist/logclient/frame.js.map +1 -0
- package/dist/logclient/index.d.ts +31 -0
- package/dist/logclient/index.js +12 -0
- package/dist/logclient/index.js.map +1 -0
- package/dist/logclient/parser.d.ts +211 -0
- package/dist/logclient/parser.js +393 -0
- package/dist/logclient/parser.js.map +1 -0
- package/dist/logclient/rest.d.ts +41 -0
- package/dist/logclient/rest.js +111 -0
- package/dist/logclient/rest.js.map +1 -0
- package/dist/logclient/settings.d.ts +15 -0
- package/dist/logclient/settings.js +64 -0
- package/dist/logclient/settings.js.map +1 -0
- package/dist/logclient/socket-double.d.ts +201 -0
- package/dist/logclient/socket-double.js +384 -0
- package/dist/logclient/socket-double.js.map +1 -0
- package/dist/logclient/socket.d.ts +257 -0
- package/dist/logclient/socket.js +620 -0
- package/dist/logclient/socket.js.map +1 -0
- package/dist/logclient/stitch.d.ts +83 -0
- package/dist/logclient/stitch.js +146 -0
- package/dist/logclient/stitch.js.map +1 -0
- package/dist/logclient/time-expression.d.ts +42 -0
- package/dist/logclient/time-expression.js +181 -0
- package/dist/logclient/time-expression.js.map +1 -0
- package/dist/logclient/time-window.d.ts +38 -0
- package/dist/logclient/time-window.js +53 -0
- package/dist/logclient/time-window.js.map +1 -0
- package/dist/logclient/types.d.ts +107 -0
- package/dist/logclient/types.js +6 -0
- package/dist/logclient/types.js.map +1 -0
- package/dist/mqttClient.d.ts +287 -0
- package/dist/mqttClient.js +433 -0
- package/dist/mqttClient.js.map +1 -0
- package/dist/service.d.ts +64 -15
- package/dist/service.js +93 -66
- package/dist/service.js.map +1 -1
- package/dist/ui/featureOptions.js +870 -0
- package/dist/ui/featureOptions.js.map +1 -0
- package/dist/ui/formatters.js +174 -0
- package/dist/ui/formatters.js.map +1 -0
- package/dist/ui/pluginConfigSession.mjs +141 -0
- package/dist/ui/webUi-featureOptions/categoryState.mjs +135 -0
- package/dist/ui/webUi-featureOptions/effects/keyboard.mjs +61 -0
- package/dist/ui/webUi-featureOptions/effects/persist.mjs +226 -0
- package/dist/ui/webUi-featureOptions/effects/theme.mjs +398 -0
- package/dist/ui/webUi-featureOptions/effects/tokens.mjs +152 -0
- package/dist/ui/webUi-featureOptions/rendering.mjs +431 -0
- package/dist/ui/webUi-featureOptions/selectors.mjs +360 -0
- package/dist/ui/webUi-featureOptions/state.mjs +319 -0
- package/dist/ui/webUi-featureOptions/store.mjs +181 -0
- package/dist/ui/webUi-featureOptions/utils.mjs +213 -0
- package/dist/ui/webUi-featureOptions/views/connectionError.mjs +152 -0
- package/dist/ui/webUi-featureOptions/views/deviceInfo.mjs +80 -0
- package/dist/ui/webUi-featureOptions/views/header.mjs +77 -0
- package/dist/ui/webUi-featureOptions/views/nav.mjs +341 -0
- package/dist/ui/webUi-featureOptions/views/options.mjs +521 -0
- package/dist/ui/webUi-featureOptions/views/search.mjs +395 -0
- package/dist/ui/webUi-featureOptions.mjs +702 -0
- package/dist/ui/webUi.mjs +225 -88
- package/dist/util.d.ts +679 -45
- package/dist/util.js +830 -77
- package/dist/util.js.map +1 -1
- package/package.json +33 -15
- package/build/eslint-rules.mjs +0 -511
- package/dist/featureoptions.d.ts +0 -264
- package/dist/featureoptions.js +0 -480
- package/dist/featureoptions.js.map +0 -1
- package/dist/mqttclient.d.ts +0 -178
- package/dist/mqttclient.js +0 -310
- package/dist/mqttclient.js.map +0 -1
- package/dist/ui/featureoptions.js +0 -480
- package/dist/ui/featureoptions.js.map +0 -1
- package/dist/ui/webUi-featureoptions.mjs +0 -3765
package/dist/ffmpeg/stream.js
CHANGED
|
@@ -1,187 +1,158 @@
|
|
|
1
1
|
/* Copyright(C) 2017-2026, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
2
|
*
|
|
3
|
-
* ffmpeg/stream.ts:
|
|
3
|
+
* ffmpeg/stream.ts: FFmpeg process control for HomeKit livestreaming with signal-driven stream-health monitoring.
|
|
4
4
|
*/
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* HomeKit livestreaming FFmpeg process with a signal-driven internal stream-health monitor.
|
|
7
|
+
*
|
|
8
|
+
* This module defines `FfmpegStreamingProcess`, the specialization of {@link FfmpegProcess} for HomeKit live video sessions. The subclass extends the base directly and
|
|
9
|
+
* composes an internal UDP socket that watches the return port for inbound packets - the HomeKit client's RTCP receiver reports, which flow only once the client is
|
|
10
|
+
* receiving FFmpeg's output. The watchdog arms on the first such packet; if that return traffic then stops for longer than the configured window the socket aborts the
|
|
11
|
+
* process with `HbpuAbortError("timeout")` and the base class teardown handles the rest. There is no separate "delegate" surface, no error callbacks,
|
|
12
|
+
* and no reach-through to the raw ChildProcess - every external interaction is through the inherited `signal`, `ready`, `exited`, `stdin`, `stderr`, `stderrLog`,
|
|
13
|
+
* `abort()`, and `[Symbol.asyncDispose]`.
|
|
14
|
+
*
|
|
15
|
+
* `stdout` stays externally readable on this subclass because HBUP's two-way-audio talkback path forwards stdout bytes to the WebSocket that carries audio from the
|
|
16
|
+
* camera back to the HomeKit client. Unlike the fMP4 subclasses, there is no internal consumer of the stdout stream that would race with an external reader.
|
|
17
|
+
*
|
|
18
|
+
* The stream-health socket is intentionally simpler than {@link ffmpeg/rtp!RtpDemuxer | RtpDemuxer}: its sole job is to detect liveness on a known return port. It
|
|
19
|
+
* does not classify RTP vs. RTCP or forward packets. Two-way-audio demuxing is `RtpDemuxer`'s responsibility and lives in its own class.
|
|
20
|
+
*
|
|
21
|
+
* @module
|
|
22
|
+
*/
|
|
23
|
+
import { HbpuAbortError, Watchdog, onAbort } from "../util.js";
|
|
24
|
+
import { createDgramSocket, loopbackAddress } from "./dgram-util.js";
|
|
6
25
|
import { FfmpegProcess } from "./process.js";
|
|
7
|
-
import {
|
|
26
|
+
import { STREAM_HEALTH_TIMEOUT } from "./settings.js";
|
|
8
27
|
/**
|
|
9
|
-
*
|
|
28
|
+
* FFmpeg process specialization for HomeKit livestreaming. Extends {@link FfmpegProcess} directly and composes an internal stream-health UDP socket when a return port
|
|
29
|
+
* is configured.
|
|
10
30
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
31
|
+
* Lifecycle is entirely signal-driven: construction spawns the child and (optionally) binds the health socket; the socket watches for inbound packets and aborts the
|
|
32
|
+
* process with `"timeout"` if the window lapses; the inherited teardown path closes the socket and clears the watchdog timer as part of its signal-abort listener
|
|
33
|
+
* fan-out. The subclass adds no new public verbs beyond what {@link FfmpegProcess} provides.
|
|
13
34
|
*
|
|
14
35
|
* @example
|
|
15
36
|
*
|
|
16
37
|
* ```ts
|
|
17
|
-
*
|
|
38
|
+
* await using proc = new FfmpegStreamingProcess(ffmpegOptions, {
|
|
18
39
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
40
|
+
* args: commandLineArgs,
|
|
41
|
+
* returnPort: { ipFamily: "ipv4", port: 50000 },
|
|
42
|
+
* signal: session.controller.signal
|
|
43
|
+
* });
|
|
22
44
|
*
|
|
23
|
-
*
|
|
45
|
+
* await proc.ready;
|
|
24
46
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* { addressVersion: "ipv4", port: 5000 }
|
|
30
|
-
* );
|
|
47
|
+
* // Observe the process from the session's own control flow. When the health socket detects a stall, proc.signal fires
|
|
48
|
+
* // with reason "timeout" and proc.exited resolves with the kill-driven exit context. Surface crashes via the owning
|
|
49
|
+
* // session's error path.
|
|
50
|
+
* proc.exited.catch((error) => session.onStreamingError(error));
|
|
31
51
|
* ```
|
|
32
52
|
*
|
|
33
|
-
* @see HomebridgeStreamingDelegate
|
|
34
53
|
* @see FfmpegProcess
|
|
35
54
|
*
|
|
36
55
|
* @category FFmpeg
|
|
37
56
|
*/
|
|
38
57
|
export class FfmpegStreamingProcess extends FfmpegProcess {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
58
|
+
// The return-port descriptor requested at construction (verbatim from `init.returnPort`). Undefined when no return port was configured. Read through the
|
|
59
|
+
// {@link FfmpegStreamingProcess.returnPort} getter, which projects the kernel-assigned port over this descriptor once the bind settles.
|
|
60
|
+
#requestedReturnPort;
|
|
61
|
+
// The port the kernel actually bound the health socket to, captured from `socket.address().port` once the `"listening"` event fires. Undefined until then. For
|
|
62
|
+
// specific-port binds this duplicates {@link #requestedReturnPort}'s port field but the assignment keeps the post-bind read path uniform across both construction
|
|
63
|
+
// modes (specific port and `port: 0` ephemeral).
|
|
64
|
+
#assignedReturnPort;
|
|
43
65
|
/**
|
|
44
|
-
*
|
|
45
|
-
*/
|
|
46
|
-
sessionId;
|
|
47
|
-
/**
|
|
48
|
-
* The timeout reference used to monitor UDP stream health.
|
|
49
|
-
*/
|
|
50
|
-
streamTimeout;
|
|
51
|
-
/**
|
|
52
|
-
* Constructs a new FFmpeg streaming process for a HomeKit session.
|
|
53
|
-
*
|
|
54
|
-
* Sets up required delegate hooks, creates UDP return sockets if needed, and starts the FFmpeg process. Automatically handles FFmpeg process errors and cleans up on
|
|
55
|
-
* failures.
|
|
56
|
-
*
|
|
57
|
-
* @param delegate - The Homebridge streaming delegate for this session.
|
|
58
|
-
* @param sessionId - The HomeKit session identifier for this stream.
|
|
59
|
-
* @param ffmpegOptions - The FFmpeg configuration options.
|
|
60
|
-
* @param commandLineArgs - FFmpeg command-line arguments.
|
|
61
|
-
* @param returnPort - Optional. UDP port info for talkback support (used for two-way audio in HomeKit for cameras that support it).
|
|
62
|
-
* @param callback - Optional. Callback invoked when the stream is ready or errors occur.
|
|
66
|
+
* Construct and spawn a new streaming FFmpeg process.
|
|
63
67
|
*
|
|
64
|
-
*
|
|
68
|
+
* Spawning happens synchronously as part of construction. When `init.returnPort` is supplied, the subclass binds a UDP socket and enforces the liveness watchdog; the
|
|
69
|
+
* socket closes and the watchdog clears as part of the inherited teardown when the signal aborts for any reason.
|
|
65
70
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* ```
|
|
71
|
+
* @param options - Shared {@link FfmpegOptions} configuration (codec support, logger, debug flag, name).
|
|
72
|
+
* @param init - Optional init options. See {@link FfmpegStreamingInit}.
|
|
69
73
|
*/
|
|
70
|
-
constructor(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
this.delegate.ffmpegErrorCheck ??= () => undefined;
|
|
76
|
-
this.delegate.stopStream ??= () => { };
|
|
77
|
-
this.sessionId = sessionId;
|
|
78
|
-
// Create the return port for FFmpeg, if requested to do so. The only time we don't do this is when we're standing up
|
|
79
|
-
// a two-way audio stream - in that case, the audio work is done through RtpSplitter and not here.
|
|
80
|
-
if (returnPort) {
|
|
81
|
-
this.createSocket(returnPort);
|
|
74
|
+
constructor(options, init = {}) {
|
|
75
|
+
super(options, init);
|
|
76
|
+
this.#requestedReturnPort = init.returnPort;
|
|
77
|
+
if (init.returnPort) {
|
|
78
|
+
this.#startHealthMonitor(init.returnPort, init.healthTimeout ?? STREAM_HEALTH_TIMEOUT);
|
|
82
79
|
}
|
|
83
|
-
// Start it up, with appropriate error handling.
|
|
84
|
-
this.start(commandLineArgs, callback, (errorMessage) => {
|
|
85
|
-
// Stop the stream.
|
|
86
|
-
this.delegate.stopStream?.(this.sessionId);
|
|
87
|
-
// Let homebridge know what happened and stop the stream if we've already started.
|
|
88
|
-
if (!this.isStarted && this.callback) {
|
|
89
|
-
this.callback(new Error(errorMessage));
|
|
90
|
-
this.callback = null;
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
// Tell Homebridge to forcibly stop the streaming session.
|
|
94
|
-
this.delegate.controller.forceStopStreamingSession(this.sessionId);
|
|
95
|
-
this.delegate.stopStream?.(this.sessionId);
|
|
96
|
-
});
|
|
97
80
|
}
|
|
98
81
|
/**
|
|
99
|
-
*
|
|
82
|
+
* The UDP return-port descriptor the health socket is bound to, or `undefined` when no return port was configured. For a specific-port construction
|
|
83
|
+
* (`init.returnPort.port` non-zero), this descriptor equals `init.returnPort` from the moment the constructor returns; for an ephemeral construction
|
|
84
|
+
* (`init.returnPort.port === 0`), the `port` field is `0` until the kernel completes the bind, then the kernel-assigned port. Consumers that need the assigned
|
|
85
|
+
* ephemeral port `await proc.ready` before reading it; in practice the health-socket bind completes well before the FFmpeg child reaches the `ready` signal, so a
|
|
86
|
+
* post-`ready` read always observes the kernel's pick.
|
|
100
87
|
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
88
|
+
* Returns a fresh descriptor on every read; callers must treat the result as read-only. The `ipFamily` field is the verbatim value passed at construction; the
|
|
89
|
+
* `port` field reads from the live socket-address projection once captured (specific and ephemeral binds converge on a single read path).
|
|
103
90
|
*
|
|
104
|
-
* @
|
|
91
|
+
* @returns The bound return-port descriptor, or `undefined` when no return port was configured.
|
|
105
92
|
*/
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
93
|
+
get returnPort() {
|
|
94
|
+
if (this.#requestedReturnPort === undefined) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
ipFamily: this.#requestedReturnPort.ipFamily,
|
|
99
|
+
port: this.#assignedReturnPort ?? this.#requestedReturnPort.port
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
// Bind a UDP socket to the return port and enforce a re-armed inactivity watchdog. Called from the constructor when a return-port descriptor is provided. The
|
|
103
|
+
// pre-aborted-signal guard is required: if the caller passed an already-aborted parent signal, the base class's teardown runs synchronously during `super()` and
|
|
104
|
+
// `this.signal` is aborted by the time we get here. Registering an `"abort"` listener on an already-aborted signal does NOT re-dispatch, so the socket and watchdog
|
|
105
|
+
// timer would leak if we proceeded. Short-circuiting leaves no resources allocated.
|
|
106
|
+
#startHealthMonitor(returnPort, timeoutMs) {
|
|
107
|
+
if (this.aborted) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const socket = createDgramSocket(returnPort.ipFamily);
|
|
111
|
+
// Capture the kernel-assigned port as soon as the bind succeeds. For specific-port binds this duplicates `returnPort.port`; for ephemeral binds (port: 0) this
|
|
112
|
+
// is where the kernel's pick becomes observable via {@link returnPort}. Wired as `once("listening", ...)` so it fires exactly when the bind transitions to the
|
|
113
|
+
// listening state - the same moment after which message events can arrive. The handler captures `socket` by closure to avoid `this.#socket`-style reads from
|
|
114
|
+
// an async event handler.
|
|
115
|
+
socket.once("listening", () => {
|
|
116
|
+
this.#assignedReturnPort = socket.address().port;
|
|
118
117
|
});
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
118
|
+
// Inactivity watchdog: if no packets arrive within `timeoutMs`, abort the process with `"timeout"`. Self-cleans when `this.signal` aborts for any other reason,
|
|
119
|
+
// so the teardown listener below only has to close the socket.
|
|
120
|
+
const watchdog = new Watchdog({
|
|
121
|
+
onFire: () => {
|
|
122
|
+
this.log.debug("Streaming process inactivity watchdog fired after %d ms with no inbound packets.", timeoutMs);
|
|
123
|
+
this.abort(new HbpuAbortError("timeout"));
|
|
124
|
+
},
|
|
125
|
+
signal: this.signal,
|
|
126
|
+
timeoutMs
|
|
123
127
|
});
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
// Inbound-packet handler and the SOLE arm site for the watchdog. The first inbound packet performs the initial arm; every subsequent packet re-arms it. We do
|
|
129
|
+
// not inspect the payload - liveness is the only signal this socket cares about.
|
|
130
|
+
socket.on("message", () => { watchdog.arm(); });
|
|
131
|
+
// Socket errors on the health port are treated as fatal - if the kernel cannot deliver us packets, we cannot tell the stream is alive. Abort with `"failed"` and
|
|
132
|
+
// surface the underlying error via `cause` so downstream diagnostics carry the root cause.
|
|
133
|
+
socket.on("error", (error) => {
|
|
134
|
+
this.log.error("Streaming process return-port socket error: %s.", error.message);
|
|
135
|
+
if (!this.aborted) {
|
|
136
|
+
this.abort(new HbpuAbortError("failed", { cause: error }));
|
|
129
137
|
}
|
|
130
|
-
// Set our new canary.
|
|
131
|
-
this.streamTimeout = setTimeout(() => {
|
|
132
|
-
this.log.debug("Video stream appears to be inactive for 5 seconds. Stopping stream.");
|
|
133
|
-
this.delegate.controller.forceStopStreamingSession(this.sessionId);
|
|
134
|
-
this.delegate.stopStream?.(this.sessionId);
|
|
135
|
-
}, FFMPEG_INPUT_TIMEOUT);
|
|
136
138
|
});
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
* ```
|
|
155
|
-
*/
|
|
156
|
-
get ffmpegProcess() {
|
|
157
|
-
return this.process;
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Handle and logs FFmpeg process errors.
|
|
161
|
-
*
|
|
162
|
-
* If a known error condition is detected by the delegate, logs the custom message and returns. For "not enough frames to estimate rate; consider increasing probesize"
|
|
163
|
-
* errors, invokes the delegate's `adjustProbeSize` hook for automatic tuning. Otherwise, falls back to the parent class's logging.
|
|
164
|
-
*
|
|
165
|
-
* @param exitCode - The exit code from FFmpeg.
|
|
166
|
-
* @param signal - The signal, if any, used to terminate the process.
|
|
167
|
-
*/
|
|
168
|
-
logFfmpegError(exitCode, signal) {
|
|
169
|
-
// We want to process known streaming-related errors due to the performance and latency tweaks we've made to the FFmpeg command line. In some cases we may inform the
|
|
170
|
-
// user and take no action, in others, we tune our own internal parameters.
|
|
171
|
-
// Process any specific errors our caller is interested in.
|
|
172
|
-
const errTest = this.delegate.ffmpegErrorCheck?.(this.stderrLog);
|
|
173
|
-
if (errTest) {
|
|
174
|
-
this.log.error(errTest);
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
// Test for probesize errors.
|
|
178
|
-
if (this.stderrLog.some(logEntry => logEntry.includes("not enough frames to estimate rate; consider increasing probesize"))) {
|
|
179
|
-
// Let the streaming delegate know to adjust its parameters for the next run and inform the user.
|
|
180
|
-
this.delegate.adjustProbeSize?.();
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
// Otherwise, revert to our default logging in our parent.
|
|
184
|
-
super.logFfmpegError(exitCode, signal);
|
|
139
|
+
// Teardown convergence point: when `this.signal` aborts for any reason, close the socket. The watchdog's timer is handled by its own self-clean listener on the
|
|
140
|
+
// same signal, so nothing else is owed here. `onAbort` is preferred over bare `addEventListener` even though the early `aborted` short-circuit above already
|
|
141
|
+
// guards the pre-aborted case for this allocation path - resource-class teardown handlers throughout HBPU go through `onAbort` so no constructor site has to
|
|
142
|
+
// re-implement the AbortSignal pre-aborted-listener workaround.
|
|
143
|
+
onAbort(this.signal, () => socket.close());
|
|
144
|
+
// Bind. Binding may fail asynchronously with an `"error"` event, which the listener above catches.
|
|
145
|
+
socket.bind(returnPort.port, loopbackAddress(returnPort.ipFamily));
|
|
146
|
+
// This is an inactivity watchdog, and "inactivity" presupposes prior activity: it watches one thing - a *flowing* return stream that dried up - so it arms only
|
|
147
|
+
// on the first inbound packet (the message handler above), then re-arms on every subsequent one. There is deliberately NO arm at construction or on `ready`,
|
|
148
|
+
// because the watchdog must not police startup or establishment. Those are separate concerns owned elsewhere: "did the child process start" is the base-class
|
|
149
|
+
// optional `startupTimeout` (documented in process.ts), and "did the return stream ever go live" is the consumer's - the HomeKit session lifecycle and the
|
|
150
|
+
// consumer's establishment gate - which is the only thing that bounds a stream that never delivers a first packet. We do not fold that bound in here, and on the
|
|
151
|
+
// one-way live-view path that bound stays external to this class, owned by the consumer rather than this watchdog. Arming on `ready` was wrong precisely because a
|
|
152
|
+
// ready-armed clock starts before any media has round-tripped: on a cold start the first return packet cannot arrive within the window (prime, transcode, SRTP
|
|
153
|
+
// egress, client decode, client RTCP back all have to complete first), so the clock counts down through that legitimate first-packet latency and false-positives
|
|
154
|
+
// on a perfectly healthy stream. Arming on the packet itself is immune by construction. Note the message handler arms regardless of `ready`: any datagram that
|
|
155
|
+
// arrives before stderr does still starts the inactivity clock, so a stream that is already alive never waits on the spawn signal to be protected.
|
|
185
156
|
}
|
|
186
157
|
}
|
|
187
158
|
//# sourceMappingURL=stream.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../../src/ffmpeg/stream.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../../src/ffmpeg/stream.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format a bitrate value into a human-readable form as bps, kbps, or Mbps.
|
|
3
|
+
*
|
|
4
|
+
* @param value - The bitrate value to convert, in bits per second.
|
|
5
|
+
*
|
|
6
|
+
* @returns Returns the value as a human-readable string.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* formatBps(500); // "500 bps".
|
|
12
|
+
* formatBps(2000); // "2 kbps".
|
|
13
|
+
* formatBps(15000); // "15 kbps".
|
|
14
|
+
* formatBps(2560); // "2.6 kbps".
|
|
15
|
+
* formatBps(1000000); // "1 Mbps".
|
|
16
|
+
* formatBps(2560000); // "2.6 Mbps".
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @category Utilities
|
|
20
|
+
*/
|
|
21
|
+
export declare function formatBps(value: number): string;
|
|
22
|
+
/**
|
|
23
|
+
* Format a byte count into a human-readable form as bytes, KB, MB, GB, or TB. Uses 1024-based thresholds matching the convention every operating system uses for
|
|
24
|
+
* displaying file and buffer sizes.
|
|
25
|
+
*
|
|
26
|
+
* @param value - The byte count to convert.
|
|
27
|
+
*
|
|
28
|
+
* @returns Returns the value as a human-readable string.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
*
|
|
32
|
+
* ```ts
|
|
33
|
+
* formatBytes(512); // "512 bytes".
|
|
34
|
+
* formatBytes(2048); // "2 KB".
|
|
35
|
+
* formatBytes(1536); // "1.5 KB".
|
|
36
|
+
* formatBytes(1_048_576); // "1 MB".
|
|
37
|
+
* formatBytes(2_621_440); // "2.5 MB".
|
|
38
|
+
* formatBytes(1_073_741_824); // "1 GB".
|
|
39
|
+
* formatBytes(1_099_511_627_776); // "1 TB".
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @category Utilities
|
|
43
|
+
*/
|
|
44
|
+
export declare function formatBytes(value: number): string;
|
|
45
|
+
/**
|
|
46
|
+
* Format a millisecond duration into a human-readable form as ms, s, min, or hr. Tiered thresholds match how operators naturally read elapsed time: sub-second
|
|
47
|
+
* values stay in milliseconds for precision, longer durations promote to seconds, minutes, and hours.
|
|
48
|
+
*
|
|
49
|
+
* @param value - The duration to convert, in milliseconds.
|
|
50
|
+
*
|
|
51
|
+
* @returns Returns the value as a human-readable string.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
*
|
|
55
|
+
* ```ts
|
|
56
|
+
* formatMs(250); // "250 ms".
|
|
57
|
+
* formatMs(1500); // "1.5 s".
|
|
58
|
+
* formatMs(15000); // "15 s".
|
|
59
|
+
* formatMs(90000); // "1.5 min".
|
|
60
|
+
* formatMs(5_400_000); // "1.5 hr".
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* @category Utilities
|
|
64
|
+
*/
|
|
65
|
+
export declare function formatMs(value: number): string;
|
|
66
|
+
/**
|
|
67
|
+
* Format a numeric percentage value into a human-readable form with a trailing percent sign. Applies the same precision policy as the magnitude-based formatters
|
|
68
|
+
* via the shared internal helper: whole numbers render without a trailing decimal, fractional numbers render to one decimal place.
|
|
69
|
+
*
|
|
70
|
+
* @param value - The percentage value to convert. Treated as already-scaled into percent units (50 means 50%, not 0.5).
|
|
71
|
+
*
|
|
72
|
+
* @returns Returns the value as a human-readable string ending in `%`.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
*
|
|
76
|
+
* ```ts
|
|
77
|
+
* formatPercent(0); // "0%".
|
|
78
|
+
* formatPercent(50); // "50%".
|
|
79
|
+
* formatPercent(100); // "100%".
|
|
80
|
+
* formatPercent(33.333); // "33.3%".
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* @category Utilities
|
|
84
|
+
*/
|
|
85
|
+
export declare function formatPercent(value: number): string;
|
|
86
|
+
/**
|
|
87
|
+
* Format a second-resolution duration into a human-readable form as s, min, or hr. Same tier semantics as {@link formatMs}, scaled for inputs that arrive already
|
|
88
|
+
* in seconds rather than milliseconds.
|
|
89
|
+
*
|
|
90
|
+
* @param value - The duration to convert, in seconds.
|
|
91
|
+
*
|
|
92
|
+
* @returns Returns the value as a human-readable string.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
*
|
|
96
|
+
* ```ts
|
|
97
|
+
* formatSeconds(45); // "45 s".
|
|
98
|
+
* formatSeconds(90); // "1.5 min".
|
|
99
|
+
* formatSeconds(1800); // "30 min".
|
|
100
|
+
* formatSeconds(5400); // "1.5 hr".
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* @category Utilities
|
|
104
|
+
*/
|
|
105
|
+
export declare function formatSeconds(value: number): string;
|
|
106
|
+
//# sourceMappingURL=formatters.d.ts.map
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/* Copyright(C) 2017-2026, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
|
+
*
|
|
3
|
+
* formatters.ts: Browser-safe magnitude and percentage formatters shared between the server-side `util.ts` surface and the browser-shipped `featureOptions.ts`
|
|
4
|
+
* model.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* **Why this file exists.** `featureOptions.ts` ships into `dist/ui/` for the browser to load (via the `copyFeatureOptions` build step). The catalog's built-in
|
|
8
|
+
* formatter registry needs `formatBps`, `formatBytes`, `formatMs`, `formatPercent`, and `formatSeconds` at runtime - and pulling them from `util.ts` would drag in
|
|
9
|
+
* `util.ts`'s `node:timers/promises` import, which the browser cannot resolve. This module is the SSOT for the magnitude-rendering policy. It has zero runtime
|
|
10
|
+
* imports of any kind, so shipping it alongside `featureOptions.js` is safe in any runtime that can execute ES2024+ JavaScript.
|
|
11
|
+
*
|
|
12
|
+
* **Precision policy.** Whole numbers render without a trailing decimal place ("5" not "5.0"); fractional numbers render to one decimal place. Centralizing the
|
|
13
|
+
* precision policy in `formatMagnitude` means tightening it later - more precision, a thousands separator, locale-aware formatting - is a single-line change
|
|
14
|
+
* rather than a sweep across every format helper.
|
|
15
|
+
*
|
|
16
|
+
* **Consumers.** `util.ts` re-exports these for the server-side surface; `featureOptions.ts` imports directly from here to keep its browser-runnable dependency
|
|
17
|
+
* graph free of `util.ts`. Both consumers share one implementation - the file is the join point.
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
21
|
+
// Shared magnitude-rendering helper used by every magnitude-based formatter, applying the module's precision policy (see the module doc): whole numbers render with
|
|
22
|
+
// no trailing decimal place, fractional numbers to one decimal place.
|
|
23
|
+
function formatMagnitude(value) {
|
|
24
|
+
return ((value % 1) === 0 ? value.toFixed(0) : value.toFixed(1));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Format a bitrate value into a human-readable form as bps, kbps, or Mbps.
|
|
28
|
+
*
|
|
29
|
+
* @param value - The bitrate value to convert, in bits per second.
|
|
30
|
+
*
|
|
31
|
+
* @returns Returns the value as a human-readable string.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
*
|
|
35
|
+
* ```ts
|
|
36
|
+
* formatBps(500); // "500 bps".
|
|
37
|
+
* formatBps(2000); // "2 kbps".
|
|
38
|
+
* formatBps(15000); // "15 kbps".
|
|
39
|
+
* formatBps(2560); // "2.6 kbps".
|
|
40
|
+
* formatBps(1000000); // "1 Mbps".
|
|
41
|
+
* formatBps(2560000); // "2.6 Mbps".
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @category Utilities
|
|
45
|
+
*/
|
|
46
|
+
export function formatBps(value) {
|
|
47
|
+
if (value < 1_000) {
|
|
48
|
+
return value.toString() + " bps";
|
|
49
|
+
}
|
|
50
|
+
if (value < 1_000_000) {
|
|
51
|
+
return formatMagnitude(value / 1_000) + " kbps";
|
|
52
|
+
}
|
|
53
|
+
return formatMagnitude(value / 1_000_000) + " Mbps";
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Format a byte count into a human-readable form as bytes, KB, MB, GB, or TB. Uses 1024-based thresholds matching the convention every operating system uses for
|
|
57
|
+
* displaying file and buffer sizes.
|
|
58
|
+
*
|
|
59
|
+
* @param value - The byte count to convert.
|
|
60
|
+
*
|
|
61
|
+
* @returns Returns the value as a human-readable string.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
*
|
|
65
|
+
* ```ts
|
|
66
|
+
* formatBytes(512); // "512 bytes".
|
|
67
|
+
* formatBytes(2048); // "2 KB".
|
|
68
|
+
* formatBytes(1536); // "1.5 KB".
|
|
69
|
+
* formatBytes(1_048_576); // "1 MB".
|
|
70
|
+
* formatBytes(2_621_440); // "2.5 MB".
|
|
71
|
+
* formatBytes(1_073_741_824); // "1 GB".
|
|
72
|
+
* formatBytes(1_099_511_627_776); // "1 TB".
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* @category Utilities
|
|
76
|
+
*/
|
|
77
|
+
export function formatBytes(value) {
|
|
78
|
+
if (value < 1_024) {
|
|
79
|
+
return value.toString() + " bytes";
|
|
80
|
+
}
|
|
81
|
+
if (value < 1_048_576) {
|
|
82
|
+
return formatMagnitude(value / 1_024) + " KB";
|
|
83
|
+
}
|
|
84
|
+
if (value < 1_073_741_824) {
|
|
85
|
+
return formatMagnitude(value / 1_048_576) + " MB";
|
|
86
|
+
}
|
|
87
|
+
if (value < 1_099_511_627_776) {
|
|
88
|
+
return formatMagnitude(value / 1_073_741_824) + " GB";
|
|
89
|
+
}
|
|
90
|
+
return formatMagnitude(value / 1_099_511_627_776) + " TB";
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Format a millisecond duration into a human-readable form as ms, s, min, or hr. Tiered thresholds match how operators naturally read elapsed time: sub-second
|
|
94
|
+
* values stay in milliseconds for precision, longer durations promote to seconds, minutes, and hours.
|
|
95
|
+
*
|
|
96
|
+
* @param value - The duration to convert, in milliseconds.
|
|
97
|
+
*
|
|
98
|
+
* @returns Returns the value as a human-readable string.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
*
|
|
102
|
+
* ```ts
|
|
103
|
+
* formatMs(250); // "250 ms".
|
|
104
|
+
* formatMs(1500); // "1.5 s".
|
|
105
|
+
* formatMs(15000); // "15 s".
|
|
106
|
+
* formatMs(90000); // "1.5 min".
|
|
107
|
+
* formatMs(5_400_000); // "1.5 hr".
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* @category Utilities
|
|
111
|
+
*/
|
|
112
|
+
export function formatMs(value) {
|
|
113
|
+
if (value < 1_000) {
|
|
114
|
+
return value.toString() + " ms";
|
|
115
|
+
}
|
|
116
|
+
if (value < 60_000) {
|
|
117
|
+
return formatMagnitude(value / 1_000) + " s";
|
|
118
|
+
}
|
|
119
|
+
if (value < 3_600_000) {
|
|
120
|
+
return formatMagnitude(value / 60_000) + " min";
|
|
121
|
+
}
|
|
122
|
+
return formatMagnitude(value / 3_600_000) + " hr";
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Format a numeric percentage value into a human-readable form with a trailing percent sign. Applies the same precision policy as the magnitude-based formatters
|
|
126
|
+
* via the shared internal helper: whole numbers render without a trailing decimal, fractional numbers render to one decimal place.
|
|
127
|
+
*
|
|
128
|
+
* @param value - The percentage value to convert. Treated as already-scaled into percent units (50 means 50%, not 0.5).
|
|
129
|
+
*
|
|
130
|
+
* @returns Returns the value as a human-readable string ending in `%`.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
*
|
|
134
|
+
* ```ts
|
|
135
|
+
* formatPercent(0); // "0%".
|
|
136
|
+
* formatPercent(50); // "50%".
|
|
137
|
+
* formatPercent(100); // "100%".
|
|
138
|
+
* formatPercent(33.333); // "33.3%".
|
|
139
|
+
* ```
|
|
140
|
+
*
|
|
141
|
+
* @category Utilities
|
|
142
|
+
*/
|
|
143
|
+
export function formatPercent(value) {
|
|
144
|
+
return formatMagnitude(value) + "%";
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Format a second-resolution duration into a human-readable form as s, min, or hr. Same tier semantics as {@link formatMs}, scaled for inputs that arrive already
|
|
148
|
+
* in seconds rather than milliseconds.
|
|
149
|
+
*
|
|
150
|
+
* @param value - The duration to convert, in seconds.
|
|
151
|
+
*
|
|
152
|
+
* @returns Returns the value as a human-readable string.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
*
|
|
156
|
+
* ```ts
|
|
157
|
+
* formatSeconds(45); // "45 s".
|
|
158
|
+
* formatSeconds(90); // "1.5 min".
|
|
159
|
+
* formatSeconds(1800); // "30 min".
|
|
160
|
+
* formatSeconds(5400); // "1.5 hr".
|
|
161
|
+
* ```
|
|
162
|
+
*
|
|
163
|
+
* @category Utilities
|
|
164
|
+
*/
|
|
165
|
+
export function formatSeconds(value) {
|
|
166
|
+
if (value < 60) {
|
|
167
|
+
return value.toString() + " s";
|
|
168
|
+
}
|
|
169
|
+
if (value < 3_600) {
|
|
170
|
+
return formatMagnitude(value / 60) + " min";
|
|
171
|
+
}
|
|
172
|
+
return formatMagnitude(value / 3_600) + " hr";
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=formatters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatters.js","sourceRoot":"","sources":["../src/formatters.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;;;;;;GAcG;AAEH,oKAAoK;AACpK,sEAAsE;AACtE,SAAS,eAAe,CAAC,KAAa;IAEpC,OAAO,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;AACnE,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,SAAS,CAAC,KAAa;IAErC,IAAG,KAAK,GAAG,KAAK,EAAE,CAAC;QAEjB,OAAO,KAAK,CAAC,QAAQ,EAAE,GAAG,MAAM,CAAC;IACnC,CAAC;IAED,IAAG,KAAK,GAAG,SAAS,EAAE,CAAC;QAErB,OAAO,eAAe,CAAC,KAAK,GAAG,KAAK,CAAC,GAAG,OAAO,CAAC;IAClD,CAAC;IAED,OAAO,eAAe,CAAC,KAAK,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC;AACtD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa;IAEvC,IAAG,KAAK,GAAG,KAAK,EAAE,CAAC;QAEjB,OAAO,KAAK,CAAC,QAAQ,EAAE,GAAG,QAAQ,CAAC;IACrC,CAAC;IAED,IAAG,KAAK,GAAG,SAAS,EAAE,CAAC;QAErB,OAAO,eAAe,CAAC,KAAK,GAAG,KAAK,CAAC,GAAG,KAAK,CAAC;IAChD,CAAC;IAED,IAAG,KAAK,GAAG,aAAa,EAAE,CAAC;QAEzB,OAAO,eAAe,CAAC,KAAK,GAAG,SAAS,CAAC,GAAG,KAAK,CAAC;IACpD,CAAC;IAED,IAAG,KAAK,GAAG,iBAAiB,EAAE,CAAC;QAE7B,OAAO,eAAe,CAAC,KAAK,GAAG,aAAa,CAAC,GAAG,KAAK,CAAC;IACxD,CAAC;IAED,OAAO,eAAe,CAAC,KAAK,GAAG,iBAAiB,CAAC,GAAG,KAAK,CAAC;AAC5D,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAa;IAEpC,IAAG,KAAK,GAAG,KAAK,EAAE,CAAC;QAEjB,OAAO,KAAK,CAAC,QAAQ,EAAE,GAAG,KAAK,CAAC;IAClC,CAAC;IAED,IAAG,KAAK,GAAG,MAAM,EAAE,CAAC;QAElB,OAAO,eAAe,CAAC,KAAK,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC;IAC/C,CAAC;IAED,IAAG,KAAK,GAAG,SAAS,EAAE,CAAC;QAErB,OAAO,eAAe,CAAC,KAAK,GAAG,MAAM,CAAC,GAAG,MAAM,CAAC;IAClD,CAAC;IAED,OAAO,eAAe,CAAC,KAAK,GAAG,SAAS,CAAC,GAAG,KAAK,CAAC;AACpD,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,aAAa,CAAC,KAAa;IAEzC,OAAO,eAAe,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC;AACtC,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,aAAa,CAAC,KAAa;IAEzC,IAAG,KAAK,GAAG,EAAE,EAAE,CAAC;QAEd,OAAO,KAAK,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC;IACjC,CAAC;IAED,IAAG,KAAK,GAAG,KAAK,EAAE,CAAC;QAEjB,OAAO,eAAe,CAAC,KAAK,GAAG,EAAE,CAAC,GAAG,MAAM,CAAC;IAC9C,CAAC;IAED,OAAO,eAAe,CAAC,KAAK,GAAG,KAAK,CAAC,GAAG,KAAK,CAAC;AAChD,CAAC"}
|