@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.mjs CHANGED
@@ -5385,7 +5385,7 @@ var ZodIssueCode = {
5385
5385
  var ZodFirstPartyTypeKind;
5386
5386
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5387
5387
  //#endregion
5388
- //#region ../types/dist/sleep-BVhJDJka.mjs
5388
+ //#region ../types/dist/sleep-CUmLTud7.mjs
5389
5389
  /**
5390
5390
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5391
5391
  * window to float samples (D455).
@@ -25634,6 +25634,23 @@ var videoclipsCapability = {
25634
25634
  deviceId: number(),
25635
25635
  clipId: string().min(1),
25636
25636
  /**
25637
+ * WHICH provider holds the bytes — the `addonId` a
25638
+ * {@link ClipSourceSchema} row carries. **Required**, for the reason
25639
+ * `listClips` states one method above and for a second one this method
25640
+ * learned the hard way.
25641
+ *
25642
+ * The caller is the stream broker, and the broker lives in
25643
+ * `addon-pipeline` — the same addon that registers the RECORDER's
25644
+ * videoclips provider. With no provider named, the in-process
25645
+ * resolution handed it its own sibling, which implements this method
25646
+ * solely to explain that an analytics visit has no file behind it. So
25647
+ * every onboard clip answered with that sentence, a flash of video and
25648
+ * nothing more, while the SAME call from outside the addon reached the
25649
+ * right provider and returned the bytes (measured on 3628,
25650
+ * 2026-09-21).
25651
+ */
25652
+ provider: string().min(1),
25653
+ /**
25637
25654
  * Which twin to fetch, on the mapping D549 15 already fixed:
25638
25655
  * `low | mid` → the sub file, `high` → the main twin. `auto` is not
25639
25656
  * accepted here for the same reason it is not accepted by
@@ -33380,7 +33397,7 @@ method(object({
33380
33397
  auth: "admin"
33381
33398
  }), method(object({ deviceId: number() }), RecordingConfigSchema, {
33382
33399
  kind: "query",
33383
- auth: "admin"
33400
+ auth: "protected"
33384
33401
  }), method(object({
33385
33402
  deviceId: number(),
33386
33403
  profile: string(),
@@ -38148,6 +38165,12 @@ function createRuntimeStateBridge(params) {
38148
38165
  };
38149
38166
  }
38150
38167
  Object.freeze({
38168
+ "accessories.getStatus": {
38169
+ capName: "accessories",
38170
+ capScope: "device",
38171
+ addonId: null,
38172
+ access: "view"
38173
+ },
38151
38174
  "accessories.setChildHidden": {
38152
38175
  capName: "accessories",
38153
38176
  capScope: "device",
@@ -38430,6 +38453,12 @@ Object.freeze({
38430
38453
  addonId: null,
38431
38454
  access: "view"
38432
38455
  },
38456
+ "airQualitySensor.getStatus": {
38457
+ capName: "air-quality-sensor",
38458
+ capScope: "device",
38459
+ addonId: null,
38460
+ access: "view"
38461
+ },
38433
38462
  "alarmPanel.arm": {
38434
38463
  capName: "alarm-panel",
38435
38464
  capScope: "device",
@@ -38442,6 +38471,12 @@ Object.freeze({
38442
38471
  addonId: null,
38443
38472
  access: "create"
38444
38473
  },
38474
+ "alarmPanel.getStatus": {
38475
+ capName: "alarm-panel",
38476
+ capScope: "device",
38477
+ addonId: null,
38478
+ access: "view"
38479
+ },
38445
38480
  "alarmPanel.trigger": {
38446
38481
  capName: "alarm-panel",
38447
38482
  capScope: "device",
@@ -38490,6 +38525,30 @@ Object.freeze({
38490
38525
  addonId: null,
38491
38526
  access: "create"
38492
38527
  },
38528
+ "ambientLightSensor.getStatus": {
38529
+ capName: "ambient-light-sensor",
38530
+ capScope: "device",
38531
+ addonId: null,
38532
+ access: "view"
38533
+ },
38534
+ "audioAnalysis.applyDeviceSettingsPatch": {
38535
+ capName: "audio-analysis",
38536
+ capScope: "device",
38537
+ addonId: null,
38538
+ access: "create"
38539
+ },
38540
+ "audioAnalysis.getDeviceLiveContribution": {
38541
+ capName: "audio-analysis",
38542
+ capScope: "device",
38543
+ addonId: null,
38544
+ access: "view"
38545
+ },
38546
+ "audioAnalysis.getDeviceSettingsContribution": {
38547
+ capName: "audio-analysis",
38548
+ capScope: "device",
38549
+ addonId: null,
38550
+ access: "view"
38551
+ },
38493
38552
  "audioAnalysis.resolveDeviceSettings": {
38494
38553
  capName: "audio-analysis",
38495
38554
  capScope: "device",
@@ -38652,6 +38711,12 @@ Object.freeze({
38652
38711
  addonId: null,
38653
38712
  access: "create"
38654
38713
  },
38714
+ "automationControl.getStatus": {
38715
+ capName: "automation-control",
38716
+ capScope: "device",
38717
+ addonId: null,
38718
+ access: "view"
38719
+ },
38655
38720
  "automationControl.trigger": {
38656
38721
  capName: "automation-control",
38657
38722
  capScope: "device",
@@ -38748,12 +38813,30 @@ Object.freeze({
38748
38813
  addonId: null,
38749
38814
  access: "create"
38750
38815
  },
38816
+ "battery.getStatus": {
38817
+ capName: "battery",
38818
+ capScope: "device",
38819
+ addonId: null,
38820
+ access: "view"
38821
+ },
38751
38822
  "battery.wakeForStream": {
38752
38823
  capName: "battery",
38753
38824
  capScope: "device",
38754
38825
  addonId: null,
38755
38826
  access: "create"
38756
38827
  },
38828
+ "binary.getStatus": {
38829
+ capName: "binary",
38830
+ capScope: "device",
38831
+ addonId: null,
38832
+ access: "view"
38833
+ },
38834
+ "brightness.getStatus": {
38835
+ capName: "brightness",
38836
+ capScope: "device",
38837
+ addonId: null,
38838
+ access: "view"
38839
+ },
38757
38840
  "brightness.setBrightness": {
38758
38841
  capName: "brightness",
38759
38842
  capScope: "device",
@@ -38868,6 +38951,30 @@ Object.freeze({
38868
38951
  addonId: null,
38869
38952
  access: "view"
38870
38953
  },
38954
+ "cameraCredentials.getStatus": {
38955
+ capName: "camera-credentials",
38956
+ capScope: "device",
38957
+ addonId: null,
38958
+ access: "view"
38959
+ },
38960
+ "cameraGridLayout.applyDeviceSettingsPatch": {
38961
+ capName: "camera-grid-layout",
38962
+ capScope: "device",
38963
+ addonId: null,
38964
+ access: "create"
38965
+ },
38966
+ "cameraGridLayout.getDeviceLiveContribution": {
38967
+ capName: "camera-grid-layout",
38968
+ capScope: "device",
38969
+ addonId: null,
38970
+ access: "view"
38971
+ },
38972
+ "cameraGridLayout.getDeviceSettingsContribution": {
38973
+ capName: "camera-grid-layout",
38974
+ capScope: "device",
38975
+ addonId: null,
38976
+ access: "view"
38977
+ },
38871
38978
  "cameraGridLayout.getLayout": {
38872
38979
  capName: "camera-grid-layout",
38873
38980
  capScope: "device",
@@ -38880,6 +38987,24 @@ Object.freeze({
38880
38987
  addonId: null,
38881
38988
  access: "create"
38882
38989
  },
38990
+ "cameraPipelineConfig.applyDeviceSettingsPatch": {
38991
+ capName: "camera-pipeline-config",
38992
+ capScope: "device",
38993
+ addonId: null,
38994
+ access: "create"
38995
+ },
38996
+ "cameraPipelineConfig.getDeviceLiveContribution": {
38997
+ capName: "camera-pipeline-config",
38998
+ capScope: "device",
38999
+ addonId: null,
39000
+ access: "view"
39001
+ },
39002
+ "cameraPipelineConfig.getDeviceSettingsContribution": {
39003
+ capName: "camera-pipeline-config",
39004
+ capScope: "device",
39005
+ addonId: null,
39006
+ access: "view"
39007
+ },
38883
39008
  "cameraStreams.getBrokerStreams": {
38884
39009
  capName: "camera-streams",
38885
39010
  capScope: "device",
@@ -38910,6 +39035,18 @@ Object.freeze({
38910
39035
  addonId: null,
38911
39036
  access: "view"
38912
39037
  },
39038
+ "carbonMonoxide.getStatus": {
39039
+ capName: "carbon-monoxide",
39040
+ capScope: "device",
39041
+ addonId: null,
39042
+ access: "view"
39043
+ },
39044
+ "climateControl.getStatus": {
39045
+ capName: "climate-control",
39046
+ capScope: "device",
39047
+ addonId: null,
39048
+ access: "view"
39049
+ },
38913
39050
  "climateControl.setFanMode": {
38914
39051
  capName: "climate-control",
38915
39052
  capScope: "device",
@@ -38958,6 +39095,12 @@ Object.freeze({
38958
39095
  addonId: null,
38959
39096
  access: "create"
38960
39097
  },
39098
+ "color.getStatus": {
39099
+ capName: "color",
39100
+ capScope: "device",
39101
+ addonId: null,
39102
+ access: "view"
39103
+ },
38961
39104
  "color.setColor": {
38962
39105
  capName: "color",
38963
39106
  capScope: "device",
@@ -38976,12 +39119,36 @@ Object.freeze({
38976
39119
  addonId: null,
38977
39120
  access: "create"
38978
39121
  },
39122
+ "connectivity.getStatus": {
39123
+ capName: "connectivity",
39124
+ capScope: "device",
39125
+ addonId: null,
39126
+ access: "view"
39127
+ },
39128
+ "consumables.getStatus": {
39129
+ capName: "consumables",
39130
+ capScope: "device",
39131
+ addonId: null,
39132
+ access: "view"
39133
+ },
38979
39134
  "consumables.reset": {
38980
39135
  capName: "consumables",
38981
39136
  capScope: "device",
38982
39137
  addonId: null,
38983
39138
  access: "delete"
38984
39139
  },
39140
+ "contact.getStatus": {
39141
+ capName: "contact",
39142
+ capScope: "device",
39143
+ addonId: null,
39144
+ access: "view"
39145
+ },
39146
+ "control.getStatus": {
39147
+ capName: "control",
39148
+ capScope: "device",
39149
+ addonId: null,
39150
+ access: "view"
39151
+ },
38985
39152
  "control.setValue": {
38986
39153
  capName: "control",
38987
39154
  capScope: "device",
@@ -39048,6 +39215,12 @@ Object.freeze({
39048
39215
  addonId: null,
39049
39216
  access: "create"
39050
39217
  },
39218
+ "cover.getStatus": {
39219
+ capName: "cover",
39220
+ capScope: "device",
39221
+ addonId: null,
39222
+ access: "view"
39223
+ },
39051
39224
  "cover.open": {
39052
39225
  capName: "cover",
39053
39226
  capScope: "device",
@@ -39180,6 +39353,12 @@ Object.freeze({
39180
39353
  addonId: null,
39181
39354
  access: "view"
39182
39355
  },
39356
+ "dayNight.getStatus": {
39357
+ capName: "day-night",
39358
+ capScope: "device",
39359
+ addonId: null,
39360
+ access: "view"
39361
+ },
39183
39362
  "dayNight.setSettings": {
39184
39363
  capName: "day-night",
39185
39364
  capScope: "device",
@@ -39270,6 +39449,24 @@ Object.freeze({
39270
39449
  addonId: null,
39271
39450
  access: "view"
39272
39451
  },
39452
+ "detectionPipeline.applyDeviceSettingsPatch": {
39453
+ capName: "detection-pipeline",
39454
+ capScope: "device",
39455
+ addonId: null,
39456
+ access: "create"
39457
+ },
39458
+ "detectionPipeline.getDeviceLiveContribution": {
39459
+ capName: "detection-pipeline",
39460
+ capScope: "device",
39461
+ addonId: null,
39462
+ access: "view"
39463
+ },
39464
+ "detectionPipeline.getDeviceSettingsContribution": {
39465
+ capName: "detection-pipeline",
39466
+ capScope: "device",
39467
+ addonId: null,
39468
+ access: "view"
39469
+ },
39273
39470
  "deviceAdoption.adopt": {
39274
39471
  capName: "device-adoption",
39275
39472
  capScope: "system",
@@ -39282,6 +39479,12 @@ Object.freeze({
39282
39479
  addonId: null,
39283
39480
  access: "view"
39284
39481
  },
39482
+ "deviceAdoption.getStatus": {
39483
+ capName: "device-adoption",
39484
+ capScope: "system",
39485
+ addonId: null,
39486
+ access: "view"
39487
+ },
39285
39488
  "deviceAdoption.listCandidateFilters": {
39286
39489
  capName: "device-adoption",
39287
39490
  capScope: "system",
@@ -39318,6 +39521,12 @@ Object.freeze({
39318
39521
  addonId: null,
39319
39522
  access: "create"
39320
39523
  },
39524
+ "deviceDiscovery.getStatus": {
39525
+ capName: "device-discovery",
39526
+ capScope: "device",
39527
+ addonId: null,
39528
+ access: "view"
39529
+ },
39321
39530
  "deviceDiscovery.listDiscovered": {
39322
39531
  capName: "device-discovery",
39323
39532
  capScope: "device",
@@ -39336,12 +39545,30 @@ Object.freeze({
39336
39545
  addonId: null,
39337
39546
  access: "create"
39338
39547
  },
39548
+ "deviceExport.applyDeviceSettingsPatch": {
39549
+ capName: "device-export",
39550
+ capScope: "system",
39551
+ addonId: null,
39552
+ access: "create"
39553
+ },
39339
39554
  "deviceExport.exposeDevice": {
39340
39555
  capName: "device-export",
39341
39556
  capScope: "system",
39342
39557
  addonId: null,
39343
39558
  access: "create"
39344
39559
  },
39560
+ "deviceExport.getDeviceLiveContribution": {
39561
+ capName: "device-export",
39562
+ capScope: "system",
39563
+ addonId: null,
39564
+ access: "view"
39565
+ },
39566
+ "deviceExport.getDeviceSettingsContribution": {
39567
+ capName: "device-export",
39568
+ capScope: "system",
39569
+ addonId: null,
39570
+ access: "view"
39571
+ },
39345
39572
  "deviceExport.getStatus": {
39346
39573
  capName: "device-export",
39347
39574
  capScope: "system",
@@ -39438,6 +39665,12 @@ Object.freeze({
39438
39665
  addonId: null,
39439
39666
  access: "create"
39440
39667
  },
39668
+ "deviceManager.applyDeviceSettingsPatch": {
39669
+ capName: "device-manager",
39670
+ capScope: "system",
39671
+ addonId: null,
39672
+ access: "create"
39673
+ },
39441
39674
  "deviceManager.applyInitialMeta": {
39442
39675
  capName: "device-manager",
39443
39676
  capScope: "system",
@@ -39540,6 +39773,12 @@ Object.freeze({
39540
39773
  addonId: null,
39541
39774
  access: "view"
39542
39775
  },
39776
+ "deviceManager.getDeviceLiveContribution": {
39777
+ capName: "device-manager",
39778
+ capScope: "system",
39779
+ addonId: null,
39780
+ access: "view"
39781
+ },
39543
39782
  "deviceManager.getDeviceLiveInfoAggregate": {
39544
39783
  capName: "device-manager",
39545
39784
  capScope: "system",
@@ -39552,6 +39791,12 @@ Object.freeze({
39552
39791
  addonId: null,
39553
39792
  access: "view"
39554
39793
  },
39794
+ "deviceManager.getDeviceSettingsContribution": {
39795
+ capName: "device-manager",
39796
+ capScope: "system",
39797
+ addonId: null,
39798
+ access: "view"
39799
+ },
39555
39800
  "deviceManager.getDeviceStatusAggregate": {
39556
39801
  capName: "device-manager",
39557
39802
  capScope: "system",
@@ -40044,6 +40289,36 @@ Object.freeze({
40044
40289
  addonId: null,
40045
40290
  access: "create"
40046
40291
  },
40292
+ "deviceStatus.getStatus": {
40293
+ capName: "device-status",
40294
+ capScope: "device",
40295
+ addonId: null,
40296
+ access: "view"
40297
+ },
40298
+ "doorbell.applyDeviceSettingsPatch": {
40299
+ capName: "doorbell",
40300
+ capScope: "device",
40301
+ addonId: null,
40302
+ access: "create"
40303
+ },
40304
+ "doorbell.getDeviceLiveContribution": {
40305
+ capName: "doorbell",
40306
+ capScope: "device",
40307
+ addonId: null,
40308
+ access: "view"
40309
+ },
40310
+ "doorbell.getDeviceSettingsContribution": {
40311
+ capName: "doorbell",
40312
+ capScope: "device",
40313
+ addonId: null,
40314
+ access: "view"
40315
+ },
40316
+ "doorbell.getStatus": {
40317
+ capName: "doorbell",
40318
+ capScope: "device",
40319
+ addonId: null,
40320
+ access: "view"
40321
+ },
40047
40322
  "embeddingEncoder.encode": {
40048
40323
  capName: "embedding-encoder",
40049
40324
  capScope: "system",
@@ -40062,6 +40337,18 @@ Object.freeze({
40062
40337
  addonId: null,
40063
40338
  access: "view"
40064
40339
  },
40340
+ "enumSensor.getStatus": {
40341
+ capName: "enum-sensor",
40342
+ capScope: "device",
40343
+ addonId: null,
40344
+ access: "view"
40345
+ },
40346
+ "eventEmitter.getStatus": {
40347
+ capName: "event-emitter",
40348
+ capScope: "device",
40349
+ addonId: null,
40350
+ access: "view"
40351
+ },
40065
40352
  "events.getEventClipUrl": {
40066
40353
  capName: "events",
40067
40354
  capScope: "device",
@@ -40182,6 +40469,12 @@ Object.freeze({
40182
40469
  addonId: null,
40183
40470
  access: "view"
40184
40471
  },
40472
+ "fanControl.getStatus": {
40473
+ capName: "fan-control",
40474
+ capScope: "device",
40475
+ addonId: null,
40476
+ access: "view"
40477
+ },
40185
40478
  "fanControl.setDirection": {
40186
40479
  capName: "fan-control",
40187
40480
  capScope: "device",
@@ -40206,6 +40499,12 @@ Object.freeze({
40206
40499
  addonId: null,
40207
40500
  access: "create"
40208
40501
  },
40502
+ "featureProbe.getStatus": {
40503
+ capName: "feature-probe",
40504
+ capScope: "device",
40505
+ addonId: null,
40506
+ access: "view"
40507
+ },
40209
40508
  "filesystemBrowse.browse": {
40210
40509
  capName: "filesystem-browse",
40211
40510
  capScope: "system",
@@ -40224,6 +40523,24 @@ Object.freeze({
40224
40523
  addonId: null,
40225
40524
  access: "view"
40226
40525
  },
40526
+ "flood.getStatus": {
40527
+ capName: "flood",
40528
+ capScope: "device",
40529
+ addonId: null,
40530
+ access: "view"
40531
+ },
40532
+ "gas.getStatus": {
40533
+ capName: "gas",
40534
+ capScope: "device",
40535
+ addonId: null,
40536
+ access: "view"
40537
+ },
40538
+ "humidifier.getStatus": {
40539
+ capName: "humidifier",
40540
+ capScope: "device",
40541
+ addonId: null,
40542
+ access: "view"
40543
+ },
40227
40544
  "humidifier.setMode": {
40228
40545
  capName: "humidifier",
40229
40546
  capScope: "device",
@@ -40242,12 +40559,30 @@ Object.freeze({
40242
40559
  addonId: null,
40243
40560
  access: "create"
40244
40561
  },
40562
+ "humiditySensor.getStatus": {
40563
+ capName: "humidity-sensor",
40564
+ capScope: "device",
40565
+ addonId: null,
40566
+ access: "view"
40567
+ },
40568
+ "image.getStatus": {
40569
+ capName: "image",
40570
+ capScope: "device",
40571
+ addonId: null,
40572
+ access: "view"
40573
+ },
40245
40574
  "imageSettings.getOptions": {
40246
40575
  capName: "image-settings",
40247
40576
  capScope: "device",
40248
40577
  addonId: null,
40249
40578
  access: "view"
40250
40579
  },
40580
+ "imageSettings.getStatus": {
40581
+ capName: "image-settings",
40582
+ capScope: "device",
40583
+ addonId: null,
40584
+ access: "view"
40585
+ },
40251
40586
  "imageSettings.setSettings": {
40252
40587
  capName: "image-settings",
40253
40588
  capScope: "device",
@@ -40320,6 +40655,12 @@ Object.freeze({
40320
40655
  addonId: null,
40321
40656
  access: "create"
40322
40657
  },
40658
+ "intercom.getStatus": {
40659
+ capName: "intercom",
40660
+ capScope: "device",
40661
+ addonId: null,
40662
+ access: "view"
40663
+ },
40323
40664
  "intercom.handleAnswer": {
40324
40665
  capName: "intercom",
40325
40666
  capScope: "device",
@@ -40356,6 +40697,12 @@ Object.freeze({
40356
40697
  addonId: null,
40357
40698
  access: "create"
40358
40699
  },
40700
+ "lawnMowerControl.getStatus": {
40701
+ capName: "lawn-mower-control",
40702
+ capScope: "device",
40703
+ addonId: null,
40704
+ access: "view"
40705
+ },
40359
40706
  "lawnMowerControl.pause": {
40360
40707
  capName: "lawn-mower-control",
40361
40708
  capScope: "device",
@@ -40638,6 +40985,12 @@ Object.freeze({
40638
40985
  addonId: null,
40639
40986
  access: "create"
40640
40987
  },
40988
+ "lockControl.getStatus": {
40989
+ capName: "lock-control",
40990
+ capScope: "device",
40991
+ addonId: null,
40992
+ access: "view"
40993
+ },
40641
40994
  "lockControl.lock": {
40642
40995
  capName: "lock-control",
40643
40996
  capScope: "device",
@@ -40686,6 +41039,12 @@ Object.freeze({
40686
41039
  addonId: null,
40687
41040
  access: "view"
40688
41041
  },
41042
+ "mediaPlayer.getStatus": {
41043
+ capName: "media-player",
41044
+ capScope: "device",
41045
+ addonId: null,
41046
+ access: "view"
41047
+ },
40689
41048
  "mediaPlayer.next": {
40690
41049
  capName: "media-player",
40691
41050
  capScope: "device",
@@ -40878,6 +41237,12 @@ Object.freeze({
40878
41237
  addonId: null,
40879
41238
  access: "create"
40880
41239
  },
41240
+ "motion.getStatus": {
41241
+ capName: "motion",
41242
+ capScope: "device",
41243
+ addonId: null,
41244
+ access: "view"
41245
+ },
40881
41246
  "motion.isDetected": {
40882
41247
  capName: "motion",
40883
41248
  capScope: "device",
@@ -40890,6 +41255,24 @@ Object.freeze({
40890
41255
  addonId: null,
40891
41256
  access: "create"
40892
41257
  },
41258
+ "motionDetection.applyDeviceSettingsPatch": {
41259
+ capName: "motion-detection",
41260
+ capScope: "device",
41261
+ addonId: null,
41262
+ access: "create"
41263
+ },
41264
+ "motionDetection.getDeviceLiveContribution": {
41265
+ capName: "motion-detection",
41266
+ capScope: "device",
41267
+ addonId: null,
41268
+ access: "view"
41269
+ },
41270
+ "motionDetection.getDeviceSettingsContribution": {
41271
+ capName: "motion-detection",
41272
+ capScope: "device",
41273
+ addonId: null,
41274
+ access: "view"
41275
+ },
40893
41276
  "motionDetection.removeCamera": {
40894
41277
  capName: "motion-detection",
40895
41278
  capScope: "device",
@@ -40902,6 +41285,12 @@ Object.freeze({
40902
41285
  addonId: null,
40903
41286
  access: "delete"
40904
41287
  },
41288
+ "motionTrigger.getStatus": {
41289
+ capName: "motion-trigger",
41290
+ capScope: "device",
41291
+ addonId: null,
41292
+ access: "view"
41293
+ },
40905
41294
  "motionTrigger.setMotionTrigger": {
40906
41295
  capName: "motion-trigger",
40907
41296
  capScope: "device",
@@ -40914,6 +41303,12 @@ Object.freeze({
40914
41303
  addonId: null,
40915
41304
  access: "view"
40916
41305
  },
41306
+ "motionZones.getStatus": {
41307
+ capName: "motion-zones",
41308
+ capScope: "device",
41309
+ addonId: null,
41310
+ access: "view"
41311
+ },
40917
41312
  "motionZones.setZone": {
40918
41313
  capName: "motion-zones",
40919
41314
  capScope: "device",
@@ -40974,6 +41369,12 @@ Object.freeze({
40974
41369
  addonId: null,
40975
41370
  access: "view"
40976
41371
  },
41372
+ "nativeObjectDetection.getStatus": {
41373
+ capName: "native-object-detection",
41374
+ capScope: "device",
41375
+ addonId: null,
41376
+ access: "view"
41377
+ },
40977
41378
  "nativeObjectDetection.setEnabled": {
40978
41379
  capName: "native-object-detection",
40979
41380
  capScope: "device",
@@ -40992,6 +41393,12 @@ Object.freeze({
40992
41393
  addonId: null,
40993
41394
  access: "view"
40994
41395
  },
41396
+ "navigation.getStatus": {
41397
+ capName: "navigation",
41398
+ capScope: "device",
41399
+ addonId: null,
41400
+ access: "view"
41401
+ },
40995
41402
  "navigation.goToPoint": {
40996
41403
  capName: "navigation",
40997
41404
  capScope: "device",
@@ -41076,6 +41483,12 @@ Object.freeze({
41076
41483
  addonId: null,
41077
41484
  access: "create"
41078
41485
  },
41486
+ "networkLink.getStatus": {
41487
+ capName: "network-link",
41488
+ capScope: "device",
41489
+ addonId: null,
41490
+ access: "view"
41491
+ },
41079
41492
  "networkQuality.getAllStats": {
41080
41493
  capName: "network-quality",
41081
41494
  capScope: "system",
@@ -41334,18 +41747,36 @@ Object.freeze({
41334
41747
  addonId: null,
41335
41748
  access: "create"
41336
41749
  },
41750
+ "notifier.getStatus": {
41751
+ capName: "notifier",
41752
+ capScope: "device",
41753
+ addonId: null,
41754
+ access: "view"
41755
+ },
41337
41756
  "notifier.send": {
41338
41757
  capName: "notifier",
41339
41758
  capScope: "device",
41340
41759
  addonId: null,
41341
41760
  access: "create"
41342
41761
  },
41762
+ "numericSensor.getStatus": {
41763
+ capName: "numeric-sensor",
41764
+ capScope: "device",
41765
+ addonId: null,
41766
+ access: "view"
41767
+ },
41343
41768
  "oauthIntegration.getDescriptor": {
41344
41769
  capName: "oauth-integration",
41345
41770
  capScope: "system",
41346
41771
  addonId: null,
41347
41772
  access: "view"
41348
41773
  },
41774
+ "osd.getStatus": {
41775
+ capName: "osd",
41776
+ capScope: "device",
41777
+ addonId: null,
41778
+ access: "view"
41779
+ },
41349
41780
  "osd.setOverlay": {
41350
41781
  capName: "osd",
41351
41782
  capScope: "device",
@@ -41418,6 +41849,12 @@ Object.freeze({
41418
41849
  addonId: null,
41419
41850
  access: "create"
41420
41851
  },
41852
+ "petFeeder.getStatus": {
41853
+ capName: "pet-feeder",
41854
+ capScope: "device",
41855
+ addonId: null,
41856
+ access: "view"
41857
+ },
41421
41858
  "petFeeder.markFoodReplenished": {
41422
41859
  capName: "pet-feeder",
41423
41860
  capScope: "device",
@@ -41460,6 +41897,12 @@ Object.freeze({
41460
41897
  addonId: null,
41461
41898
  access: "create"
41462
41899
  },
41900
+ "pipelineAnalytics.applyDeviceSettingsPatch": {
41901
+ capName: "pipeline-analytics",
41902
+ capScope: "device",
41903
+ addonId: null,
41904
+ access: "create"
41905
+ },
41463
41906
  "pipelineAnalytics.cancelRelocateMedia": {
41464
41907
  capName: "pipeline-analytics",
41465
41908
  capScope: "device",
@@ -41526,6 +41969,18 @@ Object.freeze({
41526
41969
  addonId: null,
41527
41970
  access: "view"
41528
41971
  },
41972
+ "pipelineAnalytics.getDeviceLiveContribution": {
41973
+ capName: "pipeline-analytics",
41974
+ capScope: "device",
41975
+ addonId: null,
41976
+ access: "view"
41977
+ },
41978
+ "pipelineAnalytics.getDeviceSettingsContribution": {
41979
+ capName: "pipeline-analytics",
41980
+ capScope: "device",
41981
+ addonId: null,
41982
+ access: "view"
41983
+ },
41529
41984
  "pipelineAnalytics.getEventDensity": {
41530
41985
  capName: "pipeline-analytics",
41531
41986
  capScope: "device",
@@ -42096,6 +42551,12 @@ Object.freeze({
42096
42551
  addonId: null,
42097
42552
  access: "view"
42098
42553
  },
42554
+ "pipelineOrchestrator.applyDeviceSettingsPatch": {
42555
+ capName: "pipeline-orchestrator",
42556
+ capScope: "system",
42557
+ addonId: null,
42558
+ access: "create"
42559
+ },
42099
42560
  "pipelineOrchestrator.assignAudio": {
42100
42561
  capName: "pipeline-orchestrator",
42101
42562
  capScope: "system",
@@ -42186,6 +42647,18 @@ Object.freeze({
42186
42647
  addonId: null,
42187
42648
  access: "view"
42188
42649
  },
42650
+ "pipelineOrchestrator.getDeviceLiveContribution": {
42651
+ capName: "pipeline-orchestrator",
42652
+ capScope: "system",
42653
+ addonId: null,
42654
+ access: "view"
42655
+ },
42656
+ "pipelineOrchestrator.getDeviceSettingsContribution": {
42657
+ capName: "pipeline-orchestrator",
42658
+ capScope: "system",
42659
+ addonId: null,
42660
+ access: "view"
42661
+ },
42189
42662
  "pipelineOrchestrator.getGlobalMetrics": {
42190
42663
  capName: "pipeline-orchestrator",
42191
42664
  capScope: "system",
@@ -42594,12 +43067,36 @@ Object.freeze({
42594
43067
  addonId: null,
42595
43068
  access: "view"
42596
43069
  },
43070
+ "powerMeter.getStatus": {
43071
+ capName: "power-meter",
43072
+ capScope: "device",
43073
+ addonId: null,
43074
+ access: "view"
43075
+ },
43076
+ "presence.getStatus": {
43077
+ capName: "presence",
43078
+ capScope: "device",
43079
+ addonId: null,
43080
+ access: "view"
43081
+ },
43082
+ "pressureSensor.getStatus": {
43083
+ capName: "pressure-sensor",
43084
+ capScope: "device",
43085
+ addonId: null,
43086
+ access: "view"
43087
+ },
42597
43088
  "privacyMask.getOptions": {
42598
43089
  capName: "privacy-mask",
42599
43090
  capScope: "device",
42600
43091
  addonId: null,
42601
43092
  access: "view"
42602
43093
  },
43094
+ "privacyMask.getStatus": {
43095
+ capName: "privacy-mask",
43096
+ capScope: "device",
43097
+ addonId: null,
43098
+ access: "view"
43099
+ },
42603
43100
  "privacyMask.setAudioEnabled": {
42604
43101
  capName: "privacy-mask",
42605
43102
  capScope: "device",
@@ -42660,6 +43157,12 @@ Object.freeze({
42660
43157
  addonId: null,
42661
43158
  access: "view"
42662
43159
  },
43160
+ "ptz.getStatus": {
43161
+ capName: "ptz",
43162
+ capScope: "device",
43163
+ addonId: null,
43164
+ access: "view"
43165
+ },
42663
43166
  "ptz.goHome": {
42664
43167
  capName: "ptz",
42665
43168
  capScope: "device",
@@ -42738,6 +43241,12 @@ Object.freeze({
42738
43241
  addonId: null,
42739
43242
  access: "create"
42740
43243
  },
43244
+ "recording.applyDeviceSettingsPatch": {
43245
+ capName: "recording",
43246
+ capScope: "system",
43247
+ addonId: null,
43248
+ access: "create"
43249
+ },
42741
43250
  "recording.cancelRelocateJob": {
42742
43251
  capName: "recording",
42743
43252
  capScope: "system",
@@ -42786,6 +43295,18 @@ Object.freeze({
42786
43295
  addonId: null,
42787
43296
  access: "view"
42788
43297
  },
43298
+ "recording.getDeviceLiveContribution": {
43299
+ capName: "recording",
43300
+ capScope: "system",
43301
+ addonId: null,
43302
+ access: "view"
43303
+ },
43304
+ "recording.getDeviceSettingsContribution": {
43305
+ capName: "recording",
43306
+ capScope: "system",
43307
+ addonId: null,
43308
+ access: "view"
43309
+ },
42789
43310
  "recording.getPlacement": {
42790
43311
  capName: "recording",
42791
43312
  capScope: "system",
@@ -42804,6 +43325,12 @@ Object.freeze({
42804
43325
  addonId: null,
42805
43326
  access: "view"
42806
43327
  },
43328
+ "recording.getStatus": {
43329
+ capName: "recording",
43330
+ capScope: "system",
43331
+ addonId: null,
43332
+ access: "view"
43333
+ },
42807
43334
  "recording.getStorageMigrationMoveStatus": {
42808
43335
  capName: "recording",
42809
43336
  capScope: "system",
@@ -43008,6 +43535,12 @@ Object.freeze({
43008
43535
  addonId: null,
43009
43536
  access: "delete"
43010
43537
  },
43538
+ "sceneMonitor.getStatus": {
43539
+ capName: "scene-monitor",
43540
+ capScope: "device",
43541
+ addonId: null,
43542
+ access: "view"
43543
+ },
43011
43544
  "sceneMonitor.listScenes": {
43012
43545
  capName: "scene-monitor",
43013
43546
  capScope: "device",
@@ -43038,6 +43571,12 @@ Object.freeze({
43038
43571
  addonId: null,
43039
43572
  access: "create"
43040
43573
  },
43574
+ "scriptRunner.getStatus": {
43575
+ capName: "script-runner",
43576
+ capScope: "device",
43577
+ addonId: null,
43578
+ access: "view"
43579
+ },
43041
43580
  "scriptRunner.run": {
43042
43581
  capName: "script-runner",
43043
43582
  capScope: "device",
@@ -43170,6 +43709,12 @@ Object.freeze({
43170
43709
  addonId: null,
43171
43710
  access: "create"
43172
43711
  },
43712
+ "smoke.getStatus": {
43713
+ capName: "smoke",
43714
+ capScope: "device",
43715
+ addonId: null,
43716
+ access: "view"
43717
+ },
43173
43718
  "smtpProvider.getStatus": {
43174
43719
  capName: "smtp-provider",
43175
43720
  capScope: "system",
@@ -43188,12 +43733,30 @@ Object.freeze({
43188
43733
  addonId: null,
43189
43734
  access: "view"
43190
43735
  },
43736
+ "snapshot.applyDeviceSettingsPatch": {
43737
+ capName: "snapshot",
43738
+ capScope: "device",
43739
+ addonId: null,
43740
+ access: "create"
43741
+ },
43191
43742
  "snapshot.getDebugState": {
43192
43743
  capName: "snapshot",
43193
43744
  capScope: "device",
43194
43745
  addonId: null,
43195
43746
  access: "view"
43196
43747
  },
43748
+ "snapshot.getDeviceLiveContribution": {
43749
+ capName: "snapshot",
43750
+ capScope: "device",
43751
+ addonId: null,
43752
+ access: "view"
43753
+ },
43754
+ "snapshot.getDeviceSettingsContribution": {
43755
+ capName: "snapshot",
43756
+ capScope: "device",
43757
+ addonId: null,
43758
+ access: "view"
43759
+ },
43197
43760
  "snapshot.getSnapshot": {
43198
43761
  capName: "snapshot",
43199
43762
  capScope: "device",
@@ -43212,6 +43775,12 @@ Object.freeze({
43212
43775
  addonId: null,
43213
43776
  access: "view"
43214
43777
  },
43778
+ "snapshot.getStatus": {
43779
+ capName: "snapshot",
43780
+ capScope: "device",
43781
+ addonId: null,
43782
+ access: "view"
43783
+ },
43215
43784
  "snapshot.invalidateCache": {
43216
43785
  capName: "snapshot",
43217
43786
  capScope: "device",
@@ -43548,6 +44117,12 @@ Object.freeze({
43548
44117
  addonId: null,
43549
44118
  access: "create"
43550
44119
  },
44120
+ "streamBroker.applyDeviceSettingsPatch": {
44121
+ capName: "stream-broker",
44122
+ capScope: "system",
44123
+ addonId: null,
44124
+ access: "create"
44125
+ },
43551
44126
  "streamBroker.assignProfile": {
43552
44127
  capName: "stream-broker",
43553
44128
  capScope: "system",
@@ -43584,6 +44159,18 @@ Object.freeze({
43584
44159
  addonId: null,
43585
44160
  access: "view"
43586
44161
  },
44162
+ "streamBroker.getDeviceLiveContribution": {
44163
+ capName: "stream-broker",
44164
+ capScope: "system",
44165
+ addonId: null,
44166
+ access: "view"
44167
+ },
44168
+ "streamBroker.getDeviceSettingsContribution": {
44169
+ capName: "stream-broker",
44170
+ capScope: "system",
44171
+ addonId: null,
44172
+ access: "view"
44173
+ },
43587
44174
  "streamBroker.getPreBufferInfo": {
43588
44175
  capName: "stream-broker",
43589
44176
  capScope: "system",
@@ -43782,6 +44369,12 @@ Object.freeze({
43782
44369
  addonId: null,
43783
44370
  access: "view"
43784
44371
  },
44372
+ "streamParams.getStatus": {
44373
+ capName: "stream-params",
44374
+ capScope: "device",
44375
+ addonId: null,
44376
+ access: "view"
44377
+ },
43785
44378
  "streamParams.setProfile": {
43786
44379
  capName: "stream-params",
43787
44380
  capScope: "device",
@@ -43794,6 +44387,12 @@ Object.freeze({
43794
44387
  addonId: null,
43795
44388
  access: "view"
43796
44389
  },
44390
+ "switch.getStatus": {
44391
+ capName: "switch",
44392
+ capScope: "device",
44393
+ addonId: null,
44394
+ access: "view"
44395
+ },
43797
44396
  "switch.setState": {
43798
44397
  capName: "switch",
43799
44398
  capScope: "device",
@@ -43890,6 +44489,18 @@ Object.freeze({
43890
44489
  addonId: null,
43891
44490
  access: "create"
43892
44491
  },
44492
+ "tamper.getStatus": {
44493
+ capName: "tamper",
44494
+ capScope: "device",
44495
+ addonId: null,
44496
+ access: "view"
44497
+ },
44498
+ "temperatureSensor.getStatus": {
44499
+ capName: "temperature-sensor",
44500
+ capScope: "device",
44501
+ addonId: null,
44502
+ access: "view"
44503
+ },
43893
44504
  "terminalSession.adoptLegacyMonitor": {
43894
44505
  capName: "terminal-session",
43895
44506
  capScope: "system",
@@ -43986,6 +44597,12 @@ Object.freeze({
43986
44597
  addonId: null,
43987
44598
  access: "view"
43988
44599
  },
44600
+ "update.getStatus": {
44601
+ capName: "update",
44602
+ capScope: "device",
44603
+ addonId: null,
44604
+ access: "view"
44605
+ },
43989
44606
  "update.installUpdate": {
43990
44607
  capName: "update",
43991
44608
  capScope: "device",
@@ -44208,6 +44825,12 @@ Object.freeze({
44208
44825
  addonId: null,
44209
44826
  access: "create"
44210
44827
  },
44828
+ "vacuumControl.getStatus": {
44829
+ capName: "vacuum-control",
44830
+ capScope: "device",
44831
+ addonId: null,
44832
+ access: "view"
44833
+ },
44211
44834
  "vacuumControl.locate": {
44212
44835
  capName: "vacuum-control",
44213
44836
  capScope: "device",
@@ -44250,6 +44873,12 @@ Object.freeze({
44250
44873
  addonId: null,
44251
44874
  access: "create"
44252
44875
  },
44876
+ "valve.getStatus": {
44877
+ capName: "valve",
44878
+ capScope: "device",
44879
+ addonId: null,
44880
+ access: "view"
44881
+ },
44253
44882
  "valve.open": {
44254
44883
  capName: "valve",
44255
44884
  capScope: "device",
@@ -44322,6 +44951,12 @@ Object.freeze({
44322
44951
  addonId: null,
44323
44952
  access: "create"
44324
44953
  },
44954
+ "vibration.getStatus": {
44955
+ capName: "vibration",
44956
+ capScope: "device",
44957
+ addonId: null,
44958
+ access: "view"
44959
+ },
44325
44960
  "videoclips.getClipPlayback": {
44326
44961
  capName: "videoclips",
44327
44962
  capScope: "device",
@@ -44358,6 +44993,12 @@ Object.freeze({
44358
44993
  addonId: null,
44359
44994
  access: "view"
44360
44995
  },
44996
+ "waterHeater.getStatus": {
44997
+ capName: "water-heater",
44998
+ capScope: "device",
44999
+ addonId: null,
45000
+ access: "view"
45001
+ },
44361
45002
  "waterHeater.setAway": {
44362
45003
  capName: "water-heater",
44363
45004
  capScope: "device",
@@ -44376,6 +45017,12 @@ Object.freeze({
44376
45017
  addonId: null,
44377
45018
  access: "create"
44378
45019
  },
45020
+ "weather.getStatus": {
45021
+ capName: "weather",
45022
+ capScope: "device",
45023
+ addonId: null,
45024
+ access: "view"
45025
+ },
44379
45026
  "webrtcSession.addIceCandidate": {
44380
45027
  capName: "webrtc-session",
44381
45028
  capScope: "device",
@@ -76163,7 +76810,7 @@ var require_lz4 = /* @__PURE__ */ __commonJSMin(((exports) => {
76163
76810
  };
76164
76811
  }));
76165
76812
  //#endregion
76166
- //#region node_modules/@apocaliss92/nodelink-js/dist/chunk-5PJ6XPBF.js
76813
+ //#region node_modules/@apocaliss92/nodelink-js/dist/chunk-GNC5U3YA.js
76167
76814
  var import_fxp = require_fxp();
76168
76815
  var import_lz4 = /* @__PURE__ */ __toESM(require_lz4(), 1);
76169
76816
  function encodeHeader(h) {
@@ -80328,6 +80975,7 @@ var BaichuanClient = class _BaichuanClient extends EventEmitter {
80328
80975
  lockedChannelId = frame.header.channelId;
80329
80976
  lockedStreamType = frame.header.streamType;
80330
80977
  }
80978
+ params.onChunk?.(decrypted);
80331
80979
  chunks.push(decrypted);
80332
80980
  armIdleFinish();
80333
80981
  if (frame.header.responseCode === 201) finish(Buffer.concat(chunks));
@@ -92334,7 +92982,8 @@ ${stderr}`));
92334
92982
  payloadXml,
92335
92983
  streamType: 0,
92336
92984
  timeoutMs,
92337
- ...params.idleTimeoutMs != null ? { idleTimeoutMs: params.idleTimeoutMs } : {}
92985
+ ...params.idleTimeoutMs != null ? { idleTimeoutMs: params.idleTimeoutMs } : {},
92986
+ ...params.onChunk ? { onChunk: params.onChunk } : {}
92338
92987
  });
92339
92988
  } catch (e) {
92340
92989
  trace(`download failed: ${e instanceof Error ? e.message : String(e)}`);
@@ -148398,6 +149047,123 @@ function createClipPlaybackHandler(deps) {
148398
149047
  }).pipe(res);
148399
149048
  };
148400
149049
  }
149050
+ /**
149051
+ * `/addon/provider-reolink/clip-stream/<deviceId>/<clipKey>.mp4` — the same
149052
+ * recording as `clip/…`, served as it ARRIVES from the camera.
149053
+ *
149054
+ * **Not `Range`-able, and it says so.** The response has no length: it is a
149055
+ * fragmented MP4 written by an ffmpeg that is still reading the camera when
149056
+ * the first byte leaves. So there is no `content-length`, `accept-ranges` is
149057
+ * `none`, a `Range` header is IGNORED (a 206 over something with no length
149058
+ * would be a lie), and the peer may not cache it (`no-store`) — a stream is
149059
+ * not an immutable artifact, the file route is. The status line is written
149060
+ * on the first muxed byte, never before: until then every refusal still owns
149061
+ * it, and a camera that serves nothing answers a status with a reason, not an
149062
+ * empty 200 (D391).
149063
+ *
149064
+ * `HEAD` answers the headers and touches no camera.
149065
+ */
149066
+ var CLIP_STREAM_PREFIX = "clip-stream";
149067
+ var STREAM_HEADERS = {
149068
+ "content-type": "video/mp4",
149069
+ "accept-ranges": "none",
149070
+ "cache-control": "no-store",
149071
+ ["x-camstack-clip-transport"]: "forward-only"
149072
+ };
149073
+ /** The status a refusal answers. `404` = nothing here to serve; `409` = a
149074
+ * clip that exists and cannot be served right now; `502` = the camera. */
149075
+ function streamRefusalStatus(code) {
149076
+ switch (code) {
149077
+ case "unknown-device":
149078
+ case "no-catalog-row": return 404;
149079
+ case "sleeping":
149080
+ case "uid-missing":
149081
+ case "clip-in-progress": return 409;
149082
+ case "camera-refused":
149083
+ case "fetch-failed": return 502;
149084
+ }
149085
+ }
149086
+ function createClipStreamHandler(deps) {
149087
+ return async (req, res) => {
149088
+ if (req.method !== "GET" && req.method !== "HEAD") {
149089
+ res.writeHead(405, { allow: "GET, HEAD" });
149090
+ res.end();
149091
+ return;
149092
+ }
149093
+ const match = MEDIA_PATH.exec(pathnameOf(req));
149094
+ if (match === null) {
149095
+ res.writeHead(400);
149096
+ res.end();
149097
+ return;
149098
+ }
149099
+ const deviceId = Number(match[1]);
149100
+ const clipKey = match[2] ?? "";
149101
+ if (req.method === "HEAD") {
149102
+ res.writeHead(200, STREAM_HEADERS);
149103
+ res.end();
149104
+ return;
149105
+ }
149106
+ if (req.headers.range !== void 0) deps.logger.debug("videoclips: a Range header on the clip stream route is ignored", {
149107
+ tags: { deviceId },
149108
+ meta: {
149109
+ clipKey,
149110
+ range: req.headers.range
149111
+ }
149112
+ });
149113
+ const sink = {
149114
+ begin: () => {
149115
+ res.writeHead(200, STREAM_HEADERS);
149116
+ },
149117
+ write: (chunk) => res.write(chunk),
149118
+ onceDrain: (listener) => {
149119
+ res.once("drain", listener);
149120
+ },
149121
+ onceClose: (listener) => {
149122
+ res.once("close", listener);
149123
+ },
149124
+ end: () => {
149125
+ res.end();
149126
+ },
149127
+ destroy: () => {
149128
+ res.destroy();
149129
+ }
149130
+ };
149131
+ let outcome;
149132
+ try {
149133
+ outcome = await deps.stream(deviceId, clipKey, sink);
149134
+ } catch (err) {
149135
+ deps.logger.warn("videoclips: the clip stream route threw", {
149136
+ tags: { deviceId },
149137
+ meta: {
149138
+ clipKey,
149139
+ branch: "stream-threw",
149140
+ error: err instanceof Error ? err.message : String(err)
149141
+ }
149142
+ });
149143
+ if (res.headersSent) res.destroy();
149144
+ else {
149145
+ res.writeHead(500);
149146
+ res.end();
149147
+ }
149148
+ return;
149149
+ }
149150
+ if (outcome.kind !== "refused") return;
149151
+ deps.logger.info("videoclips: clip stream not served", {
149152
+ tags: { deviceId },
149153
+ meta: {
149154
+ clipKey,
149155
+ code: outcome.code,
149156
+ reason: outcome.detail,
149157
+ status: streamRefusalStatus(outcome.code)
149158
+ }
149159
+ });
149160
+ res.writeHead(streamRefusalStatus(outcome.code), {
149161
+ [CLIP_STILL_REASON_HEADER]: outcome.detail.slice(0, 500),
149162
+ [CLIP_STILL_REASON_CODE_HEADER]: outcome.code
149163
+ });
149164
+ res.end();
149165
+ };
149166
+ }
148401
149167
  //#endregion
148402
149168
  //#region src/clips/clip-mux.ts
148403
149169
  /** A clip that has not transferred by here is not going to. */
@@ -148555,6 +149321,94 @@ function describeMuxFailure(run) {
148555
149321
  if (run.code !== 0) return `mux-failed (exit ${String(run.code)}): ${run.stderr.slice(0, 300)}`;
148556
149322
  return null;
148557
149323
  }
149324
+ /** The fragmented container a stream is muxed into — see the file header. */
149325
+ var CLIP_STREAM_MOVFLAGS = "frag_keyframe+empty_moov+default_base_moof";
149326
+ /**
149327
+ * The rate the probe measured, through the library's one derivation.
149328
+ *
149329
+ * `estimateVideoTiming` falls back to the info header when the timestamps
149330
+ * cannot say; that fallback is REFUSED here (`fpsSource: 'unmeasured'`), as
149331
+ * D567 says, so a guess is never handed to `-r` under a measurement's name.
149332
+ */
149333
+ function probeClipStreamRate(timestampsUs) {
149334
+ const timing = estimateVideoTiming({ timestampsUs });
149335
+ if (timing.fpsSource !== "timestamps" || timing.fps === null) return {
149336
+ fps: null,
149337
+ fpsSource: "unmeasured",
149338
+ accessUnits: timestampsUs.length
149339
+ };
149340
+ return {
149341
+ fps: timing.fps,
149342
+ fpsSource: "timestamps",
149343
+ accessUnits: timestampsUs.length
149344
+ };
149345
+ }
149346
+ /**
149347
+ * One ffmpeg, stdin to stdout, video only, fragmented MP4.
149348
+ *
149349
+ * Mirrors `buildClipMuxArgs` where a stream can: the demuxer is named from
149350
+ * the DETECTED codec (the BcMedia header lies on a hub child), `-r` is an
149351
+ * input option before its own `-i`, and the video is copied. Where it
149352
+ * cannot — `+faststart`, the audio input — the file header says why.
149353
+ */
149354
+ function buildClipStreamMuxArgs(input) {
149355
+ const demuxer = input.videoType === "H265" ? "hevc" : "h264";
149356
+ return [
149357
+ "-hide_banner",
149358
+ "-loglevel",
149359
+ "error",
149360
+ "-nostdin",
149361
+ ...input.fps !== null && Number.isFinite(input.fps) && input.fps > 0 ? ["-r", String(input.fps)] : [],
149362
+ "-f",
149363
+ demuxer,
149364
+ "-i",
149365
+ "pipe:0",
149366
+ "-c:v",
149367
+ "copy",
149368
+ "-movflags",
149369
+ CLIP_STREAM_MOVFLAGS,
149370
+ "-f",
149371
+ "mp4",
149372
+ "pipe:1"
149373
+ ];
149374
+ }
149375
+ /**
149376
+ * The D447 rule as a state machine, so it can be proved without a socket.
149377
+ *
149378
+ * A `false` write ARMS the timer; further `false` writes while it is armed do
149379
+ * not push the deadline out. `drain` — the hop's own word that it took what
149380
+ * it held — disarms it. Stall fires at most once, and a disposed gate is
149381
+ * silent for ever.
149382
+ */
149383
+ function createDrainGate(deps) {
149384
+ let handle = null;
149385
+ let stalled = false;
149386
+ const disarm = () => {
149387
+ if (handle !== null) clearTimeout(handle);
149388
+ handle = null;
149389
+ };
149390
+ return {
149391
+ wrote(accepted) {
149392
+ if (accepted || handle !== null || stalled) return;
149393
+ handle = setTimeout(() => {
149394
+ handle = null;
149395
+ if (stalled) return;
149396
+ stalled = true;
149397
+ deps.onStall();
149398
+ }, deps.timeoutMs);
149399
+ },
149400
+ drained() {
149401
+ disarm();
149402
+ },
149403
+ waiting() {
149404
+ return handle !== null;
149405
+ },
149406
+ dispose() {
149407
+ disarm();
149408
+ stalled = true;
149409
+ }
149410
+ };
149411
+ }
148558
149412
  //#endregion
148559
149413
  //#region src/clips/clip-truncation.ts
148560
149414
  /**
@@ -148601,6 +149455,500 @@ function judgeClipDuration(input) {
148601
149455
  };
148602
149456
  }
148603
149457
  //#endregion
149458
+ //#region src/clips/clip-stream.ts
149459
+ var defaultDecoder = (handlers) => new BcMediaAnnexBDecoder({
149460
+ strict: false,
149461
+ onVideoAccessUnit: handlers.onVideoAccessUnit,
149462
+ onAudioFrame: () => handlers.onAudioFrame()
149463
+ });
149464
+ var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
149465
+ function createClipStreamProducer(deps) {
149466
+ const sleep = deps.sleep ?? defaultSleep;
149467
+ const createDecoder = deps.createDecoder ?? defaultDecoder;
149468
+ const drainTimeoutMs = deps.drainTimeoutMs ?? 1e4;
149469
+ const flushTimeoutMs = deps.flushTimeoutMs ?? 15e3;
149470
+ const lanes = /* @__PURE__ */ new Map();
149471
+ const activeByHost = /* @__PURE__ */ new Map();
149472
+ const runAttempt = (input, attempt) => new ClipStreamRun({
149473
+ logger: deps.logger,
149474
+ spawnMux: deps.spawnMux,
149475
+ createDecoder,
149476
+ drainTimeoutMs,
149477
+ flushTimeoutMs
149478
+ }, input, attempt).run();
149479
+ const runAttempts = async (input) => {
149480
+ const { deviceId } = input.device;
149481
+ let lastDetail = "no reason given";
149482
+ for (let attempt = 1; attempt <= 3; attempt += 1) {
149483
+ if (attempt > 1) await sleep(500 * 2 ** (attempt - 2));
149484
+ const result = await runAttempt(input, attempt);
149485
+ if (result.kind !== "no-media") return result;
149486
+ lastDetail = result.detail;
149487
+ deps.logger.warn("videoclips: a clip stream produced no media — retrying", {
149488
+ tags: { deviceId },
149489
+ meta: {
149490
+ clipKey: input.row.clipKey,
149491
+ twin: input.twin,
149492
+ branch: "stream-no-media",
149493
+ attempt,
149494
+ error: lastDetail
149495
+ }
149496
+ });
149497
+ }
149498
+ deps.logger.warn("videoclips: the camera never delivered this clip stream", {
149499
+ tags: { deviceId },
149500
+ meta: {
149501
+ clipKey: input.row.clipKey,
149502
+ twin: input.twin,
149503
+ branch: "camera-refused",
149504
+ attempts: 3,
149505
+ error: lastDetail
149506
+ }
149507
+ });
149508
+ return {
149509
+ kind: "refused",
149510
+ code: "camera-refused",
149511
+ detail: `clip stream never landed after 3 attempts — ${lastDetail}`,
149512
+ attempts: 3
149513
+ };
149514
+ };
149515
+ return {
149516
+ active: (hostKey) => activeByHost.get(hostKey) ?? 0,
149517
+ stream: (input) => {
149518
+ const { hostKey, deviceId } = input.device;
149519
+ const ahead = activeByHost.get(hostKey) ?? 0;
149520
+ if (ahead > 0) deps.logger.info("videoclips: a clip stream is queued behind another on this host", {
149521
+ tags: { deviceId },
149522
+ meta: {
149523
+ clipKey: input.row.clipKey,
149524
+ twin: input.twin,
149525
+ branch: "stream-queued",
149526
+ ahead
149527
+ }
149528
+ });
149529
+ activeByHost.set(hostKey, ahead + 1);
149530
+ const run = (lanes.get(hostKey) ?? Promise.resolve()).then(() => runAttempts(input)).finally(() => {
149531
+ const left = (activeByHost.get(hostKey) ?? 1) - 1;
149532
+ if (left <= 0) activeByHost.delete(hostKey);
149533
+ else activeByHost.set(hostKey, left);
149534
+ });
149535
+ lanes.set(hostKey, run.then(() => void 0, () => void 0));
149536
+ return run;
149537
+ }
149538
+ };
149539
+ }
149540
+ /**
149541
+ * One transfer: camera → decoder → ffmpeg → peer, with a cut on every hop.
149542
+ *
149543
+ * A class because the run is a state machine with a dozen fields the chunk
149544
+ * callback and the download promise both touch; passing them around as
149545
+ * closures over `let`s was less readable, not more immutable.
149546
+ */
149547
+ var ClipStreamRun = class {
149548
+ deps;
149549
+ input;
149550
+ attempt;
149551
+ accessUnits = 0;
149552
+ audioFrames = 0;
149553
+ timestampsUs = [];
149554
+ /** Access units held until the rate is probed — the frames in flight. */
149555
+ probe = [];
149556
+ mux = null;
149557
+ muxStderr = "";
149558
+ muxClosed = null;
149559
+ rate = {
149560
+ fps: null,
149561
+ fpsSource: "unmeasured",
149562
+ accessUnits: 0
149563
+ };
149564
+ videoType = null;
149565
+ begun = false;
149566
+ ended = false;
149567
+ bytesOut = 0;
149568
+ cut = null;
149569
+ stdinGate;
149570
+ peerGate;
149571
+ tags;
149572
+ constructor(deps, input, attempt) {
149573
+ this.deps = deps;
149574
+ this.input = input;
149575
+ this.attempt = attempt;
149576
+ this.tags = { deviceId: input.device.deviceId };
149577
+ this.stdinGate = createDrainGate({
149578
+ timeoutMs: deps.drainTimeoutMs,
149579
+ onStall: () => this.sever("mux-stalled", "ffmpeg stopped taking access units")
149580
+ });
149581
+ this.peerGate = createDrainGate({
149582
+ timeoutMs: deps.drainTimeoutMs,
149583
+ onStall: () => this.sever("peer-stalled", "the peer stopped taking bytes")
149584
+ });
149585
+ }
149586
+ async run() {
149587
+ const { device, row, fileName, twin, sink } = this.input;
149588
+ sink.onceClose(() => {
149589
+ if (!this.ended) this.sever("peer-closed", "the peer closed the connection");
149590
+ });
149591
+ const decoder = this.deps.createDecoder({
149592
+ onVideoAccessUnit: (unit) => this.onAccessUnit(unit),
149593
+ onAudioFrame: () => {
149594
+ this.audioFrames += 1;
149595
+ }
149596
+ });
149597
+ this.deps.logger.info("videoclips: streaming a clip file from the camera", {
149598
+ tags: this.tags,
149599
+ meta: {
149600
+ clipKey: row.clipKey,
149601
+ twin,
149602
+ fileName,
149603
+ startMs: row.startMs,
149604
+ endMs: row.endMs,
149605
+ attempt: this.attempt,
149606
+ idleTimeoutMs: 300
149607
+ }
149608
+ });
149609
+ let api;
149610
+ try {
149611
+ api = await device.getApi();
149612
+ } catch (err) {
149613
+ return {
149614
+ kind: "no-media",
149615
+ detail: describe(err)
149616
+ };
149617
+ }
149618
+ try {
149619
+ await api.fileInfoListReplayBinaryDownload({
149620
+ channel: device.channel,
149621
+ ...device.uid !== void 0 ? { uid: device.uid } : {},
149622
+ fileName,
149623
+ idleTimeoutMs: 300,
149624
+ onChunk: (chunk) => this.onChunk(decoder, chunk)
149625
+ });
149626
+ } catch (err) {
149627
+ return this.onTransferFailed(err);
149628
+ }
149629
+ return this.onTransferEnded();
149630
+ }
149631
+ /** The camera's bytes, as they arrive. A cut run THROWS here: the only stop. */
149632
+ onChunk(decoder, chunk) {
149633
+ if (this.cut !== null) throw new Error(`clip stream cut: ${this.cut.reason}`);
149634
+ try {
149635
+ decoder.push(chunk);
149636
+ } catch (err) {
149637
+ this.sever("decode-failed", describe(err));
149638
+ throw err;
149639
+ }
149640
+ }
149641
+ onAccessUnit(unit) {
149642
+ if (this.cut !== null) return;
149643
+ this.accessUnits += 1;
149644
+ this.timestampsUs.push(unit.microseconds);
149645
+ if (this.mux === null) {
149646
+ this.probe = [...this.probe, unit];
149647
+ if (this.probe.length >= 16) this.startMux();
149648
+ return;
149649
+ }
149650
+ this.feed(unit.annexB);
149651
+ }
149652
+ /** Spawn ffmpeg with the probed rate and hand it what was held. */
149653
+ startMux() {
149654
+ const first = this.probe[0];
149655
+ if (first === void 0 || this.mux !== null) return;
149656
+ this.videoType = first.videoType;
149657
+ this.rate = probeClipStreamRate(this.timestampsUs);
149658
+ const { row, twin } = this.input;
149659
+ if (this.rate.fpsSource !== "timestamps") this.deps.logger.warn("videoclips: no measured frame rate for this clip stream", {
149660
+ tags: this.tags,
149661
+ meta: {
149662
+ clipKey: row.clipKey,
149663
+ twin,
149664
+ branch: "fps-unmeasured",
149665
+ probeAccessUnits: this.rate.accessUnits
149666
+ }
149667
+ });
149668
+ let mux;
149669
+ try {
149670
+ mux = this.deps.spawnMux(buildClipStreamMuxArgs({
149671
+ videoType: this.videoType,
149672
+ fps: this.rate.fps
149673
+ }));
149674
+ } catch (err) {
149675
+ this.sever("mux-failed", `ffmpeg could not be spawned: ${describe(err)}`);
149676
+ return;
149677
+ }
149678
+ this.mux = mux;
149679
+ this.muxClosed = new Promise((resolve) => {
149680
+ mux.on("close", (code) => resolve(code));
149681
+ mux.on("error", (err) => {
149682
+ this.sever("mux-failed", `ffmpeg failed: ${err.message}`);
149683
+ resolve(null);
149684
+ });
149685
+ });
149686
+ mux.stderr?.on("data", (chunk) => {
149687
+ if (this.muxStderr.length < 2e3) this.muxStderr += chunk.toString("utf8");
149688
+ });
149689
+ mux.stdin?.on("error", (err) => this.sever("mux-failed", `ffmpeg stdin: ${err.message}`));
149690
+ mux.stdin?.on("drain", () => this.stdinGate.drained());
149691
+ mux.stdout?.on("data", (chunk) => this.onMuxOut(chunk));
149692
+ mux.stdout?.on("end", () => this.finishSink());
149693
+ this.deps.logger.info("videoclips: a clip stream is muxing", {
149694
+ tags: this.tags,
149695
+ meta: {
149696
+ clipKey: row.clipKey,
149697
+ twin,
149698
+ videoType: this.videoType,
149699
+ fps: this.rate.fps,
149700
+ fpsSource: this.rate.fpsSource,
149701
+ probeAccessUnits: this.rate.accessUnits
149702
+ }
149703
+ });
149704
+ const held = this.probe;
149705
+ this.probe = [];
149706
+ for (const unit of held) this.feed(unit.annexB);
149707
+ }
149708
+ feed(annexB) {
149709
+ if (this.cut !== null || this.mux?.stdin === null || this.mux?.stdin === void 0) return;
149710
+ this.stdinGate.wrote(this.mux.stdin.write(annexB));
149711
+ }
149712
+ /** ffmpeg's output, to the peer, honouring `write()`'s return (D447). */
149713
+ onMuxOut(chunk) {
149714
+ if (this.cut !== null) return;
149715
+ const { sink } = this.input;
149716
+ if (!this.begun) {
149717
+ this.begun = true;
149718
+ sink.begin();
149719
+ }
149720
+ const accepted = sink.write(chunk);
149721
+ this.bytesOut += chunk.byteLength;
149722
+ this.peerGate.wrote(accepted);
149723
+ if (accepted) return;
149724
+ const stdout = this.mux?.stdout;
149725
+ stdout?.pause();
149726
+ sink.onceDrain(() => {
149727
+ this.peerGate.drained();
149728
+ stdout?.resume();
149729
+ });
149730
+ }
149731
+ finishSink() {
149732
+ if (this.cut !== null || this.ended) return;
149733
+ this.ended = true;
149734
+ this.input.sink.end();
149735
+ }
149736
+ /** Sever every hop, once, and say why. */
149737
+ sever(reason, detail) {
149738
+ if (this.cut !== null) return;
149739
+ this.cut = {
149740
+ reason,
149741
+ detail
149742
+ };
149743
+ this.stdinGate.dispose();
149744
+ this.peerGate.dispose();
149745
+ this.mux?.kill("SIGKILL");
149746
+ this.deps.logger.warn("videoclips: a clip stream was cut", {
149747
+ tags: this.tags,
149748
+ meta: {
149749
+ clipKey: this.input.row.clipKey,
149750
+ twin: this.input.twin,
149751
+ branch: reason,
149752
+ detail,
149753
+ accessUnits: this.accessUnits,
149754
+ bytesOut: this.bytesOut,
149755
+ begun: this.begun
149756
+ }
149757
+ });
149758
+ if (this.begun) this.input.sink.destroy();
149759
+ }
149760
+ cutOutcome(cut) {
149761
+ if (this.begun) return {
149762
+ kind: "cut",
149763
+ reason: cut.reason,
149764
+ detail: cut.detail,
149765
+ bytesOut: this.bytesOut
149766
+ };
149767
+ return {
149768
+ kind: "refused",
149769
+ code: "fetch-failed",
149770
+ detail: `${cut.reason}: ${cut.detail}`,
149771
+ attempts: this.attempt
149772
+ };
149773
+ }
149774
+ onTransferFailed(err) {
149775
+ if (this.cut !== null) return this.cutOutcome(this.cut);
149776
+ if (this.accessUnits === 0) return {
149777
+ kind: "no-media",
149778
+ detail: describe(err)
149779
+ };
149780
+ this.sever("camera-failed", describe(err));
149781
+ return this.cutOutcome({
149782
+ reason: "camera-failed",
149783
+ detail: describe(err)
149784
+ });
149785
+ }
149786
+ async onTransferEnded() {
149787
+ if (this.cut !== null) return this.cutOutcome(this.cut);
149788
+ if (this.accessUnits === 0) return {
149789
+ kind: "no-media",
149790
+ detail: "the camera answered the transfer with no access unit"
149791
+ };
149792
+ if (this.mux === null) this.startMux();
149793
+ const cutAtStart = this.cut;
149794
+ if (cutAtStart !== null) return this.cutOutcome(cutAtStart);
149795
+ const mux = this.mux;
149796
+ if (mux === null || this.muxClosed === null) return this.cutOutcome({
149797
+ reason: "mux-failed",
149798
+ detail: "no mux after the transfer ended"
149799
+ });
149800
+ mux.stdin?.end();
149801
+ const flush = setTimeout(() => this.sever("mux-hung", `ffmpeg did not exit ${this.deps.flushTimeoutMs} ms after its input ended`), this.deps.flushTimeoutMs);
149802
+ const code = await this.muxClosed;
149803
+ clearTimeout(flush);
149804
+ const cutAfter = this.cut;
149805
+ if (cutAfter !== null) return this.cutOutcome(cutAfter);
149806
+ if (code !== 0) {
149807
+ this.sever("mux-failed", `ffmpeg exited ${String(code)}: ${this.muxStderr.slice(0, 300)}`);
149808
+ return this.cutOutcome({
149809
+ reason: "mux-failed",
149810
+ detail: this.muxStderr.slice(0, 300)
149811
+ });
149812
+ }
149813
+ this.finishSink();
149814
+ return this.served();
149815
+ }
149816
+ /** The stream landed: judge it against the row, out loud, and answer. */
149817
+ served() {
149818
+ const { row, twin } = this.input;
149819
+ const timing = estimateVideoTiming({ timestampsUs: this.timestampsUs });
149820
+ const durationSeconds = timing.fpsSource === "timestamps" ? timing.durationSeconds : null;
149821
+ const verdict = judgeClipDuration({
149822
+ expectedMs: row.endMs - row.startMs,
149823
+ decodedSeconds: durationSeconds,
149824
+ ...row.inProgress === true ? { inProgress: true } : {}
149825
+ });
149826
+ const meta = {
149827
+ clipKey: row.clipKey,
149828
+ twin,
149829
+ attempt: this.attempt,
149830
+ accessUnits: this.accessUnits,
149831
+ bytesOut: this.bytesOut,
149832
+ durationSeconds,
149833
+ expectedMs: row.endMs - row.startMs,
149834
+ fps: this.rate.fps,
149835
+ fpsSource: this.rate.fpsSource,
149836
+ videoType: this.videoType
149837
+ };
149838
+ if (verdict.kind === "short") this.deps.logger.warn("videoclips: a clip stream came up short against its row", {
149839
+ tags: this.tags,
149840
+ meta: {
149841
+ ...meta,
149842
+ branch: "stream-short",
149843
+ shortfallSeconds: verdict.shortfallSeconds,
149844
+ retried: false
149845
+ }
149846
+ });
149847
+ else if (verdict.kind === "unknown") this.deps.logger.warn("videoclips: a clip stream could not be checked for truncation", {
149848
+ tags: this.tags,
149849
+ meta: {
149850
+ ...meta,
149851
+ branch: "completeness-unverified",
149852
+ reason: verdict.reason
149853
+ }
149854
+ });
149855
+ else this.deps.logger.info("videoclips: a clip stream landed whole", {
149856
+ tags: this.tags,
149857
+ meta: {
149858
+ ...meta,
149859
+ branch: "stream-served"
149860
+ }
149861
+ });
149862
+ if (this.audioFrames > 0) this.deps.logger.warn("videoclips: a clip stream was served without sound", {
149863
+ tags: this.tags,
149864
+ meta: {
149865
+ clipKey: row.clipKey,
149866
+ twin,
149867
+ branch: "stream-video-only",
149868
+ audioFramesDropped: this.audioFrames
149869
+ }
149870
+ });
149871
+ return {
149872
+ kind: "served",
149873
+ accessUnits: this.accessUnits,
149874
+ audioFramesDropped: this.audioFrames,
149875
+ bytesOut: this.bytesOut,
149876
+ durationSeconds,
149877
+ verdict,
149878
+ rate: this.rate,
149879
+ attempts: this.attempt
149880
+ };
149881
+ }
149882
+ };
149883
+ function describe(err) {
149884
+ return err instanceof Error ? err.message : String(err);
149885
+ }
149886
+ //#endregion
149887
+ //#region src/clips/clip-stream-route.ts
149888
+ function createClipStreamRoute(deps) {
149889
+ return async (deviceId, clipKey, sink) => {
149890
+ const twin = "sub";
149891
+ const gate = await deps.gate({
149892
+ deviceId,
149893
+ clipKey,
149894
+ twin,
149895
+ path: "stream"
149896
+ });
149897
+ if (gate.kind === "refused") return {
149898
+ kind: "refused",
149899
+ code: gate.branch,
149900
+ detail: gate.detail
149901
+ };
149902
+ const row = deps.rowFor(deviceId, clipKey);
149903
+ if (row === null) {
149904
+ deps.logger.warn("videoclips: no catalog row for this clip stream", {
149905
+ tags: { deviceId },
149906
+ meta: {
149907
+ clipKey,
149908
+ twin,
149909
+ branch: "no-catalog-row"
149910
+ }
149911
+ });
149912
+ return {
149913
+ kind: "refused",
149914
+ code: "no-catalog-row",
149915
+ detail: "no-catalog-row: list this day before asking for its stream"
149916
+ };
149917
+ }
149918
+ if (row.inProgress === true) {
149919
+ deps.logger.warn("videoclips: refusing to stream a clip the camera is still writing", {
149920
+ tags: { deviceId },
149921
+ meta: {
149922
+ clipKey,
149923
+ twin,
149924
+ branch: "clip-in-progress"
149925
+ }
149926
+ });
149927
+ return {
149928
+ kind: "refused",
149929
+ code: "clip-in-progress",
149930
+ detail: "clip-in-progress: the camera is still writing this file; it has no end to stream to"
149931
+ };
149932
+ }
149933
+ const outcome = await deps.producer.stream({
149934
+ device: gate.device,
149935
+ row,
149936
+ fileName: row.subFileName,
149937
+ twin,
149938
+ sink
149939
+ });
149940
+ switch (outcome.kind) {
149941
+ case "served": return { kind: "served" };
149942
+ case "cut": return { kind: "cut" };
149943
+ case "refused": return {
149944
+ kind: "refused",
149945
+ code: outcome.code,
149946
+ detail: outcome.detail
149947
+ };
149948
+ }
149949
+ };
149950
+ }
149951
+ //#endregion
148604
149952
  //#region src/clips/clip-thumb-queue.ts
148605
149953
  /**
148606
149954
  * The provider's OWN CoverPreview lane — two queues, one in flight per host.
@@ -148780,6 +150128,59 @@ function decideClipPrefetch(input) {
148780
150128
  return { kind: "run" };
148781
150129
  }
148782
150130
  //#endregion
150131
+ //#region src/clips/clip-window.ts
150132
+ /**
150133
+ * Does this row overlap the window?
150134
+ *
150135
+ * Half-open on both ends (`start < until`, `end > since`), so a clip that ends
150136
+ * exactly when the window begins is OUT and a clip that begins exactly when it
150137
+ * ends is OUT — otherwise two adjacent windows both claim the same boundary
150138
+ * clip and a surface paging through them renders it twice.
150139
+ *
150140
+ * A row whose `endMs` is not after its `startMs` is treated as an INSTANT at
150141
+ * `startMs`. That is not defensive padding: a clip still being written carries
150142
+ * a frozen `endMs` from the camera's index (D563), and on this fleet that has
150143
+ * been observed equal to — and once behind — its own start. Judging such a row
150144
+ * by a bad end would drop a recording that is happening right now.
150145
+ */
150146
+ function clipOverlapsWindow(row, window) {
150147
+ const end = row.endMs > row.startMs ? row.endMs : row.startMs;
150148
+ return row.startMs < window.until && end > window.since;
150149
+ }
150150
+ /**
150151
+ * What the provider ANSWERS with — the one gate, so there is a single place
150152
+ * that decides what leaves this listing and a single line that reports it.
150153
+ *
150154
+ * ## Why a clip still being written is not listed
150155
+ *
150156
+ * The operator, 2026-09-22: *"Niente clips in preparazione, non ha senso
150157
+ * visualizzarle."* He is right, and the reasons had been accumulating:
150158
+ *
150159
+ * - its `endMs` is a frozen guess — the camera's index keeps whatever it held
150160
+ * when the file was opened, measured on 592 as the same `endTime` for eight
150161
+ * minutes (D563) — so the row's duration, its position on the axis and its
150162
+ * height are all wrong and all move;
150163
+ * - `readClipBytes` already refuses it by name (`clip-in-progress`): it has no
150164
+ * end to export against, so the completion rule has nothing to prove the
150165
+ * fetch finished (D568);
150166
+ * - it cannot be looped, and the surface has to explain why (`resolveClipLoop`);
150167
+ * - the player route would file a TRUNCATED copy under an immutable key, so
150168
+ * the first seconds become permanently what that clip is.
150169
+ *
150170
+ * Four different places bending around a row that cannot be played properly.
150171
+ * Not listing it deletes all four questions instead of answering them: the
150172
+ * clip appears, whole, on the next listing after the camera closes the file.
150173
+ */
150174
+ function listableClips(rows, window) {
150175
+ const inWindow = rows.filter((row) => clipOverlapsWindow(row, window));
150176
+ const kept = inWindow.filter((row) => row.inProgress !== true);
150177
+ return {
150178
+ kept,
150179
+ outOfWindow: rows.length - inWindow.length,
150180
+ inProgress: inWindow.length - kept.length
150181
+ };
150182
+ }
150183
+ //#endregion
148783
150184
  //#region src/clips/clip-identity.ts
148784
150185
  /**
148785
150186
  * A Reolink clip's IDENTITY — the id the operator's surface carries around and
@@ -148896,7 +150297,8 @@ var CLASS_BY_NATIVE = {
148896
150297
  var KIND_BY_NATIVE = {
148897
150298
  md: "motion",
148898
150299
  pir: "motion",
148899
- cry: "audio"
150300
+ cry: "audio",
150301
+ other: "motion"
148900
150302
  };
148901
150303
  /** What the subject DID — carried beside a class, never as one. */
148902
150304
  var BEHAVIOUR_BY_NATIVE = {
@@ -148909,8 +150311,9 @@ var BEHAVIOUR_BY_NATIVE = {
148909
150311
  /**
148910
150312
  * Recording REASONS — why the file exists, not what is in it. They get no
148911
150313
  * label and no behaviour; the row still lists, showing the reason raw.
148912
- * `other` / `none` are the camera declining to classify, which is not the same
148913
- * as the camera saying "nothing".
150314
+ * `none` is the camera declining to classify, which is not the same as the
150315
+ * camera saying "nothing". `other` used to live here and is now a motion KIND
150316
+ * — see {@link KIND_BY_NATIVE}.
148914
150317
  */
148915
150318
  var RECORDING_REASONS = new Set([
148916
150319
  "sched",
@@ -148922,7 +150325,6 @@ var RECORDING_REASONS = new Set([
148922
150325
  "answer",
148923
150326
  "tamper",
148924
150327
  "lmsg",
148925
- "other",
148926
150328
  "none"
148927
150329
  ]);
148928
150330
  /**
@@ -149498,6 +150900,18 @@ function createReolinkVideoclipsProvider(deps) {
149498
150900
  timeZone: device.timeZone,
149499
150901
  day
149500
150902
  });
150903
+ const newest = result.rows[result.rows.length - 1] ?? result.rows[0];
150904
+ deps.logger.info("videoclips: the camera answered a day search", {
150905
+ tags,
150906
+ meta: {
150907
+ day,
150908
+ rows: result.rows.length,
150909
+ newestFile: newest?.subFileName ?? null,
150910
+ newestStartMs: newest?.startMs ?? null,
150911
+ oldestStartMs: result.rows[0]?.startMs ?? null,
150912
+ askedAtMs: Date.now()
150913
+ }
150914
+ });
149501
150915
  if (result.mainSearchFailed !== void 0) deps.logger.warn("videoclips: the main-stream search refused — no high twin today", {
149502
150916
  tags,
149503
150917
  meta: {
@@ -149539,7 +150953,24 @@ function createReolinkVideoclipsProvider(deps) {
149539
150953
  state: "ok",
149540
150954
  catalogAsOf: deps.now()
149541
150955
  });
149542
- return rows;
150956
+ for (const row of rows) rowsByKey.set(`${device.deviceId}|${row.clipKey}`, row);
150957
+ const narrowed = listableClips(rows, {
150958
+ since,
150959
+ until
150960
+ });
150961
+ if (narrowed.outOfWindow > 0 || narrowed.inProgress > 0) deps.logger.info("videoclips: narrowed a day listing to what it can answer", {
150962
+ tags,
150963
+ meta: {
150964
+ branch: "listing-narrowed",
150965
+ listed: rows.length,
150966
+ kept: narrowed.kept.length,
150967
+ outOfWindow: narrowed.outOfWindow,
150968
+ inProgress: narrowed.inProgress,
150969
+ since,
150970
+ until
150971
+ }
150972
+ });
150973
+ return narrowed.kept;
149543
150974
  };
149544
150975
  /**
149545
150976
  * Give a day's rows a class when the FILES carried none.
@@ -150447,9 +151878,15 @@ function createClipService(deps) {
150447
151878
  * clip-export vocabulary, so the provider can hand the engine a token it
150448
151879
  * believes rather than prose it has to guess at (D572).
150449
151880
  */
150450
- const produceTwin = async (input) => {
150451
- const { deviceId, row, fileName, twin } = input;
150452
- const clipKey = row.clipKey;
151881
+ /**
151882
+ * The gates every clip byte path runs, in ONE place: the device, the
151883
+ * sleeping standalone, the uid. The file producer and the stream producer
151884
+ * both call this and neither decides any of it again — a second copy of
151885
+ * the sleeping gate is a second chance to wake a camera nobody authorised.
151886
+ * Every refusal is logged HERE, with its branch, so a caller cannot forget.
151887
+ */
151888
+ const gateClipDevice = async (input) => {
151889
+ const { deviceId, clipKey, twin, path } = input;
150453
151890
  const tags = { deviceId };
150454
151891
  const device = await deps.resolveDevice(deviceId);
150455
151892
  if (device === null) {
@@ -150458,11 +151895,13 @@ function createClipService(deps) {
150458
151895
  meta: {
150459
151896
  clipKey,
150460
151897
  twin,
151898
+ path,
150461
151899
  branch: "unknown-device"
150462
151900
  }
150463
151901
  });
150464
151902
  return {
150465
151903
  kind: "refused",
151904
+ branch: "unknown-device",
150466
151905
  code: "catalog-unreachable",
150467
151906
  detail: `device ${deviceId} is not a Reolink camera on this node`
150468
151907
  };
@@ -150474,11 +151913,13 @@ function createClipService(deps) {
150474
151913
  meta: {
150475
151914
  clipKey,
150476
151915
  twin,
151916
+ path,
150477
151917
  branch: "sleeping"
150478
151918
  }
150479
151919
  });
150480
151920
  return {
150481
151921
  kind: "refused",
151922
+ branch: "sleeping",
150482
151923
  code: "sleeping",
150483
151924
  detail: `device ${deviceId} is a standalone battery camera and is sleeping — fetching a clip would wake it, and no operator authorisation was carried`
150484
151925
  };
@@ -150488,6 +151929,7 @@ function createClipService(deps) {
150488
151929
  meta: {
150489
151930
  clipKey,
150490
151931
  twin,
151932
+ path,
150491
151933
  branch: "wake-authorised"
150492
151934
  }
150493
151935
  });
@@ -150498,16 +151940,50 @@ function createClipService(deps) {
150498
151940
  meta: {
150499
151941
  clipKey,
150500
151942
  twin,
151943
+ path,
150501
151944
  branch: "uid-missing"
150502
151945
  }
150503
151946
  });
150504
151947
  return {
150505
151948
  kind: "refused",
151949
+ branch: "uid-missing",
150506
151950
  code: "catalog-unreachable",
150507
151951
  detail: UID_MISSING_REASON
150508
151952
  };
150509
151953
  }
151954
+ return {
151955
+ kind: "ok",
151956
+ device
151957
+ };
151958
+ };
151959
+ const produceTwin = async (input) => {
151960
+ const { deviceId, row, fileName, twin } = input;
151961
+ const clipKey = row.clipKey;
151962
+ const tags = { deviceId };
151963
+ const gate = await gateClipDevice({
151964
+ deviceId,
151965
+ clipKey,
151966
+ twin,
151967
+ path: "file",
151968
+ ...input.wake !== void 0 ? { wake: input.wake } : {}
151969
+ });
151970
+ if (gate.kind === "refused") return {
151971
+ kind: "refused",
151972
+ code: gate.code,
151973
+ detail: gate.detail
151974
+ };
151975
+ const { device } = gate;
150510
151976
  let downloaded;
151977
+ deps.logger.info("videoclips: fetching a clip file from the camera", {
151978
+ tags,
151979
+ meta: {
151980
+ clipKey,
151981
+ twin,
151982
+ fileName,
151983
+ startMs: row.startMs,
151984
+ endMs: row.endMs
151985
+ }
151986
+ });
150511
151987
  try {
150512
151988
  downloaded = await fetchVerifiedClipBytes(device, row, clipKey, fileName);
150513
151989
  } catch (err) {
@@ -150674,6 +152150,23 @@ function createClipService(deps) {
150674
152150
  if (outcome.kind === "ok" && twin === "sub") await media.put(deviceId, row.clipKey, outcome.bytes);
150675
152151
  return outcome;
150676
152152
  };
152153
+ /**
152154
+ * The STREAM's path (`clip-stream-route.ts`): the same gates, the same row,
152155
+ * nothing written. Beside the file path, not in its place.
152156
+ */
152157
+ const streamClip = createClipStreamRoute({
152158
+ logger: deps.logger,
152159
+ gate: gateClipDevice,
152160
+ rowFor,
152161
+ producer: createClipStreamProducer({
152162
+ logger: deps.logger,
152163
+ spawnMux: (args) => spawn$1(deps.ffmpegBinaryPath ?? "ffmpeg", [...args], { stdio: [
152164
+ "pipe",
152165
+ "pipe",
152166
+ "pipe"
152167
+ ] })
152168
+ })
152169
+ });
150677
152170
  const provider = createReolinkVideoclipsProvider({
150678
152171
  now,
150679
152172
  cache,
@@ -150703,6 +152196,11 @@ function createClipService(deps) {
150703
152196
  hostBusy: (hostKey) => queue.busy(hostKey),
150704
152197
  thumbPrefix: CLIP_THUMB_PREFIX,
150705
152198
  mediaPrefix: CLIP_MEDIA_PREFIX,
152199
+ streamPrefix: CLIP_STREAM_PREFIX,
152200
+ streamHandler: createClipStreamHandler({
152201
+ stream: streamClip,
152202
+ logger: deps.logger
152203
+ }),
150706
152204
  thumbHandler: createClipThumbHandler({
150707
152205
  readCached: (deviceId, clipKey) => thumbs.read(deviceId, clipKey),
150708
152206
  mint: (deviceId, clipKey) => mintThumbnail(deviceId, clipKey, "interactive"),
@@ -164942,11 +166440,18 @@ var ReolinkProviderAddon = class extends BaseDeviceProvider {
164942
166440
  access: "authenticated",
164943
166441
  handler: service.playbackHandler
164944
166442
  });
166443
+ const stream = await this.ctx.dataPlane?.serve({
166444
+ prefix: service.streamPrefix,
166445
+ access: "authenticated",
166446
+ handler: service.streamHandler
166447
+ });
164945
166448
  this.ctx.logger.info("Reolink clip data planes served", { meta: {
164946
166449
  thumbPath: `/addon/${REOLINK_ADDON_ID}/${service.thumbPrefix}`,
164947
166450
  mediaPath: `/addon/${REOLINK_ADDON_ID}/${service.mediaPrefix}`,
166451
+ streamPath: `/addon/${REOLINK_ADDON_ID}/${service.streamPrefix}`,
164948
166452
  thumbServed: thumb !== void 0,
164949
- mediaServed: media !== void 0
166453
+ mediaServed: media !== void 0,
166454
+ streamServed: stream !== void 0
164950
166455
  } });
164951
166456
  } catch (err) {
164952
166457
  this.ctx.logger.warn("Reolink clip data planes failed to serve — clips will not render", { meta: { error: err instanceof Error ? err.message : String(err) } });