@camstack/addon-provider-reolink 1.2.155 → 1.2.159

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.
Files changed (3) hide show
  1. package/dist/addon.js +1518 -13
  2. package/dist/addon.mjs +1518 -13
  3. package/package.json +2 -2
package/dist/addon.js CHANGED
@@ -5390,7 +5390,7 @@ var ZodIssueCode = {
5390
5390
  var ZodFirstPartyTypeKind;
5391
5391
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5392
5392
  //#endregion
5393
- //#region ../types/dist/sleep-BVhJDJka.mjs
5393
+ //#region ../types/dist/sleep-CUmLTud7.mjs
5394
5394
  /**
5395
5395
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5396
5396
  * window to float samples (D455).
@@ -25639,6 +25639,23 @@ var videoclipsCapability = {
25639
25639
  deviceId: number(),
25640
25640
  clipId: string().min(1),
25641
25641
  /**
25642
+ * WHICH provider holds the bytes — the `addonId` a
25643
+ * {@link ClipSourceSchema} row carries. **Required**, for the reason
25644
+ * `listClips` states one method above and for a second one this method
25645
+ * learned the hard way.
25646
+ *
25647
+ * The caller is the stream broker, and the broker lives in
25648
+ * `addon-pipeline` — the same addon that registers the RECORDER's
25649
+ * videoclips provider. With no provider named, the in-process
25650
+ * resolution handed it its own sibling, which implements this method
25651
+ * solely to explain that an analytics visit has no file behind it. So
25652
+ * every onboard clip answered with that sentence, a flash of video and
25653
+ * nothing more, while the SAME call from outside the addon reached the
25654
+ * right provider and returned the bytes (measured on 3628,
25655
+ * 2026-09-21).
25656
+ */
25657
+ provider: string().min(1),
25658
+ /**
25642
25659
  * Which twin to fetch, on the mapping D549 15 already fixed:
25643
25660
  * `low | mid` → the sub file, `high` → the main twin. `auto` is not
25644
25661
  * accepted here for the same reason it is not accepted by
@@ -33385,7 +33402,7 @@ method(object({
33385
33402
  auth: "admin"
33386
33403
  }), method(object({ deviceId: number() }), RecordingConfigSchema, {
33387
33404
  kind: "query",
33388
- auth: "admin"
33405
+ auth: "protected"
33389
33406
  }), method(object({
33390
33407
  deviceId: number(),
33391
33408
  profile: string(),
@@ -38153,6 +38170,12 @@ function createRuntimeStateBridge(params) {
38153
38170
  };
38154
38171
  }
38155
38172
  Object.freeze({
38173
+ "accessories.getStatus": {
38174
+ capName: "accessories",
38175
+ capScope: "device",
38176
+ addonId: null,
38177
+ access: "view"
38178
+ },
38156
38179
  "accessories.setChildHidden": {
38157
38180
  capName: "accessories",
38158
38181
  capScope: "device",
@@ -38435,6 +38458,12 @@ Object.freeze({
38435
38458
  addonId: null,
38436
38459
  access: "view"
38437
38460
  },
38461
+ "airQualitySensor.getStatus": {
38462
+ capName: "air-quality-sensor",
38463
+ capScope: "device",
38464
+ addonId: null,
38465
+ access: "view"
38466
+ },
38438
38467
  "alarmPanel.arm": {
38439
38468
  capName: "alarm-panel",
38440
38469
  capScope: "device",
@@ -38447,6 +38476,12 @@ Object.freeze({
38447
38476
  addonId: null,
38448
38477
  access: "create"
38449
38478
  },
38479
+ "alarmPanel.getStatus": {
38480
+ capName: "alarm-panel",
38481
+ capScope: "device",
38482
+ addonId: null,
38483
+ access: "view"
38484
+ },
38450
38485
  "alarmPanel.trigger": {
38451
38486
  capName: "alarm-panel",
38452
38487
  capScope: "device",
@@ -38495,6 +38530,30 @@ Object.freeze({
38495
38530
  addonId: null,
38496
38531
  access: "create"
38497
38532
  },
38533
+ "ambientLightSensor.getStatus": {
38534
+ capName: "ambient-light-sensor",
38535
+ capScope: "device",
38536
+ addonId: null,
38537
+ access: "view"
38538
+ },
38539
+ "audioAnalysis.applyDeviceSettingsPatch": {
38540
+ capName: "audio-analysis",
38541
+ capScope: "device",
38542
+ addonId: null,
38543
+ access: "create"
38544
+ },
38545
+ "audioAnalysis.getDeviceLiveContribution": {
38546
+ capName: "audio-analysis",
38547
+ capScope: "device",
38548
+ addonId: null,
38549
+ access: "view"
38550
+ },
38551
+ "audioAnalysis.getDeviceSettingsContribution": {
38552
+ capName: "audio-analysis",
38553
+ capScope: "device",
38554
+ addonId: null,
38555
+ access: "view"
38556
+ },
38498
38557
  "audioAnalysis.resolveDeviceSettings": {
38499
38558
  capName: "audio-analysis",
38500
38559
  capScope: "device",
@@ -38657,6 +38716,12 @@ Object.freeze({
38657
38716
  addonId: null,
38658
38717
  access: "create"
38659
38718
  },
38719
+ "automationControl.getStatus": {
38720
+ capName: "automation-control",
38721
+ capScope: "device",
38722
+ addonId: null,
38723
+ access: "view"
38724
+ },
38660
38725
  "automationControl.trigger": {
38661
38726
  capName: "automation-control",
38662
38727
  capScope: "device",
@@ -38753,12 +38818,30 @@ Object.freeze({
38753
38818
  addonId: null,
38754
38819
  access: "create"
38755
38820
  },
38821
+ "battery.getStatus": {
38822
+ capName: "battery",
38823
+ capScope: "device",
38824
+ addonId: null,
38825
+ access: "view"
38826
+ },
38756
38827
  "battery.wakeForStream": {
38757
38828
  capName: "battery",
38758
38829
  capScope: "device",
38759
38830
  addonId: null,
38760
38831
  access: "create"
38761
38832
  },
38833
+ "binary.getStatus": {
38834
+ capName: "binary",
38835
+ capScope: "device",
38836
+ addonId: null,
38837
+ access: "view"
38838
+ },
38839
+ "brightness.getStatus": {
38840
+ capName: "brightness",
38841
+ capScope: "device",
38842
+ addonId: null,
38843
+ access: "view"
38844
+ },
38762
38845
  "brightness.setBrightness": {
38763
38846
  capName: "brightness",
38764
38847
  capScope: "device",
@@ -38873,6 +38956,30 @@ Object.freeze({
38873
38956
  addonId: null,
38874
38957
  access: "view"
38875
38958
  },
38959
+ "cameraCredentials.getStatus": {
38960
+ capName: "camera-credentials",
38961
+ capScope: "device",
38962
+ addonId: null,
38963
+ access: "view"
38964
+ },
38965
+ "cameraGridLayout.applyDeviceSettingsPatch": {
38966
+ capName: "camera-grid-layout",
38967
+ capScope: "device",
38968
+ addonId: null,
38969
+ access: "create"
38970
+ },
38971
+ "cameraGridLayout.getDeviceLiveContribution": {
38972
+ capName: "camera-grid-layout",
38973
+ capScope: "device",
38974
+ addonId: null,
38975
+ access: "view"
38976
+ },
38977
+ "cameraGridLayout.getDeviceSettingsContribution": {
38978
+ capName: "camera-grid-layout",
38979
+ capScope: "device",
38980
+ addonId: null,
38981
+ access: "view"
38982
+ },
38876
38983
  "cameraGridLayout.getLayout": {
38877
38984
  capName: "camera-grid-layout",
38878
38985
  capScope: "device",
@@ -38885,6 +38992,24 @@ Object.freeze({
38885
38992
  addonId: null,
38886
38993
  access: "create"
38887
38994
  },
38995
+ "cameraPipelineConfig.applyDeviceSettingsPatch": {
38996
+ capName: "camera-pipeline-config",
38997
+ capScope: "device",
38998
+ addonId: null,
38999
+ access: "create"
39000
+ },
39001
+ "cameraPipelineConfig.getDeviceLiveContribution": {
39002
+ capName: "camera-pipeline-config",
39003
+ capScope: "device",
39004
+ addonId: null,
39005
+ access: "view"
39006
+ },
39007
+ "cameraPipelineConfig.getDeviceSettingsContribution": {
39008
+ capName: "camera-pipeline-config",
39009
+ capScope: "device",
39010
+ addonId: null,
39011
+ access: "view"
39012
+ },
38888
39013
  "cameraStreams.getBrokerStreams": {
38889
39014
  capName: "camera-streams",
38890
39015
  capScope: "device",
@@ -38915,6 +39040,18 @@ Object.freeze({
38915
39040
  addonId: null,
38916
39041
  access: "view"
38917
39042
  },
39043
+ "carbonMonoxide.getStatus": {
39044
+ capName: "carbon-monoxide",
39045
+ capScope: "device",
39046
+ addonId: null,
39047
+ access: "view"
39048
+ },
39049
+ "climateControl.getStatus": {
39050
+ capName: "climate-control",
39051
+ capScope: "device",
39052
+ addonId: null,
39053
+ access: "view"
39054
+ },
38918
39055
  "climateControl.setFanMode": {
38919
39056
  capName: "climate-control",
38920
39057
  capScope: "device",
@@ -38963,6 +39100,12 @@ Object.freeze({
38963
39100
  addonId: null,
38964
39101
  access: "create"
38965
39102
  },
39103
+ "color.getStatus": {
39104
+ capName: "color",
39105
+ capScope: "device",
39106
+ addonId: null,
39107
+ access: "view"
39108
+ },
38966
39109
  "color.setColor": {
38967
39110
  capName: "color",
38968
39111
  capScope: "device",
@@ -38981,12 +39124,36 @@ Object.freeze({
38981
39124
  addonId: null,
38982
39125
  access: "create"
38983
39126
  },
39127
+ "connectivity.getStatus": {
39128
+ capName: "connectivity",
39129
+ capScope: "device",
39130
+ addonId: null,
39131
+ access: "view"
39132
+ },
39133
+ "consumables.getStatus": {
39134
+ capName: "consumables",
39135
+ capScope: "device",
39136
+ addonId: null,
39137
+ access: "view"
39138
+ },
38984
39139
  "consumables.reset": {
38985
39140
  capName: "consumables",
38986
39141
  capScope: "device",
38987
39142
  addonId: null,
38988
39143
  access: "delete"
38989
39144
  },
39145
+ "contact.getStatus": {
39146
+ capName: "contact",
39147
+ capScope: "device",
39148
+ addonId: null,
39149
+ access: "view"
39150
+ },
39151
+ "control.getStatus": {
39152
+ capName: "control",
39153
+ capScope: "device",
39154
+ addonId: null,
39155
+ access: "view"
39156
+ },
38990
39157
  "control.setValue": {
38991
39158
  capName: "control",
38992
39159
  capScope: "device",
@@ -39053,6 +39220,12 @@ Object.freeze({
39053
39220
  addonId: null,
39054
39221
  access: "create"
39055
39222
  },
39223
+ "cover.getStatus": {
39224
+ capName: "cover",
39225
+ capScope: "device",
39226
+ addonId: null,
39227
+ access: "view"
39228
+ },
39056
39229
  "cover.open": {
39057
39230
  capName: "cover",
39058
39231
  capScope: "device",
@@ -39185,6 +39358,12 @@ Object.freeze({
39185
39358
  addonId: null,
39186
39359
  access: "view"
39187
39360
  },
39361
+ "dayNight.getStatus": {
39362
+ capName: "day-night",
39363
+ capScope: "device",
39364
+ addonId: null,
39365
+ access: "view"
39366
+ },
39188
39367
  "dayNight.setSettings": {
39189
39368
  capName: "day-night",
39190
39369
  capScope: "device",
@@ -39275,6 +39454,24 @@ Object.freeze({
39275
39454
  addonId: null,
39276
39455
  access: "view"
39277
39456
  },
39457
+ "detectionPipeline.applyDeviceSettingsPatch": {
39458
+ capName: "detection-pipeline",
39459
+ capScope: "device",
39460
+ addonId: null,
39461
+ access: "create"
39462
+ },
39463
+ "detectionPipeline.getDeviceLiveContribution": {
39464
+ capName: "detection-pipeline",
39465
+ capScope: "device",
39466
+ addonId: null,
39467
+ access: "view"
39468
+ },
39469
+ "detectionPipeline.getDeviceSettingsContribution": {
39470
+ capName: "detection-pipeline",
39471
+ capScope: "device",
39472
+ addonId: null,
39473
+ access: "view"
39474
+ },
39278
39475
  "deviceAdoption.adopt": {
39279
39476
  capName: "device-adoption",
39280
39477
  capScope: "system",
@@ -39287,6 +39484,12 @@ Object.freeze({
39287
39484
  addonId: null,
39288
39485
  access: "view"
39289
39486
  },
39487
+ "deviceAdoption.getStatus": {
39488
+ capName: "device-adoption",
39489
+ capScope: "system",
39490
+ addonId: null,
39491
+ access: "view"
39492
+ },
39290
39493
  "deviceAdoption.listCandidateFilters": {
39291
39494
  capName: "device-adoption",
39292
39495
  capScope: "system",
@@ -39323,6 +39526,12 @@ Object.freeze({
39323
39526
  addonId: null,
39324
39527
  access: "create"
39325
39528
  },
39529
+ "deviceDiscovery.getStatus": {
39530
+ capName: "device-discovery",
39531
+ capScope: "device",
39532
+ addonId: null,
39533
+ access: "view"
39534
+ },
39326
39535
  "deviceDiscovery.listDiscovered": {
39327
39536
  capName: "device-discovery",
39328
39537
  capScope: "device",
@@ -39341,12 +39550,30 @@ Object.freeze({
39341
39550
  addonId: null,
39342
39551
  access: "create"
39343
39552
  },
39553
+ "deviceExport.applyDeviceSettingsPatch": {
39554
+ capName: "device-export",
39555
+ capScope: "system",
39556
+ addonId: null,
39557
+ access: "create"
39558
+ },
39344
39559
  "deviceExport.exposeDevice": {
39345
39560
  capName: "device-export",
39346
39561
  capScope: "system",
39347
39562
  addonId: null,
39348
39563
  access: "create"
39349
39564
  },
39565
+ "deviceExport.getDeviceLiveContribution": {
39566
+ capName: "device-export",
39567
+ capScope: "system",
39568
+ addonId: null,
39569
+ access: "view"
39570
+ },
39571
+ "deviceExport.getDeviceSettingsContribution": {
39572
+ capName: "device-export",
39573
+ capScope: "system",
39574
+ addonId: null,
39575
+ access: "view"
39576
+ },
39350
39577
  "deviceExport.getStatus": {
39351
39578
  capName: "device-export",
39352
39579
  capScope: "system",
@@ -39443,6 +39670,12 @@ Object.freeze({
39443
39670
  addonId: null,
39444
39671
  access: "create"
39445
39672
  },
39673
+ "deviceManager.applyDeviceSettingsPatch": {
39674
+ capName: "device-manager",
39675
+ capScope: "system",
39676
+ addonId: null,
39677
+ access: "create"
39678
+ },
39446
39679
  "deviceManager.applyInitialMeta": {
39447
39680
  capName: "device-manager",
39448
39681
  capScope: "system",
@@ -39545,6 +39778,12 @@ Object.freeze({
39545
39778
  addonId: null,
39546
39779
  access: "view"
39547
39780
  },
39781
+ "deviceManager.getDeviceLiveContribution": {
39782
+ capName: "device-manager",
39783
+ capScope: "system",
39784
+ addonId: null,
39785
+ access: "view"
39786
+ },
39548
39787
  "deviceManager.getDeviceLiveInfoAggregate": {
39549
39788
  capName: "device-manager",
39550
39789
  capScope: "system",
@@ -39557,6 +39796,12 @@ Object.freeze({
39557
39796
  addonId: null,
39558
39797
  access: "view"
39559
39798
  },
39799
+ "deviceManager.getDeviceSettingsContribution": {
39800
+ capName: "device-manager",
39801
+ capScope: "system",
39802
+ addonId: null,
39803
+ access: "view"
39804
+ },
39560
39805
  "deviceManager.getDeviceStatusAggregate": {
39561
39806
  capName: "device-manager",
39562
39807
  capScope: "system",
@@ -40049,6 +40294,36 @@ Object.freeze({
40049
40294
  addonId: null,
40050
40295
  access: "create"
40051
40296
  },
40297
+ "deviceStatus.getStatus": {
40298
+ capName: "device-status",
40299
+ capScope: "device",
40300
+ addonId: null,
40301
+ access: "view"
40302
+ },
40303
+ "doorbell.applyDeviceSettingsPatch": {
40304
+ capName: "doorbell",
40305
+ capScope: "device",
40306
+ addonId: null,
40307
+ access: "create"
40308
+ },
40309
+ "doorbell.getDeviceLiveContribution": {
40310
+ capName: "doorbell",
40311
+ capScope: "device",
40312
+ addonId: null,
40313
+ access: "view"
40314
+ },
40315
+ "doorbell.getDeviceSettingsContribution": {
40316
+ capName: "doorbell",
40317
+ capScope: "device",
40318
+ addonId: null,
40319
+ access: "view"
40320
+ },
40321
+ "doorbell.getStatus": {
40322
+ capName: "doorbell",
40323
+ capScope: "device",
40324
+ addonId: null,
40325
+ access: "view"
40326
+ },
40052
40327
  "embeddingEncoder.encode": {
40053
40328
  capName: "embedding-encoder",
40054
40329
  capScope: "system",
@@ -40067,6 +40342,18 @@ Object.freeze({
40067
40342
  addonId: null,
40068
40343
  access: "view"
40069
40344
  },
40345
+ "enumSensor.getStatus": {
40346
+ capName: "enum-sensor",
40347
+ capScope: "device",
40348
+ addonId: null,
40349
+ access: "view"
40350
+ },
40351
+ "eventEmitter.getStatus": {
40352
+ capName: "event-emitter",
40353
+ capScope: "device",
40354
+ addonId: null,
40355
+ access: "view"
40356
+ },
40070
40357
  "events.getEventClipUrl": {
40071
40358
  capName: "events",
40072
40359
  capScope: "device",
@@ -40187,6 +40474,12 @@ Object.freeze({
40187
40474
  addonId: null,
40188
40475
  access: "view"
40189
40476
  },
40477
+ "fanControl.getStatus": {
40478
+ capName: "fan-control",
40479
+ capScope: "device",
40480
+ addonId: null,
40481
+ access: "view"
40482
+ },
40190
40483
  "fanControl.setDirection": {
40191
40484
  capName: "fan-control",
40192
40485
  capScope: "device",
@@ -40211,6 +40504,12 @@ Object.freeze({
40211
40504
  addonId: null,
40212
40505
  access: "create"
40213
40506
  },
40507
+ "featureProbe.getStatus": {
40508
+ capName: "feature-probe",
40509
+ capScope: "device",
40510
+ addonId: null,
40511
+ access: "view"
40512
+ },
40214
40513
  "filesystemBrowse.browse": {
40215
40514
  capName: "filesystem-browse",
40216
40515
  capScope: "system",
@@ -40229,6 +40528,24 @@ Object.freeze({
40229
40528
  addonId: null,
40230
40529
  access: "view"
40231
40530
  },
40531
+ "flood.getStatus": {
40532
+ capName: "flood",
40533
+ capScope: "device",
40534
+ addonId: null,
40535
+ access: "view"
40536
+ },
40537
+ "gas.getStatus": {
40538
+ capName: "gas",
40539
+ capScope: "device",
40540
+ addonId: null,
40541
+ access: "view"
40542
+ },
40543
+ "humidifier.getStatus": {
40544
+ capName: "humidifier",
40545
+ capScope: "device",
40546
+ addonId: null,
40547
+ access: "view"
40548
+ },
40232
40549
  "humidifier.setMode": {
40233
40550
  capName: "humidifier",
40234
40551
  capScope: "device",
@@ -40247,12 +40564,30 @@ Object.freeze({
40247
40564
  addonId: null,
40248
40565
  access: "create"
40249
40566
  },
40567
+ "humiditySensor.getStatus": {
40568
+ capName: "humidity-sensor",
40569
+ capScope: "device",
40570
+ addonId: null,
40571
+ access: "view"
40572
+ },
40573
+ "image.getStatus": {
40574
+ capName: "image",
40575
+ capScope: "device",
40576
+ addonId: null,
40577
+ access: "view"
40578
+ },
40250
40579
  "imageSettings.getOptions": {
40251
40580
  capName: "image-settings",
40252
40581
  capScope: "device",
40253
40582
  addonId: null,
40254
40583
  access: "view"
40255
40584
  },
40585
+ "imageSettings.getStatus": {
40586
+ capName: "image-settings",
40587
+ capScope: "device",
40588
+ addonId: null,
40589
+ access: "view"
40590
+ },
40256
40591
  "imageSettings.setSettings": {
40257
40592
  capName: "image-settings",
40258
40593
  capScope: "device",
@@ -40325,6 +40660,12 @@ Object.freeze({
40325
40660
  addonId: null,
40326
40661
  access: "create"
40327
40662
  },
40663
+ "intercom.getStatus": {
40664
+ capName: "intercom",
40665
+ capScope: "device",
40666
+ addonId: null,
40667
+ access: "view"
40668
+ },
40328
40669
  "intercom.handleAnswer": {
40329
40670
  capName: "intercom",
40330
40671
  capScope: "device",
@@ -40361,6 +40702,12 @@ Object.freeze({
40361
40702
  addonId: null,
40362
40703
  access: "create"
40363
40704
  },
40705
+ "lawnMowerControl.getStatus": {
40706
+ capName: "lawn-mower-control",
40707
+ capScope: "device",
40708
+ addonId: null,
40709
+ access: "view"
40710
+ },
40364
40711
  "lawnMowerControl.pause": {
40365
40712
  capName: "lawn-mower-control",
40366
40713
  capScope: "device",
@@ -40643,6 +40990,12 @@ Object.freeze({
40643
40990
  addonId: null,
40644
40991
  access: "create"
40645
40992
  },
40993
+ "lockControl.getStatus": {
40994
+ capName: "lock-control",
40995
+ capScope: "device",
40996
+ addonId: null,
40997
+ access: "view"
40998
+ },
40646
40999
  "lockControl.lock": {
40647
41000
  capName: "lock-control",
40648
41001
  capScope: "device",
@@ -40691,6 +41044,12 @@ Object.freeze({
40691
41044
  addonId: null,
40692
41045
  access: "view"
40693
41046
  },
41047
+ "mediaPlayer.getStatus": {
41048
+ capName: "media-player",
41049
+ capScope: "device",
41050
+ addonId: null,
41051
+ access: "view"
41052
+ },
40694
41053
  "mediaPlayer.next": {
40695
41054
  capName: "media-player",
40696
41055
  capScope: "device",
@@ -40883,6 +41242,12 @@ Object.freeze({
40883
41242
  addonId: null,
40884
41243
  access: "create"
40885
41244
  },
41245
+ "motion.getStatus": {
41246
+ capName: "motion",
41247
+ capScope: "device",
41248
+ addonId: null,
41249
+ access: "view"
41250
+ },
40886
41251
  "motion.isDetected": {
40887
41252
  capName: "motion",
40888
41253
  capScope: "device",
@@ -40895,6 +41260,24 @@ Object.freeze({
40895
41260
  addonId: null,
40896
41261
  access: "create"
40897
41262
  },
41263
+ "motionDetection.applyDeviceSettingsPatch": {
41264
+ capName: "motion-detection",
41265
+ capScope: "device",
41266
+ addonId: null,
41267
+ access: "create"
41268
+ },
41269
+ "motionDetection.getDeviceLiveContribution": {
41270
+ capName: "motion-detection",
41271
+ capScope: "device",
41272
+ addonId: null,
41273
+ access: "view"
41274
+ },
41275
+ "motionDetection.getDeviceSettingsContribution": {
41276
+ capName: "motion-detection",
41277
+ capScope: "device",
41278
+ addonId: null,
41279
+ access: "view"
41280
+ },
40898
41281
  "motionDetection.removeCamera": {
40899
41282
  capName: "motion-detection",
40900
41283
  capScope: "device",
@@ -40907,6 +41290,12 @@ Object.freeze({
40907
41290
  addonId: null,
40908
41291
  access: "delete"
40909
41292
  },
41293
+ "motionTrigger.getStatus": {
41294
+ capName: "motion-trigger",
41295
+ capScope: "device",
41296
+ addonId: null,
41297
+ access: "view"
41298
+ },
40910
41299
  "motionTrigger.setMotionTrigger": {
40911
41300
  capName: "motion-trigger",
40912
41301
  capScope: "device",
@@ -40919,6 +41308,12 @@ Object.freeze({
40919
41308
  addonId: null,
40920
41309
  access: "view"
40921
41310
  },
41311
+ "motionZones.getStatus": {
41312
+ capName: "motion-zones",
41313
+ capScope: "device",
41314
+ addonId: null,
41315
+ access: "view"
41316
+ },
40922
41317
  "motionZones.setZone": {
40923
41318
  capName: "motion-zones",
40924
41319
  capScope: "device",
@@ -40979,6 +41374,12 @@ Object.freeze({
40979
41374
  addonId: null,
40980
41375
  access: "view"
40981
41376
  },
41377
+ "nativeObjectDetection.getStatus": {
41378
+ capName: "native-object-detection",
41379
+ capScope: "device",
41380
+ addonId: null,
41381
+ access: "view"
41382
+ },
40982
41383
  "nativeObjectDetection.setEnabled": {
40983
41384
  capName: "native-object-detection",
40984
41385
  capScope: "device",
@@ -40997,6 +41398,12 @@ Object.freeze({
40997
41398
  addonId: null,
40998
41399
  access: "view"
40999
41400
  },
41401
+ "navigation.getStatus": {
41402
+ capName: "navigation",
41403
+ capScope: "device",
41404
+ addonId: null,
41405
+ access: "view"
41406
+ },
41000
41407
  "navigation.goToPoint": {
41001
41408
  capName: "navigation",
41002
41409
  capScope: "device",
@@ -41081,6 +41488,12 @@ Object.freeze({
41081
41488
  addonId: null,
41082
41489
  access: "create"
41083
41490
  },
41491
+ "networkLink.getStatus": {
41492
+ capName: "network-link",
41493
+ capScope: "device",
41494
+ addonId: null,
41495
+ access: "view"
41496
+ },
41084
41497
  "networkQuality.getAllStats": {
41085
41498
  capName: "network-quality",
41086
41499
  capScope: "system",
@@ -41339,18 +41752,36 @@ Object.freeze({
41339
41752
  addonId: null,
41340
41753
  access: "create"
41341
41754
  },
41755
+ "notifier.getStatus": {
41756
+ capName: "notifier",
41757
+ capScope: "device",
41758
+ addonId: null,
41759
+ access: "view"
41760
+ },
41342
41761
  "notifier.send": {
41343
41762
  capName: "notifier",
41344
41763
  capScope: "device",
41345
41764
  addonId: null,
41346
41765
  access: "create"
41347
41766
  },
41767
+ "numericSensor.getStatus": {
41768
+ capName: "numeric-sensor",
41769
+ capScope: "device",
41770
+ addonId: null,
41771
+ access: "view"
41772
+ },
41348
41773
  "oauthIntegration.getDescriptor": {
41349
41774
  capName: "oauth-integration",
41350
41775
  capScope: "system",
41351
41776
  addonId: null,
41352
41777
  access: "view"
41353
41778
  },
41779
+ "osd.getStatus": {
41780
+ capName: "osd",
41781
+ capScope: "device",
41782
+ addonId: null,
41783
+ access: "view"
41784
+ },
41354
41785
  "osd.setOverlay": {
41355
41786
  capName: "osd",
41356
41787
  capScope: "device",
@@ -41423,6 +41854,12 @@ Object.freeze({
41423
41854
  addonId: null,
41424
41855
  access: "create"
41425
41856
  },
41857
+ "petFeeder.getStatus": {
41858
+ capName: "pet-feeder",
41859
+ capScope: "device",
41860
+ addonId: null,
41861
+ access: "view"
41862
+ },
41426
41863
  "petFeeder.markFoodReplenished": {
41427
41864
  capName: "pet-feeder",
41428
41865
  capScope: "device",
@@ -41465,6 +41902,12 @@ Object.freeze({
41465
41902
  addonId: null,
41466
41903
  access: "create"
41467
41904
  },
41905
+ "pipelineAnalytics.applyDeviceSettingsPatch": {
41906
+ capName: "pipeline-analytics",
41907
+ capScope: "device",
41908
+ addonId: null,
41909
+ access: "create"
41910
+ },
41468
41911
  "pipelineAnalytics.cancelRelocateMedia": {
41469
41912
  capName: "pipeline-analytics",
41470
41913
  capScope: "device",
@@ -41531,6 +41974,18 @@ Object.freeze({
41531
41974
  addonId: null,
41532
41975
  access: "view"
41533
41976
  },
41977
+ "pipelineAnalytics.getDeviceLiveContribution": {
41978
+ capName: "pipeline-analytics",
41979
+ capScope: "device",
41980
+ addonId: null,
41981
+ access: "view"
41982
+ },
41983
+ "pipelineAnalytics.getDeviceSettingsContribution": {
41984
+ capName: "pipeline-analytics",
41985
+ capScope: "device",
41986
+ addonId: null,
41987
+ access: "view"
41988
+ },
41534
41989
  "pipelineAnalytics.getEventDensity": {
41535
41990
  capName: "pipeline-analytics",
41536
41991
  capScope: "device",
@@ -42101,6 +42556,12 @@ Object.freeze({
42101
42556
  addonId: null,
42102
42557
  access: "view"
42103
42558
  },
42559
+ "pipelineOrchestrator.applyDeviceSettingsPatch": {
42560
+ capName: "pipeline-orchestrator",
42561
+ capScope: "system",
42562
+ addonId: null,
42563
+ access: "create"
42564
+ },
42104
42565
  "pipelineOrchestrator.assignAudio": {
42105
42566
  capName: "pipeline-orchestrator",
42106
42567
  capScope: "system",
@@ -42191,6 +42652,18 @@ Object.freeze({
42191
42652
  addonId: null,
42192
42653
  access: "view"
42193
42654
  },
42655
+ "pipelineOrchestrator.getDeviceLiveContribution": {
42656
+ capName: "pipeline-orchestrator",
42657
+ capScope: "system",
42658
+ addonId: null,
42659
+ access: "view"
42660
+ },
42661
+ "pipelineOrchestrator.getDeviceSettingsContribution": {
42662
+ capName: "pipeline-orchestrator",
42663
+ capScope: "system",
42664
+ addonId: null,
42665
+ access: "view"
42666
+ },
42194
42667
  "pipelineOrchestrator.getGlobalMetrics": {
42195
42668
  capName: "pipeline-orchestrator",
42196
42669
  capScope: "system",
@@ -42599,12 +43072,36 @@ Object.freeze({
42599
43072
  addonId: null,
42600
43073
  access: "view"
42601
43074
  },
43075
+ "powerMeter.getStatus": {
43076
+ capName: "power-meter",
43077
+ capScope: "device",
43078
+ addonId: null,
43079
+ access: "view"
43080
+ },
43081
+ "presence.getStatus": {
43082
+ capName: "presence",
43083
+ capScope: "device",
43084
+ addonId: null,
43085
+ access: "view"
43086
+ },
43087
+ "pressureSensor.getStatus": {
43088
+ capName: "pressure-sensor",
43089
+ capScope: "device",
43090
+ addonId: null,
43091
+ access: "view"
43092
+ },
42602
43093
  "privacyMask.getOptions": {
42603
43094
  capName: "privacy-mask",
42604
43095
  capScope: "device",
42605
43096
  addonId: null,
42606
43097
  access: "view"
42607
43098
  },
43099
+ "privacyMask.getStatus": {
43100
+ capName: "privacy-mask",
43101
+ capScope: "device",
43102
+ addonId: null,
43103
+ access: "view"
43104
+ },
42608
43105
  "privacyMask.setAudioEnabled": {
42609
43106
  capName: "privacy-mask",
42610
43107
  capScope: "device",
@@ -42665,6 +43162,12 @@ Object.freeze({
42665
43162
  addonId: null,
42666
43163
  access: "view"
42667
43164
  },
43165
+ "ptz.getStatus": {
43166
+ capName: "ptz",
43167
+ capScope: "device",
43168
+ addonId: null,
43169
+ access: "view"
43170
+ },
42668
43171
  "ptz.goHome": {
42669
43172
  capName: "ptz",
42670
43173
  capScope: "device",
@@ -42743,6 +43246,12 @@ Object.freeze({
42743
43246
  addonId: null,
42744
43247
  access: "create"
42745
43248
  },
43249
+ "recording.applyDeviceSettingsPatch": {
43250
+ capName: "recording",
43251
+ capScope: "system",
43252
+ addonId: null,
43253
+ access: "create"
43254
+ },
42746
43255
  "recording.cancelRelocateJob": {
42747
43256
  capName: "recording",
42748
43257
  capScope: "system",
@@ -42791,6 +43300,18 @@ Object.freeze({
42791
43300
  addonId: null,
42792
43301
  access: "view"
42793
43302
  },
43303
+ "recording.getDeviceLiveContribution": {
43304
+ capName: "recording",
43305
+ capScope: "system",
43306
+ addonId: null,
43307
+ access: "view"
43308
+ },
43309
+ "recording.getDeviceSettingsContribution": {
43310
+ capName: "recording",
43311
+ capScope: "system",
43312
+ addonId: null,
43313
+ access: "view"
43314
+ },
42794
43315
  "recording.getPlacement": {
42795
43316
  capName: "recording",
42796
43317
  capScope: "system",
@@ -42809,6 +43330,12 @@ Object.freeze({
42809
43330
  addonId: null,
42810
43331
  access: "view"
42811
43332
  },
43333
+ "recording.getStatus": {
43334
+ capName: "recording",
43335
+ capScope: "system",
43336
+ addonId: null,
43337
+ access: "view"
43338
+ },
42812
43339
  "recording.getStorageMigrationMoveStatus": {
42813
43340
  capName: "recording",
42814
43341
  capScope: "system",
@@ -43013,6 +43540,12 @@ Object.freeze({
43013
43540
  addonId: null,
43014
43541
  access: "delete"
43015
43542
  },
43543
+ "sceneMonitor.getStatus": {
43544
+ capName: "scene-monitor",
43545
+ capScope: "device",
43546
+ addonId: null,
43547
+ access: "view"
43548
+ },
43016
43549
  "sceneMonitor.listScenes": {
43017
43550
  capName: "scene-monitor",
43018
43551
  capScope: "device",
@@ -43043,6 +43576,12 @@ Object.freeze({
43043
43576
  addonId: null,
43044
43577
  access: "create"
43045
43578
  },
43579
+ "scriptRunner.getStatus": {
43580
+ capName: "script-runner",
43581
+ capScope: "device",
43582
+ addonId: null,
43583
+ access: "view"
43584
+ },
43046
43585
  "scriptRunner.run": {
43047
43586
  capName: "script-runner",
43048
43587
  capScope: "device",
@@ -43175,6 +43714,12 @@ Object.freeze({
43175
43714
  addonId: null,
43176
43715
  access: "create"
43177
43716
  },
43717
+ "smoke.getStatus": {
43718
+ capName: "smoke",
43719
+ capScope: "device",
43720
+ addonId: null,
43721
+ access: "view"
43722
+ },
43178
43723
  "smtpProvider.getStatus": {
43179
43724
  capName: "smtp-provider",
43180
43725
  capScope: "system",
@@ -43193,12 +43738,30 @@ Object.freeze({
43193
43738
  addonId: null,
43194
43739
  access: "view"
43195
43740
  },
43741
+ "snapshot.applyDeviceSettingsPatch": {
43742
+ capName: "snapshot",
43743
+ capScope: "device",
43744
+ addonId: null,
43745
+ access: "create"
43746
+ },
43196
43747
  "snapshot.getDebugState": {
43197
43748
  capName: "snapshot",
43198
43749
  capScope: "device",
43199
43750
  addonId: null,
43200
43751
  access: "view"
43201
43752
  },
43753
+ "snapshot.getDeviceLiveContribution": {
43754
+ capName: "snapshot",
43755
+ capScope: "device",
43756
+ addonId: null,
43757
+ access: "view"
43758
+ },
43759
+ "snapshot.getDeviceSettingsContribution": {
43760
+ capName: "snapshot",
43761
+ capScope: "device",
43762
+ addonId: null,
43763
+ access: "view"
43764
+ },
43202
43765
  "snapshot.getSnapshot": {
43203
43766
  capName: "snapshot",
43204
43767
  capScope: "device",
@@ -43217,6 +43780,12 @@ Object.freeze({
43217
43780
  addonId: null,
43218
43781
  access: "view"
43219
43782
  },
43783
+ "snapshot.getStatus": {
43784
+ capName: "snapshot",
43785
+ capScope: "device",
43786
+ addonId: null,
43787
+ access: "view"
43788
+ },
43220
43789
  "snapshot.invalidateCache": {
43221
43790
  capName: "snapshot",
43222
43791
  capScope: "device",
@@ -43553,6 +44122,12 @@ Object.freeze({
43553
44122
  addonId: null,
43554
44123
  access: "create"
43555
44124
  },
44125
+ "streamBroker.applyDeviceSettingsPatch": {
44126
+ capName: "stream-broker",
44127
+ capScope: "system",
44128
+ addonId: null,
44129
+ access: "create"
44130
+ },
43556
44131
  "streamBroker.assignProfile": {
43557
44132
  capName: "stream-broker",
43558
44133
  capScope: "system",
@@ -43589,6 +44164,18 @@ Object.freeze({
43589
44164
  addonId: null,
43590
44165
  access: "view"
43591
44166
  },
44167
+ "streamBroker.getDeviceLiveContribution": {
44168
+ capName: "stream-broker",
44169
+ capScope: "system",
44170
+ addonId: null,
44171
+ access: "view"
44172
+ },
44173
+ "streamBroker.getDeviceSettingsContribution": {
44174
+ capName: "stream-broker",
44175
+ capScope: "system",
44176
+ addonId: null,
44177
+ access: "view"
44178
+ },
43592
44179
  "streamBroker.getPreBufferInfo": {
43593
44180
  capName: "stream-broker",
43594
44181
  capScope: "system",
@@ -43787,6 +44374,12 @@ Object.freeze({
43787
44374
  addonId: null,
43788
44375
  access: "view"
43789
44376
  },
44377
+ "streamParams.getStatus": {
44378
+ capName: "stream-params",
44379
+ capScope: "device",
44380
+ addonId: null,
44381
+ access: "view"
44382
+ },
43790
44383
  "streamParams.setProfile": {
43791
44384
  capName: "stream-params",
43792
44385
  capScope: "device",
@@ -43799,6 +44392,12 @@ Object.freeze({
43799
44392
  addonId: null,
43800
44393
  access: "view"
43801
44394
  },
44395
+ "switch.getStatus": {
44396
+ capName: "switch",
44397
+ capScope: "device",
44398
+ addonId: null,
44399
+ access: "view"
44400
+ },
43802
44401
  "switch.setState": {
43803
44402
  capName: "switch",
43804
44403
  capScope: "device",
@@ -43895,6 +44494,18 @@ Object.freeze({
43895
44494
  addonId: null,
43896
44495
  access: "create"
43897
44496
  },
44497
+ "tamper.getStatus": {
44498
+ capName: "tamper",
44499
+ capScope: "device",
44500
+ addonId: null,
44501
+ access: "view"
44502
+ },
44503
+ "temperatureSensor.getStatus": {
44504
+ capName: "temperature-sensor",
44505
+ capScope: "device",
44506
+ addonId: null,
44507
+ access: "view"
44508
+ },
43898
44509
  "terminalSession.adoptLegacyMonitor": {
43899
44510
  capName: "terminal-session",
43900
44511
  capScope: "system",
@@ -43991,6 +44602,12 @@ Object.freeze({
43991
44602
  addonId: null,
43992
44603
  access: "view"
43993
44604
  },
44605
+ "update.getStatus": {
44606
+ capName: "update",
44607
+ capScope: "device",
44608
+ addonId: null,
44609
+ access: "view"
44610
+ },
43994
44611
  "update.installUpdate": {
43995
44612
  capName: "update",
43996
44613
  capScope: "device",
@@ -44213,6 +44830,12 @@ Object.freeze({
44213
44830
  addonId: null,
44214
44831
  access: "create"
44215
44832
  },
44833
+ "vacuumControl.getStatus": {
44834
+ capName: "vacuum-control",
44835
+ capScope: "device",
44836
+ addonId: null,
44837
+ access: "view"
44838
+ },
44216
44839
  "vacuumControl.locate": {
44217
44840
  capName: "vacuum-control",
44218
44841
  capScope: "device",
@@ -44255,6 +44878,12 @@ Object.freeze({
44255
44878
  addonId: null,
44256
44879
  access: "create"
44257
44880
  },
44881
+ "valve.getStatus": {
44882
+ capName: "valve",
44883
+ capScope: "device",
44884
+ addonId: null,
44885
+ access: "view"
44886
+ },
44258
44887
  "valve.open": {
44259
44888
  capName: "valve",
44260
44889
  capScope: "device",
@@ -44327,6 +44956,12 @@ Object.freeze({
44327
44956
  addonId: null,
44328
44957
  access: "create"
44329
44958
  },
44959
+ "vibration.getStatus": {
44960
+ capName: "vibration",
44961
+ capScope: "device",
44962
+ addonId: null,
44963
+ access: "view"
44964
+ },
44330
44965
  "videoclips.getClipPlayback": {
44331
44966
  capName: "videoclips",
44332
44967
  capScope: "device",
@@ -44363,6 +44998,12 @@ Object.freeze({
44363
44998
  addonId: null,
44364
44999
  access: "view"
44365
45000
  },
45001
+ "waterHeater.getStatus": {
45002
+ capName: "water-heater",
45003
+ capScope: "device",
45004
+ addonId: null,
45005
+ access: "view"
45006
+ },
44366
45007
  "waterHeater.setAway": {
44367
45008
  capName: "water-heater",
44368
45009
  capScope: "device",
@@ -44381,6 +45022,12 @@ Object.freeze({
44381
45022
  addonId: null,
44382
45023
  access: "create"
44383
45024
  },
45025
+ "weather.getStatus": {
45026
+ capName: "weather",
45027
+ capScope: "device",
45028
+ addonId: null,
45029
+ access: "view"
45030
+ },
44384
45031
  "webrtcSession.addIceCandidate": {
44385
45032
  capName: "webrtc-session",
44386
45033
  capScope: "device",
@@ -76168,7 +76815,7 @@ var require_lz4 = /* @__PURE__ */ require_chunk.__commonJSMin(((exports) => {
76168
76815
  };
76169
76816
  }));
76170
76817
  //#endregion
76171
- //#region node_modules/@apocaliss92/nodelink-js/dist/chunk-5PJ6XPBF.js
76818
+ //#region node_modules/@apocaliss92/nodelink-js/dist/chunk-GNC5U3YA.js
76172
76819
  var import_fxp = require_fxp();
76173
76820
  var import_lz4 = /* @__PURE__ */ require_chunk.__toESM(require_lz4(), 1);
76174
76821
  function encodeHeader(h) {
@@ -80333,6 +80980,7 @@ var BaichuanClient = class _BaichuanClient extends events.EventEmitter {
80333
80980
  lockedChannelId = frame.header.channelId;
80334
80981
  lockedStreamType = frame.header.streamType;
80335
80982
  }
80983
+ params.onChunk?.(decrypted);
80336
80984
  chunks.push(decrypted);
80337
80985
  armIdleFinish();
80338
80986
  if (frame.header.responseCode === 201) finish(Buffer.concat(chunks));
@@ -92339,7 +92987,8 @@ ${stderr}`));
92339
92987
  payloadXml,
92340
92988
  streamType: 0,
92341
92989
  timeoutMs,
92342
- ...params.idleTimeoutMs != null ? { idleTimeoutMs: params.idleTimeoutMs } : {}
92990
+ ...params.idleTimeoutMs != null ? { idleTimeoutMs: params.idleTimeoutMs } : {},
92991
+ ...params.onChunk ? { onChunk: params.onChunk } : {}
92343
92992
  });
92344
92993
  } catch (e) {
92345
92994
  trace(`download failed: ${e instanceof Error ? e.message : String(e)}`);
@@ -148403,6 +149052,123 @@ function createClipPlaybackHandler(deps) {
148403
149052
  }).pipe(res);
148404
149053
  };
148405
149054
  }
149055
+ /**
149056
+ * `/addon/provider-reolink/clip-stream/<deviceId>/<clipKey>.mp4` — the same
149057
+ * recording as `clip/…`, served as it ARRIVES from the camera.
149058
+ *
149059
+ * **Not `Range`-able, and it says so.** The response has no length: it is a
149060
+ * fragmented MP4 written by an ffmpeg that is still reading the camera when
149061
+ * the first byte leaves. So there is no `content-length`, `accept-ranges` is
149062
+ * `none`, a `Range` header is IGNORED (a 206 over something with no length
149063
+ * would be a lie), and the peer may not cache it (`no-store`) — a stream is
149064
+ * not an immutable artifact, the file route is. The status line is written
149065
+ * on the first muxed byte, never before: until then every refusal still owns
149066
+ * it, and a camera that serves nothing answers a status with a reason, not an
149067
+ * empty 200 (D391).
149068
+ *
149069
+ * `HEAD` answers the headers and touches no camera.
149070
+ */
149071
+ var CLIP_STREAM_PREFIX = "clip-stream";
149072
+ var STREAM_HEADERS = {
149073
+ "content-type": "video/mp4",
149074
+ "accept-ranges": "none",
149075
+ "cache-control": "no-store",
149076
+ ["x-camstack-clip-transport"]: "forward-only"
149077
+ };
149078
+ /** The status a refusal answers. `404` = nothing here to serve; `409` = a
149079
+ * clip that exists and cannot be served right now; `502` = the camera. */
149080
+ function streamRefusalStatus(code) {
149081
+ switch (code) {
149082
+ case "unknown-device":
149083
+ case "no-catalog-row": return 404;
149084
+ case "sleeping":
149085
+ case "uid-missing":
149086
+ case "clip-in-progress": return 409;
149087
+ case "camera-refused":
149088
+ case "fetch-failed": return 502;
149089
+ }
149090
+ }
149091
+ function createClipStreamHandler(deps) {
149092
+ return async (req, res) => {
149093
+ if (req.method !== "GET" && req.method !== "HEAD") {
149094
+ res.writeHead(405, { allow: "GET, HEAD" });
149095
+ res.end();
149096
+ return;
149097
+ }
149098
+ const match = MEDIA_PATH.exec(pathnameOf(req));
149099
+ if (match === null) {
149100
+ res.writeHead(400);
149101
+ res.end();
149102
+ return;
149103
+ }
149104
+ const deviceId = Number(match[1]);
149105
+ const clipKey = match[2] ?? "";
149106
+ if (req.method === "HEAD") {
149107
+ res.writeHead(200, STREAM_HEADERS);
149108
+ res.end();
149109
+ return;
149110
+ }
149111
+ if (req.headers.range !== void 0) deps.logger.debug("videoclips: a Range header on the clip stream route is ignored", {
149112
+ tags: { deviceId },
149113
+ meta: {
149114
+ clipKey,
149115
+ range: req.headers.range
149116
+ }
149117
+ });
149118
+ const sink = {
149119
+ begin: () => {
149120
+ res.writeHead(200, STREAM_HEADERS);
149121
+ },
149122
+ write: (chunk) => res.write(chunk),
149123
+ onceDrain: (listener) => {
149124
+ res.once("drain", listener);
149125
+ },
149126
+ onceClose: (listener) => {
149127
+ res.once("close", listener);
149128
+ },
149129
+ end: () => {
149130
+ res.end();
149131
+ },
149132
+ destroy: () => {
149133
+ res.destroy();
149134
+ }
149135
+ };
149136
+ let outcome;
149137
+ try {
149138
+ outcome = await deps.stream(deviceId, clipKey, sink);
149139
+ } catch (err) {
149140
+ deps.logger.warn("videoclips: the clip stream route threw", {
149141
+ tags: { deviceId },
149142
+ meta: {
149143
+ clipKey,
149144
+ branch: "stream-threw",
149145
+ error: err instanceof Error ? err.message : String(err)
149146
+ }
149147
+ });
149148
+ if (res.headersSent) res.destroy();
149149
+ else {
149150
+ res.writeHead(500);
149151
+ res.end();
149152
+ }
149153
+ return;
149154
+ }
149155
+ if (outcome.kind !== "refused") return;
149156
+ deps.logger.info("videoclips: clip stream not served", {
149157
+ tags: { deviceId },
149158
+ meta: {
149159
+ clipKey,
149160
+ code: outcome.code,
149161
+ reason: outcome.detail,
149162
+ status: streamRefusalStatus(outcome.code)
149163
+ }
149164
+ });
149165
+ res.writeHead(streamRefusalStatus(outcome.code), {
149166
+ [CLIP_STILL_REASON_HEADER]: outcome.detail.slice(0, 500),
149167
+ [CLIP_STILL_REASON_CODE_HEADER]: outcome.code
149168
+ });
149169
+ res.end();
149170
+ };
149171
+ }
148406
149172
  //#endregion
148407
149173
  //#region src/clips/clip-mux.ts
148408
149174
  /** A clip that has not transferred by here is not going to. */
@@ -148560,6 +149326,94 @@ function describeMuxFailure(run) {
148560
149326
  if (run.code !== 0) return `mux-failed (exit ${String(run.code)}): ${run.stderr.slice(0, 300)}`;
148561
149327
  return null;
148562
149328
  }
149329
+ /** The fragmented container a stream is muxed into — see the file header. */
149330
+ var CLIP_STREAM_MOVFLAGS = "frag_keyframe+empty_moov+default_base_moof";
149331
+ /**
149332
+ * The rate the probe measured, through the library's one derivation.
149333
+ *
149334
+ * `estimateVideoTiming` falls back to the info header when the timestamps
149335
+ * cannot say; that fallback is REFUSED here (`fpsSource: 'unmeasured'`), as
149336
+ * D567 says, so a guess is never handed to `-r` under a measurement's name.
149337
+ */
149338
+ function probeClipStreamRate(timestampsUs) {
149339
+ const timing = estimateVideoTiming({ timestampsUs });
149340
+ if (timing.fpsSource !== "timestamps" || timing.fps === null) return {
149341
+ fps: null,
149342
+ fpsSource: "unmeasured",
149343
+ accessUnits: timestampsUs.length
149344
+ };
149345
+ return {
149346
+ fps: timing.fps,
149347
+ fpsSource: "timestamps",
149348
+ accessUnits: timestampsUs.length
149349
+ };
149350
+ }
149351
+ /**
149352
+ * One ffmpeg, stdin to stdout, video only, fragmented MP4.
149353
+ *
149354
+ * Mirrors `buildClipMuxArgs` where a stream can: the demuxer is named from
149355
+ * the DETECTED codec (the BcMedia header lies on a hub child), `-r` is an
149356
+ * input option before its own `-i`, and the video is copied. Where it
149357
+ * cannot — `+faststart`, the audio input — the file header says why.
149358
+ */
149359
+ function buildClipStreamMuxArgs(input) {
149360
+ const demuxer = input.videoType === "H265" ? "hevc" : "h264";
149361
+ return [
149362
+ "-hide_banner",
149363
+ "-loglevel",
149364
+ "error",
149365
+ "-nostdin",
149366
+ ...input.fps !== null && Number.isFinite(input.fps) && input.fps > 0 ? ["-r", String(input.fps)] : [],
149367
+ "-f",
149368
+ demuxer,
149369
+ "-i",
149370
+ "pipe:0",
149371
+ "-c:v",
149372
+ "copy",
149373
+ "-movflags",
149374
+ CLIP_STREAM_MOVFLAGS,
149375
+ "-f",
149376
+ "mp4",
149377
+ "pipe:1"
149378
+ ];
149379
+ }
149380
+ /**
149381
+ * The D447 rule as a state machine, so it can be proved without a socket.
149382
+ *
149383
+ * A `false` write ARMS the timer; further `false` writes while it is armed do
149384
+ * not push the deadline out. `drain` — the hop's own word that it took what
149385
+ * it held — disarms it. Stall fires at most once, and a disposed gate is
149386
+ * silent for ever.
149387
+ */
149388
+ function createDrainGate(deps) {
149389
+ let handle = null;
149390
+ let stalled = false;
149391
+ const disarm = () => {
149392
+ if (handle !== null) clearTimeout(handle);
149393
+ handle = null;
149394
+ };
149395
+ return {
149396
+ wrote(accepted) {
149397
+ if (accepted || handle !== null || stalled) return;
149398
+ handle = setTimeout(() => {
149399
+ handle = null;
149400
+ if (stalled) return;
149401
+ stalled = true;
149402
+ deps.onStall();
149403
+ }, deps.timeoutMs);
149404
+ },
149405
+ drained() {
149406
+ disarm();
149407
+ },
149408
+ waiting() {
149409
+ return handle !== null;
149410
+ },
149411
+ dispose() {
149412
+ disarm();
149413
+ stalled = true;
149414
+ }
149415
+ };
149416
+ }
148563
149417
  //#endregion
148564
149418
  //#region src/clips/clip-truncation.ts
148565
149419
  /**
@@ -148606,6 +149460,500 @@ function judgeClipDuration(input) {
148606
149460
  };
148607
149461
  }
148608
149462
  //#endregion
149463
+ //#region src/clips/clip-stream.ts
149464
+ var defaultDecoder = (handlers) => new require_chunk_ZBACPERX.BcMediaAnnexBDecoder({
149465
+ strict: false,
149466
+ onVideoAccessUnit: handlers.onVideoAccessUnit,
149467
+ onAudioFrame: () => handlers.onAudioFrame()
149468
+ });
149469
+ var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
149470
+ function createClipStreamProducer(deps) {
149471
+ const sleep = deps.sleep ?? defaultSleep;
149472
+ const createDecoder = deps.createDecoder ?? defaultDecoder;
149473
+ const drainTimeoutMs = deps.drainTimeoutMs ?? 1e4;
149474
+ const flushTimeoutMs = deps.flushTimeoutMs ?? 15e3;
149475
+ const lanes = /* @__PURE__ */ new Map();
149476
+ const activeByHost = /* @__PURE__ */ new Map();
149477
+ const runAttempt = (input, attempt) => new ClipStreamRun({
149478
+ logger: deps.logger,
149479
+ spawnMux: deps.spawnMux,
149480
+ createDecoder,
149481
+ drainTimeoutMs,
149482
+ flushTimeoutMs
149483
+ }, input, attempt).run();
149484
+ const runAttempts = async (input) => {
149485
+ const { deviceId } = input.device;
149486
+ let lastDetail = "no reason given";
149487
+ for (let attempt = 1; attempt <= 3; attempt += 1) {
149488
+ if (attempt > 1) await sleep(500 * 2 ** (attempt - 2));
149489
+ const result = await runAttempt(input, attempt);
149490
+ if (result.kind !== "no-media") return result;
149491
+ lastDetail = result.detail;
149492
+ deps.logger.warn("videoclips: a clip stream produced no media — retrying", {
149493
+ tags: { deviceId },
149494
+ meta: {
149495
+ clipKey: input.row.clipKey,
149496
+ twin: input.twin,
149497
+ branch: "stream-no-media",
149498
+ attempt,
149499
+ error: lastDetail
149500
+ }
149501
+ });
149502
+ }
149503
+ deps.logger.warn("videoclips: the camera never delivered this clip stream", {
149504
+ tags: { deviceId },
149505
+ meta: {
149506
+ clipKey: input.row.clipKey,
149507
+ twin: input.twin,
149508
+ branch: "camera-refused",
149509
+ attempts: 3,
149510
+ error: lastDetail
149511
+ }
149512
+ });
149513
+ return {
149514
+ kind: "refused",
149515
+ code: "camera-refused",
149516
+ detail: `clip stream never landed after 3 attempts — ${lastDetail}`,
149517
+ attempts: 3
149518
+ };
149519
+ };
149520
+ return {
149521
+ active: (hostKey) => activeByHost.get(hostKey) ?? 0,
149522
+ stream: (input) => {
149523
+ const { hostKey, deviceId } = input.device;
149524
+ const ahead = activeByHost.get(hostKey) ?? 0;
149525
+ if (ahead > 0) deps.logger.info("videoclips: a clip stream is queued behind another on this host", {
149526
+ tags: { deviceId },
149527
+ meta: {
149528
+ clipKey: input.row.clipKey,
149529
+ twin: input.twin,
149530
+ branch: "stream-queued",
149531
+ ahead
149532
+ }
149533
+ });
149534
+ activeByHost.set(hostKey, ahead + 1);
149535
+ const run = (lanes.get(hostKey) ?? Promise.resolve()).then(() => runAttempts(input)).finally(() => {
149536
+ const left = (activeByHost.get(hostKey) ?? 1) - 1;
149537
+ if (left <= 0) activeByHost.delete(hostKey);
149538
+ else activeByHost.set(hostKey, left);
149539
+ });
149540
+ lanes.set(hostKey, run.then(() => void 0, () => void 0));
149541
+ return run;
149542
+ }
149543
+ };
149544
+ }
149545
+ /**
149546
+ * One transfer: camera → decoder → ffmpeg → peer, with a cut on every hop.
149547
+ *
149548
+ * A class because the run is a state machine with a dozen fields the chunk
149549
+ * callback and the download promise both touch; passing them around as
149550
+ * closures over `let`s was less readable, not more immutable.
149551
+ */
149552
+ var ClipStreamRun = class {
149553
+ deps;
149554
+ input;
149555
+ attempt;
149556
+ accessUnits = 0;
149557
+ audioFrames = 0;
149558
+ timestampsUs = [];
149559
+ /** Access units held until the rate is probed — the frames in flight. */
149560
+ probe = [];
149561
+ mux = null;
149562
+ muxStderr = "";
149563
+ muxClosed = null;
149564
+ rate = {
149565
+ fps: null,
149566
+ fpsSource: "unmeasured",
149567
+ accessUnits: 0
149568
+ };
149569
+ videoType = null;
149570
+ begun = false;
149571
+ ended = false;
149572
+ bytesOut = 0;
149573
+ cut = null;
149574
+ stdinGate;
149575
+ peerGate;
149576
+ tags;
149577
+ constructor(deps, input, attempt) {
149578
+ this.deps = deps;
149579
+ this.input = input;
149580
+ this.attempt = attempt;
149581
+ this.tags = { deviceId: input.device.deviceId };
149582
+ this.stdinGate = createDrainGate({
149583
+ timeoutMs: deps.drainTimeoutMs,
149584
+ onStall: () => this.sever("mux-stalled", "ffmpeg stopped taking access units")
149585
+ });
149586
+ this.peerGate = createDrainGate({
149587
+ timeoutMs: deps.drainTimeoutMs,
149588
+ onStall: () => this.sever("peer-stalled", "the peer stopped taking bytes")
149589
+ });
149590
+ }
149591
+ async run() {
149592
+ const { device, row, fileName, twin, sink } = this.input;
149593
+ sink.onceClose(() => {
149594
+ if (!this.ended) this.sever("peer-closed", "the peer closed the connection");
149595
+ });
149596
+ const decoder = this.deps.createDecoder({
149597
+ onVideoAccessUnit: (unit) => this.onAccessUnit(unit),
149598
+ onAudioFrame: () => {
149599
+ this.audioFrames += 1;
149600
+ }
149601
+ });
149602
+ this.deps.logger.info("videoclips: streaming a clip file from the camera", {
149603
+ tags: this.tags,
149604
+ meta: {
149605
+ clipKey: row.clipKey,
149606
+ twin,
149607
+ fileName,
149608
+ startMs: row.startMs,
149609
+ endMs: row.endMs,
149610
+ attempt: this.attempt,
149611
+ idleTimeoutMs: 300
149612
+ }
149613
+ });
149614
+ let api;
149615
+ try {
149616
+ api = await device.getApi();
149617
+ } catch (err) {
149618
+ return {
149619
+ kind: "no-media",
149620
+ detail: describe(err)
149621
+ };
149622
+ }
149623
+ try {
149624
+ await api.fileInfoListReplayBinaryDownload({
149625
+ channel: device.channel,
149626
+ ...device.uid !== void 0 ? { uid: device.uid } : {},
149627
+ fileName,
149628
+ idleTimeoutMs: 300,
149629
+ onChunk: (chunk) => this.onChunk(decoder, chunk)
149630
+ });
149631
+ } catch (err) {
149632
+ return this.onTransferFailed(err);
149633
+ }
149634
+ return this.onTransferEnded();
149635
+ }
149636
+ /** The camera's bytes, as they arrive. A cut run THROWS here: the only stop. */
149637
+ onChunk(decoder, chunk) {
149638
+ if (this.cut !== null) throw new Error(`clip stream cut: ${this.cut.reason}`);
149639
+ try {
149640
+ decoder.push(chunk);
149641
+ } catch (err) {
149642
+ this.sever("decode-failed", describe(err));
149643
+ throw err;
149644
+ }
149645
+ }
149646
+ onAccessUnit(unit) {
149647
+ if (this.cut !== null) return;
149648
+ this.accessUnits += 1;
149649
+ this.timestampsUs.push(unit.microseconds);
149650
+ if (this.mux === null) {
149651
+ this.probe = [...this.probe, unit];
149652
+ if (this.probe.length >= 16) this.startMux();
149653
+ return;
149654
+ }
149655
+ this.feed(unit.annexB);
149656
+ }
149657
+ /** Spawn ffmpeg with the probed rate and hand it what was held. */
149658
+ startMux() {
149659
+ const first = this.probe[0];
149660
+ if (first === void 0 || this.mux !== null) return;
149661
+ this.videoType = first.videoType;
149662
+ this.rate = probeClipStreamRate(this.timestampsUs);
149663
+ const { row, twin } = this.input;
149664
+ if (this.rate.fpsSource !== "timestamps") this.deps.logger.warn("videoclips: no measured frame rate for this clip stream", {
149665
+ tags: this.tags,
149666
+ meta: {
149667
+ clipKey: row.clipKey,
149668
+ twin,
149669
+ branch: "fps-unmeasured",
149670
+ probeAccessUnits: this.rate.accessUnits
149671
+ }
149672
+ });
149673
+ let mux;
149674
+ try {
149675
+ mux = this.deps.spawnMux(buildClipStreamMuxArgs({
149676
+ videoType: this.videoType,
149677
+ fps: this.rate.fps
149678
+ }));
149679
+ } catch (err) {
149680
+ this.sever("mux-failed", `ffmpeg could not be spawned: ${describe(err)}`);
149681
+ return;
149682
+ }
149683
+ this.mux = mux;
149684
+ this.muxClosed = new Promise((resolve) => {
149685
+ mux.on("close", (code) => resolve(code));
149686
+ mux.on("error", (err) => {
149687
+ this.sever("mux-failed", `ffmpeg failed: ${err.message}`);
149688
+ resolve(null);
149689
+ });
149690
+ });
149691
+ mux.stderr?.on("data", (chunk) => {
149692
+ if (this.muxStderr.length < 2e3) this.muxStderr += chunk.toString("utf8");
149693
+ });
149694
+ mux.stdin?.on("error", (err) => this.sever("mux-failed", `ffmpeg stdin: ${err.message}`));
149695
+ mux.stdin?.on("drain", () => this.stdinGate.drained());
149696
+ mux.stdout?.on("data", (chunk) => this.onMuxOut(chunk));
149697
+ mux.stdout?.on("end", () => this.finishSink());
149698
+ this.deps.logger.info("videoclips: a clip stream is muxing", {
149699
+ tags: this.tags,
149700
+ meta: {
149701
+ clipKey: row.clipKey,
149702
+ twin,
149703
+ videoType: this.videoType,
149704
+ fps: this.rate.fps,
149705
+ fpsSource: this.rate.fpsSource,
149706
+ probeAccessUnits: this.rate.accessUnits
149707
+ }
149708
+ });
149709
+ const held = this.probe;
149710
+ this.probe = [];
149711
+ for (const unit of held) this.feed(unit.annexB);
149712
+ }
149713
+ feed(annexB) {
149714
+ if (this.cut !== null || this.mux?.stdin === null || this.mux?.stdin === void 0) return;
149715
+ this.stdinGate.wrote(this.mux.stdin.write(annexB));
149716
+ }
149717
+ /** ffmpeg's output, to the peer, honouring `write()`'s return (D447). */
149718
+ onMuxOut(chunk) {
149719
+ if (this.cut !== null) return;
149720
+ const { sink } = this.input;
149721
+ if (!this.begun) {
149722
+ this.begun = true;
149723
+ sink.begin();
149724
+ }
149725
+ const accepted = sink.write(chunk);
149726
+ this.bytesOut += chunk.byteLength;
149727
+ this.peerGate.wrote(accepted);
149728
+ if (accepted) return;
149729
+ const stdout = this.mux?.stdout;
149730
+ stdout?.pause();
149731
+ sink.onceDrain(() => {
149732
+ this.peerGate.drained();
149733
+ stdout?.resume();
149734
+ });
149735
+ }
149736
+ finishSink() {
149737
+ if (this.cut !== null || this.ended) return;
149738
+ this.ended = true;
149739
+ this.input.sink.end();
149740
+ }
149741
+ /** Sever every hop, once, and say why. */
149742
+ sever(reason, detail) {
149743
+ if (this.cut !== null) return;
149744
+ this.cut = {
149745
+ reason,
149746
+ detail
149747
+ };
149748
+ this.stdinGate.dispose();
149749
+ this.peerGate.dispose();
149750
+ this.mux?.kill("SIGKILL");
149751
+ this.deps.logger.warn("videoclips: a clip stream was cut", {
149752
+ tags: this.tags,
149753
+ meta: {
149754
+ clipKey: this.input.row.clipKey,
149755
+ twin: this.input.twin,
149756
+ branch: reason,
149757
+ detail,
149758
+ accessUnits: this.accessUnits,
149759
+ bytesOut: this.bytesOut,
149760
+ begun: this.begun
149761
+ }
149762
+ });
149763
+ if (this.begun) this.input.sink.destroy();
149764
+ }
149765
+ cutOutcome(cut) {
149766
+ if (this.begun) return {
149767
+ kind: "cut",
149768
+ reason: cut.reason,
149769
+ detail: cut.detail,
149770
+ bytesOut: this.bytesOut
149771
+ };
149772
+ return {
149773
+ kind: "refused",
149774
+ code: "fetch-failed",
149775
+ detail: `${cut.reason}: ${cut.detail}`,
149776
+ attempts: this.attempt
149777
+ };
149778
+ }
149779
+ onTransferFailed(err) {
149780
+ if (this.cut !== null) return this.cutOutcome(this.cut);
149781
+ if (this.accessUnits === 0) return {
149782
+ kind: "no-media",
149783
+ detail: describe(err)
149784
+ };
149785
+ this.sever("camera-failed", describe(err));
149786
+ return this.cutOutcome({
149787
+ reason: "camera-failed",
149788
+ detail: describe(err)
149789
+ });
149790
+ }
149791
+ async onTransferEnded() {
149792
+ if (this.cut !== null) return this.cutOutcome(this.cut);
149793
+ if (this.accessUnits === 0) return {
149794
+ kind: "no-media",
149795
+ detail: "the camera answered the transfer with no access unit"
149796
+ };
149797
+ if (this.mux === null) this.startMux();
149798
+ const cutAtStart = this.cut;
149799
+ if (cutAtStart !== null) return this.cutOutcome(cutAtStart);
149800
+ const mux = this.mux;
149801
+ if (mux === null || this.muxClosed === null) return this.cutOutcome({
149802
+ reason: "mux-failed",
149803
+ detail: "no mux after the transfer ended"
149804
+ });
149805
+ mux.stdin?.end();
149806
+ const flush = setTimeout(() => this.sever("mux-hung", `ffmpeg did not exit ${this.deps.flushTimeoutMs} ms after its input ended`), this.deps.flushTimeoutMs);
149807
+ const code = await this.muxClosed;
149808
+ clearTimeout(flush);
149809
+ const cutAfter = this.cut;
149810
+ if (cutAfter !== null) return this.cutOutcome(cutAfter);
149811
+ if (code !== 0) {
149812
+ this.sever("mux-failed", `ffmpeg exited ${String(code)}: ${this.muxStderr.slice(0, 300)}`);
149813
+ return this.cutOutcome({
149814
+ reason: "mux-failed",
149815
+ detail: this.muxStderr.slice(0, 300)
149816
+ });
149817
+ }
149818
+ this.finishSink();
149819
+ return this.served();
149820
+ }
149821
+ /** The stream landed: judge it against the row, out loud, and answer. */
149822
+ served() {
149823
+ const { row, twin } = this.input;
149824
+ const timing = estimateVideoTiming({ timestampsUs: this.timestampsUs });
149825
+ const durationSeconds = timing.fpsSource === "timestamps" ? timing.durationSeconds : null;
149826
+ const verdict = judgeClipDuration({
149827
+ expectedMs: row.endMs - row.startMs,
149828
+ decodedSeconds: durationSeconds,
149829
+ ...row.inProgress === true ? { inProgress: true } : {}
149830
+ });
149831
+ const meta = {
149832
+ clipKey: row.clipKey,
149833
+ twin,
149834
+ attempt: this.attempt,
149835
+ accessUnits: this.accessUnits,
149836
+ bytesOut: this.bytesOut,
149837
+ durationSeconds,
149838
+ expectedMs: row.endMs - row.startMs,
149839
+ fps: this.rate.fps,
149840
+ fpsSource: this.rate.fpsSource,
149841
+ videoType: this.videoType
149842
+ };
149843
+ if (verdict.kind === "short") this.deps.logger.warn("videoclips: a clip stream came up short against its row", {
149844
+ tags: this.tags,
149845
+ meta: {
149846
+ ...meta,
149847
+ branch: "stream-short",
149848
+ shortfallSeconds: verdict.shortfallSeconds,
149849
+ retried: false
149850
+ }
149851
+ });
149852
+ else if (verdict.kind === "unknown") this.deps.logger.warn("videoclips: a clip stream could not be checked for truncation", {
149853
+ tags: this.tags,
149854
+ meta: {
149855
+ ...meta,
149856
+ branch: "completeness-unverified",
149857
+ reason: verdict.reason
149858
+ }
149859
+ });
149860
+ else this.deps.logger.info("videoclips: a clip stream landed whole", {
149861
+ tags: this.tags,
149862
+ meta: {
149863
+ ...meta,
149864
+ branch: "stream-served"
149865
+ }
149866
+ });
149867
+ if (this.audioFrames > 0) this.deps.logger.warn("videoclips: a clip stream was served without sound", {
149868
+ tags: this.tags,
149869
+ meta: {
149870
+ clipKey: row.clipKey,
149871
+ twin,
149872
+ branch: "stream-video-only",
149873
+ audioFramesDropped: this.audioFrames
149874
+ }
149875
+ });
149876
+ return {
149877
+ kind: "served",
149878
+ accessUnits: this.accessUnits,
149879
+ audioFramesDropped: this.audioFrames,
149880
+ bytesOut: this.bytesOut,
149881
+ durationSeconds,
149882
+ verdict,
149883
+ rate: this.rate,
149884
+ attempts: this.attempt
149885
+ };
149886
+ }
149887
+ };
149888
+ function describe(err) {
149889
+ return err instanceof Error ? err.message : String(err);
149890
+ }
149891
+ //#endregion
149892
+ //#region src/clips/clip-stream-route.ts
149893
+ function createClipStreamRoute(deps) {
149894
+ return async (deviceId, clipKey, sink) => {
149895
+ const twin = "sub";
149896
+ const gate = await deps.gate({
149897
+ deviceId,
149898
+ clipKey,
149899
+ twin,
149900
+ path: "stream"
149901
+ });
149902
+ if (gate.kind === "refused") return {
149903
+ kind: "refused",
149904
+ code: gate.branch,
149905
+ detail: gate.detail
149906
+ };
149907
+ const row = deps.rowFor(deviceId, clipKey);
149908
+ if (row === null) {
149909
+ deps.logger.warn("videoclips: no catalog row for this clip stream", {
149910
+ tags: { deviceId },
149911
+ meta: {
149912
+ clipKey,
149913
+ twin,
149914
+ branch: "no-catalog-row"
149915
+ }
149916
+ });
149917
+ return {
149918
+ kind: "refused",
149919
+ code: "no-catalog-row",
149920
+ detail: "no-catalog-row: list this day before asking for its stream"
149921
+ };
149922
+ }
149923
+ if (row.inProgress === true) {
149924
+ deps.logger.warn("videoclips: refusing to stream a clip the camera is still writing", {
149925
+ tags: { deviceId },
149926
+ meta: {
149927
+ clipKey,
149928
+ twin,
149929
+ branch: "clip-in-progress"
149930
+ }
149931
+ });
149932
+ return {
149933
+ kind: "refused",
149934
+ code: "clip-in-progress",
149935
+ detail: "clip-in-progress: the camera is still writing this file; it has no end to stream to"
149936
+ };
149937
+ }
149938
+ const outcome = await deps.producer.stream({
149939
+ device: gate.device,
149940
+ row,
149941
+ fileName: row.subFileName,
149942
+ twin,
149943
+ sink
149944
+ });
149945
+ switch (outcome.kind) {
149946
+ case "served": return { kind: "served" };
149947
+ case "cut": return { kind: "cut" };
149948
+ case "refused": return {
149949
+ kind: "refused",
149950
+ code: outcome.code,
149951
+ detail: outcome.detail
149952
+ };
149953
+ }
149954
+ };
149955
+ }
149956
+ //#endregion
148609
149957
  //#region src/clips/clip-thumb-queue.ts
148610
149958
  /**
148611
149959
  * The provider's OWN CoverPreview lane — two queues, one in flight per host.
@@ -148785,6 +150133,59 @@ function decideClipPrefetch(input) {
148785
150133
  return { kind: "run" };
148786
150134
  }
148787
150135
  //#endregion
150136
+ //#region src/clips/clip-window.ts
150137
+ /**
150138
+ * Does this row overlap the window?
150139
+ *
150140
+ * Half-open on both ends (`start < until`, `end > since`), so a clip that ends
150141
+ * exactly when the window begins is OUT and a clip that begins exactly when it
150142
+ * ends is OUT — otherwise two adjacent windows both claim the same boundary
150143
+ * clip and a surface paging through them renders it twice.
150144
+ *
150145
+ * A row whose `endMs` is not after its `startMs` is treated as an INSTANT at
150146
+ * `startMs`. That is not defensive padding: a clip still being written carries
150147
+ * a frozen `endMs` from the camera's index (D563), and on this fleet that has
150148
+ * been observed equal to — and once behind — its own start. Judging such a row
150149
+ * by a bad end would drop a recording that is happening right now.
150150
+ */
150151
+ function clipOverlapsWindow(row, window) {
150152
+ const end = row.endMs > row.startMs ? row.endMs : row.startMs;
150153
+ return row.startMs < window.until && end > window.since;
150154
+ }
150155
+ /**
150156
+ * What the provider ANSWERS with — the one gate, so there is a single place
150157
+ * that decides what leaves this listing and a single line that reports it.
150158
+ *
150159
+ * ## Why a clip still being written is not listed
150160
+ *
150161
+ * The operator, 2026-09-22: *"Niente clips in preparazione, non ha senso
150162
+ * visualizzarle."* He is right, and the reasons had been accumulating:
150163
+ *
150164
+ * - its `endMs` is a frozen guess — the camera's index keeps whatever it held
150165
+ * when the file was opened, measured on 592 as the same `endTime` for eight
150166
+ * minutes (D563) — so the row's duration, its position on the axis and its
150167
+ * height are all wrong and all move;
150168
+ * - `readClipBytes` already refuses it by name (`clip-in-progress`): it has no
150169
+ * end to export against, so the completion rule has nothing to prove the
150170
+ * fetch finished (D568);
150171
+ * - it cannot be looped, and the surface has to explain why (`resolveClipLoop`);
150172
+ * - the player route would file a TRUNCATED copy under an immutable key, so
150173
+ * the first seconds become permanently what that clip is.
150174
+ *
150175
+ * Four different places bending around a row that cannot be played properly.
150176
+ * Not listing it deletes all four questions instead of answering them: the
150177
+ * clip appears, whole, on the next listing after the camera closes the file.
150178
+ */
150179
+ function listableClips(rows, window) {
150180
+ const inWindow = rows.filter((row) => clipOverlapsWindow(row, window));
150181
+ const kept = inWindow.filter((row) => row.inProgress !== true);
150182
+ return {
150183
+ kept,
150184
+ outOfWindow: rows.length - inWindow.length,
150185
+ inProgress: inWindow.length - kept.length
150186
+ };
150187
+ }
150188
+ //#endregion
148788
150189
  //#region src/clips/clip-identity.ts
148789
150190
  /**
148790
150191
  * A Reolink clip's IDENTITY — the id the operator's surface carries around and
@@ -148901,7 +150302,8 @@ var CLASS_BY_NATIVE = {
148901
150302
  var KIND_BY_NATIVE = {
148902
150303
  md: "motion",
148903
150304
  pir: "motion",
148904
- cry: "audio"
150305
+ cry: "audio",
150306
+ other: "motion"
148905
150307
  };
148906
150308
  /** What the subject DID — carried beside a class, never as one. */
148907
150309
  var BEHAVIOUR_BY_NATIVE = {
@@ -148914,8 +150316,9 @@ var BEHAVIOUR_BY_NATIVE = {
148914
150316
  /**
148915
150317
  * Recording REASONS — why the file exists, not what is in it. They get no
148916
150318
  * label and no behaviour; the row still lists, showing the reason raw.
148917
- * `other` / `none` are the camera declining to classify, which is not the same
148918
- * as the camera saying "nothing".
150319
+ * `none` is the camera declining to classify, which is not the same as the
150320
+ * camera saying "nothing". `other` used to live here and is now a motion KIND
150321
+ * — see {@link KIND_BY_NATIVE}.
148919
150322
  */
148920
150323
  var RECORDING_REASONS = new Set([
148921
150324
  "sched",
@@ -148927,7 +150330,6 @@ var RECORDING_REASONS = new Set([
148927
150330
  "answer",
148928
150331
  "tamper",
148929
150332
  "lmsg",
148930
- "other",
148931
150333
  "none"
148932
150334
  ]);
148933
150335
  /**
@@ -149503,6 +150905,18 @@ function createReolinkVideoclipsProvider(deps) {
149503
150905
  timeZone: device.timeZone,
149504
150906
  day
149505
150907
  });
150908
+ const newest = result.rows[result.rows.length - 1] ?? result.rows[0];
150909
+ deps.logger.info("videoclips: the camera answered a day search", {
150910
+ tags,
150911
+ meta: {
150912
+ day,
150913
+ rows: result.rows.length,
150914
+ newestFile: newest?.subFileName ?? null,
150915
+ newestStartMs: newest?.startMs ?? null,
150916
+ oldestStartMs: result.rows[0]?.startMs ?? null,
150917
+ askedAtMs: Date.now()
150918
+ }
150919
+ });
149506
150920
  if (result.mainSearchFailed !== void 0) deps.logger.warn("videoclips: the main-stream search refused — no high twin today", {
149507
150921
  tags,
149508
150922
  meta: {
@@ -149544,7 +150958,24 @@ function createReolinkVideoclipsProvider(deps) {
149544
150958
  state: "ok",
149545
150959
  catalogAsOf: deps.now()
149546
150960
  });
149547
- return rows;
150961
+ for (const row of rows) rowsByKey.set(`${device.deviceId}|${row.clipKey}`, row);
150962
+ const narrowed = listableClips(rows, {
150963
+ since,
150964
+ until
150965
+ });
150966
+ if (narrowed.outOfWindow > 0 || narrowed.inProgress > 0) deps.logger.info("videoclips: narrowed a day listing to what it can answer", {
150967
+ tags,
150968
+ meta: {
150969
+ branch: "listing-narrowed",
150970
+ listed: rows.length,
150971
+ kept: narrowed.kept.length,
150972
+ outOfWindow: narrowed.outOfWindow,
150973
+ inProgress: narrowed.inProgress,
150974
+ since,
150975
+ until
150976
+ }
150977
+ });
150978
+ return narrowed.kept;
149548
150979
  };
149549
150980
  /**
149550
150981
  * Give a day's rows a class when the FILES carried none.
@@ -150452,9 +151883,15 @@ function createClipService(deps) {
150452
151883
  * clip-export vocabulary, so the provider can hand the engine a token it
150453
151884
  * believes rather than prose it has to guess at (D572).
150454
151885
  */
150455
- const produceTwin = async (input) => {
150456
- const { deviceId, row, fileName, twin } = input;
150457
- const clipKey = row.clipKey;
151886
+ /**
151887
+ * The gates every clip byte path runs, in ONE place: the device, the
151888
+ * sleeping standalone, the uid. The file producer and the stream producer
151889
+ * both call this and neither decides any of it again — a second copy of
151890
+ * the sleeping gate is a second chance to wake a camera nobody authorised.
151891
+ * Every refusal is logged HERE, with its branch, so a caller cannot forget.
151892
+ */
151893
+ const gateClipDevice = async (input) => {
151894
+ const { deviceId, clipKey, twin, path } = input;
150458
151895
  const tags = { deviceId };
150459
151896
  const device = await deps.resolveDevice(deviceId);
150460
151897
  if (device === null) {
@@ -150463,11 +151900,13 @@ function createClipService(deps) {
150463
151900
  meta: {
150464
151901
  clipKey,
150465
151902
  twin,
151903
+ path,
150466
151904
  branch: "unknown-device"
150467
151905
  }
150468
151906
  });
150469
151907
  return {
150470
151908
  kind: "refused",
151909
+ branch: "unknown-device",
150471
151910
  code: "catalog-unreachable",
150472
151911
  detail: `device ${deviceId} is not a Reolink camera on this node`
150473
151912
  };
@@ -150479,11 +151918,13 @@ function createClipService(deps) {
150479
151918
  meta: {
150480
151919
  clipKey,
150481
151920
  twin,
151921
+ path,
150482
151922
  branch: "sleeping"
150483
151923
  }
150484
151924
  });
150485
151925
  return {
150486
151926
  kind: "refused",
151927
+ branch: "sleeping",
150487
151928
  code: "sleeping",
150488
151929
  detail: `device ${deviceId} is a standalone battery camera and is sleeping — fetching a clip would wake it, and no operator authorisation was carried`
150489
151930
  };
@@ -150493,6 +151934,7 @@ function createClipService(deps) {
150493
151934
  meta: {
150494
151935
  clipKey,
150495
151936
  twin,
151937
+ path,
150496
151938
  branch: "wake-authorised"
150497
151939
  }
150498
151940
  });
@@ -150503,16 +151945,50 @@ function createClipService(deps) {
150503
151945
  meta: {
150504
151946
  clipKey,
150505
151947
  twin,
151948
+ path,
150506
151949
  branch: "uid-missing"
150507
151950
  }
150508
151951
  });
150509
151952
  return {
150510
151953
  kind: "refused",
151954
+ branch: "uid-missing",
150511
151955
  code: "catalog-unreachable",
150512
151956
  detail: UID_MISSING_REASON
150513
151957
  };
150514
151958
  }
151959
+ return {
151960
+ kind: "ok",
151961
+ device
151962
+ };
151963
+ };
151964
+ const produceTwin = async (input) => {
151965
+ const { deviceId, row, fileName, twin } = input;
151966
+ const clipKey = row.clipKey;
151967
+ const tags = { deviceId };
151968
+ const gate = await gateClipDevice({
151969
+ deviceId,
151970
+ clipKey,
151971
+ twin,
151972
+ path: "file",
151973
+ ...input.wake !== void 0 ? { wake: input.wake } : {}
151974
+ });
151975
+ if (gate.kind === "refused") return {
151976
+ kind: "refused",
151977
+ code: gate.code,
151978
+ detail: gate.detail
151979
+ };
151980
+ const { device } = gate;
150515
151981
  let downloaded;
151982
+ deps.logger.info("videoclips: fetching a clip file from the camera", {
151983
+ tags,
151984
+ meta: {
151985
+ clipKey,
151986
+ twin,
151987
+ fileName,
151988
+ startMs: row.startMs,
151989
+ endMs: row.endMs
151990
+ }
151991
+ });
150516
151992
  try {
150517
151993
  downloaded = await fetchVerifiedClipBytes(device, row, clipKey, fileName);
150518
151994
  } catch (err) {
@@ -150679,6 +152155,23 @@ function createClipService(deps) {
150679
152155
  if (outcome.kind === "ok" && twin === "sub") await media.put(deviceId, row.clipKey, outcome.bytes);
150680
152156
  return outcome;
150681
152157
  };
152158
+ /**
152159
+ * The STREAM's path (`clip-stream-route.ts`): the same gates, the same row,
152160
+ * nothing written. Beside the file path, not in its place.
152161
+ */
152162
+ const streamClip = createClipStreamRoute({
152163
+ logger: deps.logger,
152164
+ gate: gateClipDevice,
152165
+ rowFor,
152166
+ producer: createClipStreamProducer({
152167
+ logger: deps.logger,
152168
+ spawnMux: (args) => (0, node_child_process.spawn)(deps.ffmpegBinaryPath ?? "ffmpeg", [...args], { stdio: [
152169
+ "pipe",
152170
+ "pipe",
152171
+ "pipe"
152172
+ ] })
152173
+ })
152174
+ });
150682
152175
  const provider = createReolinkVideoclipsProvider({
150683
152176
  now,
150684
152177
  cache,
@@ -150708,6 +152201,11 @@ function createClipService(deps) {
150708
152201
  hostBusy: (hostKey) => queue.busy(hostKey),
150709
152202
  thumbPrefix: CLIP_THUMB_PREFIX,
150710
152203
  mediaPrefix: CLIP_MEDIA_PREFIX,
152204
+ streamPrefix: CLIP_STREAM_PREFIX,
152205
+ streamHandler: createClipStreamHandler({
152206
+ stream: streamClip,
152207
+ logger: deps.logger
152208
+ }),
150711
152209
  thumbHandler: createClipThumbHandler({
150712
152210
  readCached: (deviceId, clipKey) => thumbs.read(deviceId, clipKey),
150713
152211
  mint: (deviceId, clipKey) => mintThumbnail(deviceId, clipKey, "interactive"),
@@ -164947,11 +166445,18 @@ var ReolinkProviderAddon = class extends BaseDeviceProvider {
164947
166445
  access: "authenticated",
164948
166446
  handler: service.playbackHandler
164949
166447
  });
166448
+ const stream = await this.ctx.dataPlane?.serve({
166449
+ prefix: service.streamPrefix,
166450
+ access: "authenticated",
166451
+ handler: service.streamHandler
166452
+ });
164950
166453
  this.ctx.logger.info("Reolink clip data planes served", { meta: {
164951
166454
  thumbPath: `/addon/${REOLINK_ADDON_ID}/${service.thumbPrefix}`,
164952
166455
  mediaPath: `/addon/${REOLINK_ADDON_ID}/${service.mediaPrefix}`,
166456
+ streamPath: `/addon/${REOLINK_ADDON_ID}/${service.streamPrefix}`,
164953
166457
  thumbServed: thumb !== void 0,
164954
- mediaServed: media !== void 0
166458
+ mediaServed: media !== void 0,
166459
+ streamServed: stream !== void 0
164955
166460
  } });
164956
166461
  } catch (err) {
164957
166462
  this.ctx.logger.warn("Reolink clip data planes failed to serve — clips will not render", { meta: { error: err instanceof Error ? err.message : String(err) } });