@camstack/addon-export-hap 1.2.134 → 1.2.140

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.
@@ -25460,6 +25460,23 @@ var videoclipsCapability = {
25460
25460
  deviceId: number(),
25461
25461
  clipId: string().min(1),
25462
25462
  /**
25463
+ * WHICH provider holds the bytes — the `addonId` a
25464
+ * {@link ClipSourceSchema} row carries. **Required**, for the reason
25465
+ * `listClips` states one method above and for a second one this method
25466
+ * learned the hard way.
25467
+ *
25468
+ * The caller is the stream broker, and the broker lives in
25469
+ * `addon-pipeline` — the same addon that registers the RECORDER's
25470
+ * videoclips provider. With no provider named, the in-process
25471
+ * resolution handed it its own sibling, which implements this method
25472
+ * solely to explain that an analytics visit has no file behind it. So
25473
+ * every onboard clip answered with that sentence, a flash of video and
25474
+ * nothing more, while the SAME call from outside the addon reached the
25475
+ * right provider and returned the bytes (measured on 3628,
25476
+ * 2026-09-21).
25477
+ */
25478
+ provider: string().min(1),
25479
+ /**
25463
25480
  * Which twin to fetch, on the mapping D549 15 already fixed:
25464
25481
  * `low | mid` → the sub file, `high` → the main twin. `auto` is not
25465
25482
  * accepted here for the same reason it is not accepted by
@@ -31223,7 +31240,7 @@ method(object({
31223
31240
  auth: "admin"
31224
31241
  }), method(object({ deviceId: number() }), RecordingConfigSchema, {
31225
31242
  kind: "query",
31226
- auth: "admin"
31243
+ auth: "protected"
31227
31244
  }), method(object({
31228
31245
  deviceId: number(),
31229
31246
  profile: string(),
@@ -33470,6 +33487,12 @@ DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeate
33470
33487
  new Set(Object.values(DeviceType));
33471
33488
  DeviceFeature.BatteryOperated;
33472
33489
  Object.freeze({
33490
+ "accessories.getStatus": {
33491
+ capName: "accessories",
33492
+ capScope: "device",
33493
+ addonId: null,
33494
+ access: "view"
33495
+ },
33473
33496
  "accessories.setChildHidden": {
33474
33497
  capName: "accessories",
33475
33498
  capScope: "device",
@@ -33752,6 +33775,12 @@ Object.freeze({
33752
33775
  addonId: null,
33753
33776
  access: "view"
33754
33777
  },
33778
+ "airQualitySensor.getStatus": {
33779
+ capName: "air-quality-sensor",
33780
+ capScope: "device",
33781
+ addonId: null,
33782
+ access: "view"
33783
+ },
33755
33784
  "alarmPanel.arm": {
33756
33785
  capName: "alarm-panel",
33757
33786
  capScope: "device",
@@ -33764,6 +33793,12 @@ Object.freeze({
33764
33793
  addonId: null,
33765
33794
  access: "create"
33766
33795
  },
33796
+ "alarmPanel.getStatus": {
33797
+ capName: "alarm-panel",
33798
+ capScope: "device",
33799
+ addonId: null,
33800
+ access: "view"
33801
+ },
33767
33802
  "alarmPanel.trigger": {
33768
33803
  capName: "alarm-panel",
33769
33804
  capScope: "device",
@@ -33812,6 +33847,30 @@ Object.freeze({
33812
33847
  addonId: null,
33813
33848
  access: "create"
33814
33849
  },
33850
+ "ambientLightSensor.getStatus": {
33851
+ capName: "ambient-light-sensor",
33852
+ capScope: "device",
33853
+ addonId: null,
33854
+ access: "view"
33855
+ },
33856
+ "audioAnalysis.applyDeviceSettingsPatch": {
33857
+ capName: "audio-analysis",
33858
+ capScope: "device",
33859
+ addonId: null,
33860
+ access: "create"
33861
+ },
33862
+ "audioAnalysis.getDeviceLiveContribution": {
33863
+ capName: "audio-analysis",
33864
+ capScope: "device",
33865
+ addonId: null,
33866
+ access: "view"
33867
+ },
33868
+ "audioAnalysis.getDeviceSettingsContribution": {
33869
+ capName: "audio-analysis",
33870
+ capScope: "device",
33871
+ addonId: null,
33872
+ access: "view"
33873
+ },
33815
33874
  "audioAnalysis.resolveDeviceSettings": {
33816
33875
  capName: "audio-analysis",
33817
33876
  capScope: "device",
@@ -33974,6 +34033,12 @@ Object.freeze({
33974
34033
  addonId: null,
33975
34034
  access: "create"
33976
34035
  },
34036
+ "automationControl.getStatus": {
34037
+ capName: "automation-control",
34038
+ capScope: "device",
34039
+ addonId: null,
34040
+ access: "view"
34041
+ },
33977
34042
  "automationControl.trigger": {
33978
34043
  capName: "automation-control",
33979
34044
  capScope: "device",
@@ -34070,12 +34135,30 @@ Object.freeze({
34070
34135
  addonId: null,
34071
34136
  access: "create"
34072
34137
  },
34138
+ "battery.getStatus": {
34139
+ capName: "battery",
34140
+ capScope: "device",
34141
+ addonId: null,
34142
+ access: "view"
34143
+ },
34073
34144
  "battery.wakeForStream": {
34074
34145
  capName: "battery",
34075
34146
  capScope: "device",
34076
34147
  addonId: null,
34077
34148
  access: "create"
34078
34149
  },
34150
+ "binary.getStatus": {
34151
+ capName: "binary",
34152
+ capScope: "device",
34153
+ addonId: null,
34154
+ access: "view"
34155
+ },
34156
+ "brightness.getStatus": {
34157
+ capName: "brightness",
34158
+ capScope: "device",
34159
+ addonId: null,
34160
+ access: "view"
34161
+ },
34079
34162
  "brightness.setBrightness": {
34080
34163
  capName: "brightness",
34081
34164
  capScope: "device",
@@ -34190,6 +34273,30 @@ Object.freeze({
34190
34273
  addonId: null,
34191
34274
  access: "view"
34192
34275
  },
34276
+ "cameraCredentials.getStatus": {
34277
+ capName: "camera-credentials",
34278
+ capScope: "device",
34279
+ addonId: null,
34280
+ access: "view"
34281
+ },
34282
+ "cameraGridLayout.applyDeviceSettingsPatch": {
34283
+ capName: "camera-grid-layout",
34284
+ capScope: "device",
34285
+ addonId: null,
34286
+ access: "create"
34287
+ },
34288
+ "cameraGridLayout.getDeviceLiveContribution": {
34289
+ capName: "camera-grid-layout",
34290
+ capScope: "device",
34291
+ addonId: null,
34292
+ access: "view"
34293
+ },
34294
+ "cameraGridLayout.getDeviceSettingsContribution": {
34295
+ capName: "camera-grid-layout",
34296
+ capScope: "device",
34297
+ addonId: null,
34298
+ access: "view"
34299
+ },
34193
34300
  "cameraGridLayout.getLayout": {
34194
34301
  capName: "camera-grid-layout",
34195
34302
  capScope: "device",
@@ -34202,6 +34309,24 @@ Object.freeze({
34202
34309
  addonId: null,
34203
34310
  access: "create"
34204
34311
  },
34312
+ "cameraPipelineConfig.applyDeviceSettingsPatch": {
34313
+ capName: "camera-pipeline-config",
34314
+ capScope: "device",
34315
+ addonId: null,
34316
+ access: "create"
34317
+ },
34318
+ "cameraPipelineConfig.getDeviceLiveContribution": {
34319
+ capName: "camera-pipeline-config",
34320
+ capScope: "device",
34321
+ addonId: null,
34322
+ access: "view"
34323
+ },
34324
+ "cameraPipelineConfig.getDeviceSettingsContribution": {
34325
+ capName: "camera-pipeline-config",
34326
+ capScope: "device",
34327
+ addonId: null,
34328
+ access: "view"
34329
+ },
34205
34330
  "cameraStreams.getBrokerStreams": {
34206
34331
  capName: "camera-streams",
34207
34332
  capScope: "device",
@@ -34232,6 +34357,18 @@ Object.freeze({
34232
34357
  addonId: null,
34233
34358
  access: "view"
34234
34359
  },
34360
+ "carbonMonoxide.getStatus": {
34361
+ capName: "carbon-monoxide",
34362
+ capScope: "device",
34363
+ addonId: null,
34364
+ access: "view"
34365
+ },
34366
+ "climateControl.getStatus": {
34367
+ capName: "climate-control",
34368
+ capScope: "device",
34369
+ addonId: null,
34370
+ access: "view"
34371
+ },
34235
34372
  "climateControl.setFanMode": {
34236
34373
  capName: "climate-control",
34237
34374
  capScope: "device",
@@ -34280,6 +34417,12 @@ Object.freeze({
34280
34417
  addonId: null,
34281
34418
  access: "create"
34282
34419
  },
34420
+ "color.getStatus": {
34421
+ capName: "color",
34422
+ capScope: "device",
34423
+ addonId: null,
34424
+ access: "view"
34425
+ },
34283
34426
  "color.setColor": {
34284
34427
  capName: "color",
34285
34428
  capScope: "device",
@@ -34298,12 +34441,36 @@ Object.freeze({
34298
34441
  addonId: null,
34299
34442
  access: "create"
34300
34443
  },
34444
+ "connectivity.getStatus": {
34445
+ capName: "connectivity",
34446
+ capScope: "device",
34447
+ addonId: null,
34448
+ access: "view"
34449
+ },
34450
+ "consumables.getStatus": {
34451
+ capName: "consumables",
34452
+ capScope: "device",
34453
+ addonId: null,
34454
+ access: "view"
34455
+ },
34301
34456
  "consumables.reset": {
34302
34457
  capName: "consumables",
34303
34458
  capScope: "device",
34304
34459
  addonId: null,
34305
34460
  access: "delete"
34306
34461
  },
34462
+ "contact.getStatus": {
34463
+ capName: "contact",
34464
+ capScope: "device",
34465
+ addonId: null,
34466
+ access: "view"
34467
+ },
34468
+ "control.getStatus": {
34469
+ capName: "control",
34470
+ capScope: "device",
34471
+ addonId: null,
34472
+ access: "view"
34473
+ },
34307
34474
  "control.setValue": {
34308
34475
  capName: "control",
34309
34476
  capScope: "device",
@@ -34370,6 +34537,12 @@ Object.freeze({
34370
34537
  addonId: null,
34371
34538
  access: "create"
34372
34539
  },
34540
+ "cover.getStatus": {
34541
+ capName: "cover",
34542
+ capScope: "device",
34543
+ addonId: null,
34544
+ access: "view"
34545
+ },
34373
34546
  "cover.open": {
34374
34547
  capName: "cover",
34375
34548
  capScope: "device",
@@ -34502,6 +34675,12 @@ Object.freeze({
34502
34675
  addonId: null,
34503
34676
  access: "view"
34504
34677
  },
34678
+ "dayNight.getStatus": {
34679
+ capName: "day-night",
34680
+ capScope: "device",
34681
+ addonId: null,
34682
+ access: "view"
34683
+ },
34505
34684
  "dayNight.setSettings": {
34506
34685
  capName: "day-night",
34507
34686
  capScope: "device",
@@ -34592,6 +34771,24 @@ Object.freeze({
34592
34771
  addonId: null,
34593
34772
  access: "view"
34594
34773
  },
34774
+ "detectionPipeline.applyDeviceSettingsPatch": {
34775
+ capName: "detection-pipeline",
34776
+ capScope: "device",
34777
+ addonId: null,
34778
+ access: "create"
34779
+ },
34780
+ "detectionPipeline.getDeviceLiveContribution": {
34781
+ capName: "detection-pipeline",
34782
+ capScope: "device",
34783
+ addonId: null,
34784
+ access: "view"
34785
+ },
34786
+ "detectionPipeline.getDeviceSettingsContribution": {
34787
+ capName: "detection-pipeline",
34788
+ capScope: "device",
34789
+ addonId: null,
34790
+ access: "view"
34791
+ },
34595
34792
  "deviceAdoption.adopt": {
34596
34793
  capName: "device-adoption",
34597
34794
  capScope: "system",
@@ -34604,6 +34801,12 @@ Object.freeze({
34604
34801
  addonId: null,
34605
34802
  access: "view"
34606
34803
  },
34804
+ "deviceAdoption.getStatus": {
34805
+ capName: "device-adoption",
34806
+ capScope: "system",
34807
+ addonId: null,
34808
+ access: "view"
34809
+ },
34607
34810
  "deviceAdoption.listCandidateFilters": {
34608
34811
  capName: "device-adoption",
34609
34812
  capScope: "system",
@@ -34640,6 +34843,12 @@ Object.freeze({
34640
34843
  addonId: null,
34641
34844
  access: "create"
34642
34845
  },
34846
+ "deviceDiscovery.getStatus": {
34847
+ capName: "device-discovery",
34848
+ capScope: "device",
34849
+ addonId: null,
34850
+ access: "view"
34851
+ },
34643
34852
  "deviceDiscovery.listDiscovered": {
34644
34853
  capName: "device-discovery",
34645
34854
  capScope: "device",
@@ -34658,12 +34867,30 @@ Object.freeze({
34658
34867
  addonId: null,
34659
34868
  access: "create"
34660
34869
  },
34870
+ "deviceExport.applyDeviceSettingsPatch": {
34871
+ capName: "device-export",
34872
+ capScope: "system",
34873
+ addonId: null,
34874
+ access: "create"
34875
+ },
34661
34876
  "deviceExport.exposeDevice": {
34662
34877
  capName: "device-export",
34663
34878
  capScope: "system",
34664
34879
  addonId: null,
34665
34880
  access: "create"
34666
34881
  },
34882
+ "deviceExport.getDeviceLiveContribution": {
34883
+ capName: "device-export",
34884
+ capScope: "system",
34885
+ addonId: null,
34886
+ access: "view"
34887
+ },
34888
+ "deviceExport.getDeviceSettingsContribution": {
34889
+ capName: "device-export",
34890
+ capScope: "system",
34891
+ addonId: null,
34892
+ access: "view"
34893
+ },
34667
34894
  "deviceExport.getStatus": {
34668
34895
  capName: "device-export",
34669
34896
  capScope: "system",
@@ -34760,6 +34987,12 @@ Object.freeze({
34760
34987
  addonId: null,
34761
34988
  access: "create"
34762
34989
  },
34990
+ "deviceManager.applyDeviceSettingsPatch": {
34991
+ capName: "device-manager",
34992
+ capScope: "system",
34993
+ addonId: null,
34994
+ access: "create"
34995
+ },
34763
34996
  "deviceManager.applyInitialMeta": {
34764
34997
  capName: "device-manager",
34765
34998
  capScope: "system",
@@ -34862,6 +35095,12 @@ Object.freeze({
34862
35095
  addonId: null,
34863
35096
  access: "view"
34864
35097
  },
35098
+ "deviceManager.getDeviceLiveContribution": {
35099
+ capName: "device-manager",
35100
+ capScope: "system",
35101
+ addonId: null,
35102
+ access: "view"
35103
+ },
34865
35104
  "deviceManager.getDeviceLiveInfoAggregate": {
34866
35105
  capName: "device-manager",
34867
35106
  capScope: "system",
@@ -34874,6 +35113,12 @@ Object.freeze({
34874
35113
  addonId: null,
34875
35114
  access: "view"
34876
35115
  },
35116
+ "deviceManager.getDeviceSettingsContribution": {
35117
+ capName: "device-manager",
35118
+ capScope: "system",
35119
+ addonId: null,
35120
+ access: "view"
35121
+ },
34877
35122
  "deviceManager.getDeviceStatusAggregate": {
34878
35123
  capName: "device-manager",
34879
35124
  capScope: "system",
@@ -35366,6 +35611,36 @@ Object.freeze({
35366
35611
  addonId: null,
35367
35612
  access: "create"
35368
35613
  },
35614
+ "deviceStatus.getStatus": {
35615
+ capName: "device-status",
35616
+ capScope: "device",
35617
+ addonId: null,
35618
+ access: "view"
35619
+ },
35620
+ "doorbell.applyDeviceSettingsPatch": {
35621
+ capName: "doorbell",
35622
+ capScope: "device",
35623
+ addonId: null,
35624
+ access: "create"
35625
+ },
35626
+ "doorbell.getDeviceLiveContribution": {
35627
+ capName: "doorbell",
35628
+ capScope: "device",
35629
+ addonId: null,
35630
+ access: "view"
35631
+ },
35632
+ "doorbell.getDeviceSettingsContribution": {
35633
+ capName: "doorbell",
35634
+ capScope: "device",
35635
+ addonId: null,
35636
+ access: "view"
35637
+ },
35638
+ "doorbell.getStatus": {
35639
+ capName: "doorbell",
35640
+ capScope: "device",
35641
+ addonId: null,
35642
+ access: "view"
35643
+ },
35369
35644
  "embeddingEncoder.encode": {
35370
35645
  capName: "embedding-encoder",
35371
35646
  capScope: "system",
@@ -35384,6 +35659,18 @@ Object.freeze({
35384
35659
  addonId: null,
35385
35660
  access: "view"
35386
35661
  },
35662
+ "enumSensor.getStatus": {
35663
+ capName: "enum-sensor",
35664
+ capScope: "device",
35665
+ addonId: null,
35666
+ access: "view"
35667
+ },
35668
+ "eventEmitter.getStatus": {
35669
+ capName: "event-emitter",
35670
+ capScope: "device",
35671
+ addonId: null,
35672
+ access: "view"
35673
+ },
35387
35674
  "events.getEventClipUrl": {
35388
35675
  capName: "events",
35389
35676
  capScope: "device",
@@ -35504,6 +35791,12 @@ Object.freeze({
35504
35791
  addonId: null,
35505
35792
  access: "view"
35506
35793
  },
35794
+ "fanControl.getStatus": {
35795
+ capName: "fan-control",
35796
+ capScope: "device",
35797
+ addonId: null,
35798
+ access: "view"
35799
+ },
35507
35800
  "fanControl.setDirection": {
35508
35801
  capName: "fan-control",
35509
35802
  capScope: "device",
@@ -35528,6 +35821,12 @@ Object.freeze({
35528
35821
  addonId: null,
35529
35822
  access: "create"
35530
35823
  },
35824
+ "featureProbe.getStatus": {
35825
+ capName: "feature-probe",
35826
+ capScope: "device",
35827
+ addonId: null,
35828
+ access: "view"
35829
+ },
35531
35830
  "filesystemBrowse.browse": {
35532
35831
  capName: "filesystem-browse",
35533
35832
  capScope: "system",
@@ -35546,6 +35845,24 @@ Object.freeze({
35546
35845
  addonId: null,
35547
35846
  access: "view"
35548
35847
  },
35848
+ "flood.getStatus": {
35849
+ capName: "flood",
35850
+ capScope: "device",
35851
+ addonId: null,
35852
+ access: "view"
35853
+ },
35854
+ "gas.getStatus": {
35855
+ capName: "gas",
35856
+ capScope: "device",
35857
+ addonId: null,
35858
+ access: "view"
35859
+ },
35860
+ "humidifier.getStatus": {
35861
+ capName: "humidifier",
35862
+ capScope: "device",
35863
+ addonId: null,
35864
+ access: "view"
35865
+ },
35549
35866
  "humidifier.setMode": {
35550
35867
  capName: "humidifier",
35551
35868
  capScope: "device",
@@ -35564,12 +35881,30 @@ Object.freeze({
35564
35881
  addonId: null,
35565
35882
  access: "create"
35566
35883
  },
35884
+ "humiditySensor.getStatus": {
35885
+ capName: "humidity-sensor",
35886
+ capScope: "device",
35887
+ addonId: null,
35888
+ access: "view"
35889
+ },
35890
+ "image.getStatus": {
35891
+ capName: "image",
35892
+ capScope: "device",
35893
+ addonId: null,
35894
+ access: "view"
35895
+ },
35567
35896
  "imageSettings.getOptions": {
35568
35897
  capName: "image-settings",
35569
35898
  capScope: "device",
35570
35899
  addonId: null,
35571
35900
  access: "view"
35572
35901
  },
35902
+ "imageSettings.getStatus": {
35903
+ capName: "image-settings",
35904
+ capScope: "device",
35905
+ addonId: null,
35906
+ access: "view"
35907
+ },
35573
35908
  "imageSettings.setSettings": {
35574
35909
  capName: "image-settings",
35575
35910
  capScope: "device",
@@ -35642,6 +35977,12 @@ Object.freeze({
35642
35977
  addonId: null,
35643
35978
  access: "create"
35644
35979
  },
35980
+ "intercom.getStatus": {
35981
+ capName: "intercom",
35982
+ capScope: "device",
35983
+ addonId: null,
35984
+ access: "view"
35985
+ },
35645
35986
  "intercom.handleAnswer": {
35646
35987
  capName: "intercom",
35647
35988
  capScope: "device",
@@ -35678,6 +36019,12 @@ Object.freeze({
35678
36019
  addonId: null,
35679
36020
  access: "create"
35680
36021
  },
36022
+ "lawnMowerControl.getStatus": {
36023
+ capName: "lawn-mower-control",
36024
+ capScope: "device",
36025
+ addonId: null,
36026
+ access: "view"
36027
+ },
35681
36028
  "lawnMowerControl.pause": {
35682
36029
  capName: "lawn-mower-control",
35683
36030
  capScope: "device",
@@ -35960,6 +36307,12 @@ Object.freeze({
35960
36307
  addonId: null,
35961
36308
  access: "create"
35962
36309
  },
36310
+ "lockControl.getStatus": {
36311
+ capName: "lock-control",
36312
+ capScope: "device",
36313
+ addonId: null,
36314
+ access: "view"
36315
+ },
35963
36316
  "lockControl.lock": {
35964
36317
  capName: "lock-control",
35965
36318
  capScope: "device",
@@ -36008,6 +36361,12 @@ Object.freeze({
36008
36361
  addonId: null,
36009
36362
  access: "view"
36010
36363
  },
36364
+ "mediaPlayer.getStatus": {
36365
+ capName: "media-player",
36366
+ capScope: "device",
36367
+ addonId: null,
36368
+ access: "view"
36369
+ },
36011
36370
  "mediaPlayer.next": {
36012
36371
  capName: "media-player",
36013
36372
  capScope: "device",
@@ -36200,6 +36559,12 @@ Object.freeze({
36200
36559
  addonId: null,
36201
36560
  access: "create"
36202
36561
  },
36562
+ "motion.getStatus": {
36563
+ capName: "motion",
36564
+ capScope: "device",
36565
+ addonId: null,
36566
+ access: "view"
36567
+ },
36203
36568
  "motion.isDetected": {
36204
36569
  capName: "motion",
36205
36570
  capScope: "device",
@@ -36212,6 +36577,24 @@ Object.freeze({
36212
36577
  addonId: null,
36213
36578
  access: "create"
36214
36579
  },
36580
+ "motionDetection.applyDeviceSettingsPatch": {
36581
+ capName: "motion-detection",
36582
+ capScope: "device",
36583
+ addonId: null,
36584
+ access: "create"
36585
+ },
36586
+ "motionDetection.getDeviceLiveContribution": {
36587
+ capName: "motion-detection",
36588
+ capScope: "device",
36589
+ addonId: null,
36590
+ access: "view"
36591
+ },
36592
+ "motionDetection.getDeviceSettingsContribution": {
36593
+ capName: "motion-detection",
36594
+ capScope: "device",
36595
+ addonId: null,
36596
+ access: "view"
36597
+ },
36215
36598
  "motionDetection.removeCamera": {
36216
36599
  capName: "motion-detection",
36217
36600
  capScope: "device",
@@ -36224,6 +36607,12 @@ Object.freeze({
36224
36607
  addonId: null,
36225
36608
  access: "delete"
36226
36609
  },
36610
+ "motionTrigger.getStatus": {
36611
+ capName: "motion-trigger",
36612
+ capScope: "device",
36613
+ addonId: null,
36614
+ access: "view"
36615
+ },
36227
36616
  "motionTrigger.setMotionTrigger": {
36228
36617
  capName: "motion-trigger",
36229
36618
  capScope: "device",
@@ -36236,6 +36625,12 @@ Object.freeze({
36236
36625
  addonId: null,
36237
36626
  access: "view"
36238
36627
  },
36628
+ "motionZones.getStatus": {
36629
+ capName: "motion-zones",
36630
+ capScope: "device",
36631
+ addonId: null,
36632
+ access: "view"
36633
+ },
36239
36634
  "motionZones.setZone": {
36240
36635
  capName: "motion-zones",
36241
36636
  capScope: "device",
@@ -36296,6 +36691,12 @@ Object.freeze({
36296
36691
  addonId: null,
36297
36692
  access: "view"
36298
36693
  },
36694
+ "nativeObjectDetection.getStatus": {
36695
+ capName: "native-object-detection",
36696
+ capScope: "device",
36697
+ addonId: null,
36698
+ access: "view"
36699
+ },
36299
36700
  "nativeObjectDetection.setEnabled": {
36300
36701
  capName: "native-object-detection",
36301
36702
  capScope: "device",
@@ -36314,6 +36715,12 @@ Object.freeze({
36314
36715
  addonId: null,
36315
36716
  access: "view"
36316
36717
  },
36718
+ "navigation.getStatus": {
36719
+ capName: "navigation",
36720
+ capScope: "device",
36721
+ addonId: null,
36722
+ access: "view"
36723
+ },
36317
36724
  "navigation.goToPoint": {
36318
36725
  capName: "navigation",
36319
36726
  capScope: "device",
@@ -36398,6 +36805,12 @@ Object.freeze({
36398
36805
  addonId: null,
36399
36806
  access: "create"
36400
36807
  },
36808
+ "networkLink.getStatus": {
36809
+ capName: "network-link",
36810
+ capScope: "device",
36811
+ addonId: null,
36812
+ access: "view"
36813
+ },
36401
36814
  "networkQuality.getAllStats": {
36402
36815
  capName: "network-quality",
36403
36816
  capScope: "system",
@@ -36656,18 +37069,36 @@ Object.freeze({
36656
37069
  addonId: null,
36657
37070
  access: "create"
36658
37071
  },
37072
+ "notifier.getStatus": {
37073
+ capName: "notifier",
37074
+ capScope: "device",
37075
+ addonId: null,
37076
+ access: "view"
37077
+ },
36659
37078
  "notifier.send": {
36660
37079
  capName: "notifier",
36661
37080
  capScope: "device",
36662
37081
  addonId: null,
36663
37082
  access: "create"
36664
37083
  },
37084
+ "numericSensor.getStatus": {
37085
+ capName: "numeric-sensor",
37086
+ capScope: "device",
37087
+ addonId: null,
37088
+ access: "view"
37089
+ },
36665
37090
  "oauthIntegration.getDescriptor": {
36666
37091
  capName: "oauth-integration",
36667
37092
  capScope: "system",
36668
37093
  addonId: null,
36669
37094
  access: "view"
36670
37095
  },
37096
+ "osd.getStatus": {
37097
+ capName: "osd",
37098
+ capScope: "device",
37099
+ addonId: null,
37100
+ access: "view"
37101
+ },
36671
37102
  "osd.setOverlay": {
36672
37103
  capName: "osd",
36673
37104
  capScope: "device",
@@ -36740,6 +37171,12 @@ Object.freeze({
36740
37171
  addonId: null,
36741
37172
  access: "create"
36742
37173
  },
37174
+ "petFeeder.getStatus": {
37175
+ capName: "pet-feeder",
37176
+ capScope: "device",
37177
+ addonId: null,
37178
+ access: "view"
37179
+ },
36743
37180
  "petFeeder.markFoodReplenished": {
36744
37181
  capName: "pet-feeder",
36745
37182
  capScope: "device",
@@ -36782,6 +37219,12 @@ Object.freeze({
36782
37219
  addonId: null,
36783
37220
  access: "create"
36784
37221
  },
37222
+ "pipelineAnalytics.applyDeviceSettingsPatch": {
37223
+ capName: "pipeline-analytics",
37224
+ capScope: "device",
37225
+ addonId: null,
37226
+ access: "create"
37227
+ },
36785
37228
  "pipelineAnalytics.cancelRelocateMedia": {
36786
37229
  capName: "pipeline-analytics",
36787
37230
  capScope: "device",
@@ -36848,6 +37291,18 @@ Object.freeze({
36848
37291
  addonId: null,
36849
37292
  access: "view"
36850
37293
  },
37294
+ "pipelineAnalytics.getDeviceLiveContribution": {
37295
+ capName: "pipeline-analytics",
37296
+ capScope: "device",
37297
+ addonId: null,
37298
+ access: "view"
37299
+ },
37300
+ "pipelineAnalytics.getDeviceSettingsContribution": {
37301
+ capName: "pipeline-analytics",
37302
+ capScope: "device",
37303
+ addonId: null,
37304
+ access: "view"
37305
+ },
36851
37306
  "pipelineAnalytics.getEventDensity": {
36852
37307
  capName: "pipeline-analytics",
36853
37308
  capScope: "device",
@@ -37418,6 +37873,12 @@ Object.freeze({
37418
37873
  addonId: null,
37419
37874
  access: "view"
37420
37875
  },
37876
+ "pipelineOrchestrator.applyDeviceSettingsPatch": {
37877
+ capName: "pipeline-orchestrator",
37878
+ capScope: "system",
37879
+ addonId: null,
37880
+ access: "create"
37881
+ },
37421
37882
  "pipelineOrchestrator.assignAudio": {
37422
37883
  capName: "pipeline-orchestrator",
37423
37884
  capScope: "system",
@@ -37508,6 +37969,18 @@ Object.freeze({
37508
37969
  addonId: null,
37509
37970
  access: "view"
37510
37971
  },
37972
+ "pipelineOrchestrator.getDeviceLiveContribution": {
37973
+ capName: "pipeline-orchestrator",
37974
+ capScope: "system",
37975
+ addonId: null,
37976
+ access: "view"
37977
+ },
37978
+ "pipelineOrchestrator.getDeviceSettingsContribution": {
37979
+ capName: "pipeline-orchestrator",
37980
+ capScope: "system",
37981
+ addonId: null,
37982
+ access: "view"
37983
+ },
37511
37984
  "pipelineOrchestrator.getGlobalMetrics": {
37512
37985
  capName: "pipeline-orchestrator",
37513
37986
  capScope: "system",
@@ -37916,12 +38389,36 @@ Object.freeze({
37916
38389
  addonId: null,
37917
38390
  access: "view"
37918
38391
  },
38392
+ "powerMeter.getStatus": {
38393
+ capName: "power-meter",
38394
+ capScope: "device",
38395
+ addonId: null,
38396
+ access: "view"
38397
+ },
38398
+ "presence.getStatus": {
38399
+ capName: "presence",
38400
+ capScope: "device",
38401
+ addonId: null,
38402
+ access: "view"
38403
+ },
38404
+ "pressureSensor.getStatus": {
38405
+ capName: "pressure-sensor",
38406
+ capScope: "device",
38407
+ addonId: null,
38408
+ access: "view"
38409
+ },
37919
38410
  "privacyMask.getOptions": {
37920
38411
  capName: "privacy-mask",
37921
38412
  capScope: "device",
37922
38413
  addonId: null,
37923
38414
  access: "view"
37924
38415
  },
38416
+ "privacyMask.getStatus": {
38417
+ capName: "privacy-mask",
38418
+ capScope: "device",
38419
+ addonId: null,
38420
+ access: "view"
38421
+ },
37925
38422
  "privacyMask.setAudioEnabled": {
37926
38423
  capName: "privacy-mask",
37927
38424
  capScope: "device",
@@ -37982,6 +38479,12 @@ Object.freeze({
37982
38479
  addonId: null,
37983
38480
  access: "view"
37984
38481
  },
38482
+ "ptz.getStatus": {
38483
+ capName: "ptz",
38484
+ capScope: "device",
38485
+ addonId: null,
38486
+ access: "view"
38487
+ },
37985
38488
  "ptz.goHome": {
37986
38489
  capName: "ptz",
37987
38490
  capScope: "device",
@@ -38060,6 +38563,12 @@ Object.freeze({
38060
38563
  addonId: null,
38061
38564
  access: "create"
38062
38565
  },
38566
+ "recording.applyDeviceSettingsPatch": {
38567
+ capName: "recording",
38568
+ capScope: "system",
38569
+ addonId: null,
38570
+ access: "create"
38571
+ },
38063
38572
  "recording.cancelRelocateJob": {
38064
38573
  capName: "recording",
38065
38574
  capScope: "system",
@@ -38108,6 +38617,18 @@ Object.freeze({
38108
38617
  addonId: null,
38109
38618
  access: "view"
38110
38619
  },
38620
+ "recording.getDeviceLiveContribution": {
38621
+ capName: "recording",
38622
+ capScope: "system",
38623
+ addonId: null,
38624
+ access: "view"
38625
+ },
38626
+ "recording.getDeviceSettingsContribution": {
38627
+ capName: "recording",
38628
+ capScope: "system",
38629
+ addonId: null,
38630
+ access: "view"
38631
+ },
38111
38632
  "recording.getPlacement": {
38112
38633
  capName: "recording",
38113
38634
  capScope: "system",
@@ -38126,6 +38647,12 @@ Object.freeze({
38126
38647
  addonId: null,
38127
38648
  access: "view"
38128
38649
  },
38650
+ "recording.getStatus": {
38651
+ capName: "recording",
38652
+ capScope: "system",
38653
+ addonId: null,
38654
+ access: "view"
38655
+ },
38129
38656
  "recording.getStorageMigrationMoveStatus": {
38130
38657
  capName: "recording",
38131
38658
  capScope: "system",
@@ -38330,6 +38857,12 @@ Object.freeze({
38330
38857
  addonId: null,
38331
38858
  access: "delete"
38332
38859
  },
38860
+ "sceneMonitor.getStatus": {
38861
+ capName: "scene-monitor",
38862
+ capScope: "device",
38863
+ addonId: null,
38864
+ access: "view"
38865
+ },
38333
38866
  "sceneMonitor.listScenes": {
38334
38867
  capName: "scene-monitor",
38335
38868
  capScope: "device",
@@ -38360,6 +38893,12 @@ Object.freeze({
38360
38893
  addonId: null,
38361
38894
  access: "create"
38362
38895
  },
38896
+ "scriptRunner.getStatus": {
38897
+ capName: "script-runner",
38898
+ capScope: "device",
38899
+ addonId: null,
38900
+ access: "view"
38901
+ },
38363
38902
  "scriptRunner.run": {
38364
38903
  capName: "script-runner",
38365
38904
  capScope: "device",
@@ -38492,6 +39031,12 @@ Object.freeze({
38492
39031
  addonId: null,
38493
39032
  access: "create"
38494
39033
  },
39034
+ "smoke.getStatus": {
39035
+ capName: "smoke",
39036
+ capScope: "device",
39037
+ addonId: null,
39038
+ access: "view"
39039
+ },
38495
39040
  "smtpProvider.getStatus": {
38496
39041
  capName: "smtp-provider",
38497
39042
  capScope: "system",
@@ -38510,12 +39055,30 @@ Object.freeze({
38510
39055
  addonId: null,
38511
39056
  access: "view"
38512
39057
  },
39058
+ "snapshot.applyDeviceSettingsPatch": {
39059
+ capName: "snapshot",
39060
+ capScope: "device",
39061
+ addonId: null,
39062
+ access: "create"
39063
+ },
38513
39064
  "snapshot.getDebugState": {
38514
39065
  capName: "snapshot",
38515
39066
  capScope: "device",
38516
39067
  addonId: null,
38517
39068
  access: "view"
38518
39069
  },
39070
+ "snapshot.getDeviceLiveContribution": {
39071
+ capName: "snapshot",
39072
+ capScope: "device",
39073
+ addonId: null,
39074
+ access: "view"
39075
+ },
39076
+ "snapshot.getDeviceSettingsContribution": {
39077
+ capName: "snapshot",
39078
+ capScope: "device",
39079
+ addonId: null,
39080
+ access: "view"
39081
+ },
38519
39082
  "snapshot.getSnapshot": {
38520
39083
  capName: "snapshot",
38521
39084
  capScope: "device",
@@ -38534,6 +39097,12 @@ Object.freeze({
38534
39097
  addonId: null,
38535
39098
  access: "view"
38536
39099
  },
39100
+ "snapshot.getStatus": {
39101
+ capName: "snapshot",
39102
+ capScope: "device",
39103
+ addonId: null,
39104
+ access: "view"
39105
+ },
38537
39106
  "snapshot.invalidateCache": {
38538
39107
  capName: "snapshot",
38539
39108
  capScope: "device",
@@ -38870,6 +39439,12 @@ Object.freeze({
38870
39439
  addonId: null,
38871
39440
  access: "create"
38872
39441
  },
39442
+ "streamBroker.applyDeviceSettingsPatch": {
39443
+ capName: "stream-broker",
39444
+ capScope: "system",
39445
+ addonId: null,
39446
+ access: "create"
39447
+ },
38873
39448
  "streamBroker.assignProfile": {
38874
39449
  capName: "stream-broker",
38875
39450
  capScope: "system",
@@ -38906,6 +39481,18 @@ Object.freeze({
38906
39481
  addonId: null,
38907
39482
  access: "view"
38908
39483
  },
39484
+ "streamBroker.getDeviceLiveContribution": {
39485
+ capName: "stream-broker",
39486
+ capScope: "system",
39487
+ addonId: null,
39488
+ access: "view"
39489
+ },
39490
+ "streamBroker.getDeviceSettingsContribution": {
39491
+ capName: "stream-broker",
39492
+ capScope: "system",
39493
+ addonId: null,
39494
+ access: "view"
39495
+ },
38909
39496
  "streamBroker.getPreBufferInfo": {
38910
39497
  capName: "stream-broker",
38911
39498
  capScope: "system",
@@ -39104,6 +39691,12 @@ Object.freeze({
39104
39691
  addonId: null,
39105
39692
  access: "view"
39106
39693
  },
39694
+ "streamParams.getStatus": {
39695
+ capName: "stream-params",
39696
+ capScope: "device",
39697
+ addonId: null,
39698
+ access: "view"
39699
+ },
39107
39700
  "streamParams.setProfile": {
39108
39701
  capName: "stream-params",
39109
39702
  capScope: "device",
@@ -39116,6 +39709,12 @@ Object.freeze({
39116
39709
  addonId: null,
39117
39710
  access: "view"
39118
39711
  },
39712
+ "switch.getStatus": {
39713
+ capName: "switch",
39714
+ capScope: "device",
39715
+ addonId: null,
39716
+ access: "view"
39717
+ },
39119
39718
  "switch.setState": {
39120
39719
  capName: "switch",
39121
39720
  capScope: "device",
@@ -39212,6 +39811,18 @@ Object.freeze({
39212
39811
  addonId: null,
39213
39812
  access: "create"
39214
39813
  },
39814
+ "tamper.getStatus": {
39815
+ capName: "tamper",
39816
+ capScope: "device",
39817
+ addonId: null,
39818
+ access: "view"
39819
+ },
39820
+ "temperatureSensor.getStatus": {
39821
+ capName: "temperature-sensor",
39822
+ capScope: "device",
39823
+ addonId: null,
39824
+ access: "view"
39825
+ },
39215
39826
  "terminalSession.adoptLegacyMonitor": {
39216
39827
  capName: "terminal-session",
39217
39828
  capScope: "system",
@@ -39308,6 +39919,12 @@ Object.freeze({
39308
39919
  addonId: null,
39309
39920
  access: "view"
39310
39921
  },
39922
+ "update.getStatus": {
39923
+ capName: "update",
39924
+ capScope: "device",
39925
+ addonId: null,
39926
+ access: "view"
39927
+ },
39311
39928
  "update.installUpdate": {
39312
39929
  capName: "update",
39313
39930
  capScope: "device",
@@ -39530,6 +40147,12 @@ Object.freeze({
39530
40147
  addonId: null,
39531
40148
  access: "create"
39532
40149
  },
40150
+ "vacuumControl.getStatus": {
40151
+ capName: "vacuum-control",
40152
+ capScope: "device",
40153
+ addonId: null,
40154
+ access: "view"
40155
+ },
39533
40156
  "vacuumControl.locate": {
39534
40157
  capName: "vacuum-control",
39535
40158
  capScope: "device",
@@ -39572,6 +40195,12 @@ Object.freeze({
39572
40195
  addonId: null,
39573
40196
  access: "create"
39574
40197
  },
40198
+ "valve.getStatus": {
40199
+ capName: "valve",
40200
+ capScope: "device",
40201
+ addonId: null,
40202
+ access: "view"
40203
+ },
39575
40204
  "valve.open": {
39576
40205
  capName: "valve",
39577
40206
  capScope: "device",
@@ -39644,6 +40273,12 @@ Object.freeze({
39644
40273
  addonId: null,
39645
40274
  access: "create"
39646
40275
  },
40276
+ "vibration.getStatus": {
40277
+ capName: "vibration",
40278
+ capScope: "device",
40279
+ addonId: null,
40280
+ access: "view"
40281
+ },
39647
40282
  "videoclips.getClipPlayback": {
39648
40283
  capName: "videoclips",
39649
40284
  capScope: "device",
@@ -39680,6 +40315,12 @@ Object.freeze({
39680
40315
  addonId: null,
39681
40316
  access: "view"
39682
40317
  },
40318
+ "waterHeater.getStatus": {
40319
+ capName: "water-heater",
40320
+ capScope: "device",
40321
+ addonId: null,
40322
+ access: "view"
40323
+ },
39683
40324
  "waterHeater.setAway": {
39684
40325
  capName: "water-heater",
39685
40326
  capScope: "device",
@@ -39698,6 +40339,12 @@ Object.freeze({
39698
40339
  addonId: null,
39699
40340
  access: "create"
39700
40341
  },
40342
+ "weather.getStatus": {
40343
+ capName: "weather",
40344
+ capScope: "device",
40345
+ addonId: null,
40346
+ access: "view"
40347
+ },
39701
40348
  "webrtcSession.addIceCandidate": {
39702
40349
  capName: "webrtc-session",
39703
40350
  capScope: "device",
@@ -90660,6 +91307,153 @@ function createClipThumbnailMinter(input) {
90660
91307
  };
90661
91308
  }
90662
91309
  //#endregion
91310
+ //#region src/hksv/recording-active.ts
91311
+ /**
91312
+ * `true`/`false` as HomeKit holds it, or `null` when this accessory does not
91313
+ * offer recording at all.
91314
+ */
91315
+ function readHomekitRecordingActive(accessories) {
91316
+ for (const accessory of accessories) {
91317
+ const service = accessory.getService(import_dist.Service.CameraRecordingManagement);
91318
+ if (service === void 0) continue;
91319
+ const value = service.getCharacteristic(import_dist.Characteristic.Active).value;
91320
+ if (typeof value === "number") return value === 1;
91321
+ if (typeof value === "boolean") return value;
91322
+ return null;
91323
+ }
91324
+ return null;
91325
+ }
91326
+ /**
91327
+ * One sentence for the settings page: what HomeKit says, or why it was never
91328
+ * asked.
91329
+ *
91330
+ * The cost stays in the text even though the switch is gone. It is still the
91331
+ * operator's to understand — a camera recording here runs a permanent ffmpeg —
91332
+ * and a status line that reported only the state would have quietly dropped
91333
+ * the one number that decides whether to turn it on.
91334
+ */
91335
+ function describeHomekitRecording(active, lastBuild) {
91336
+ if (active === true) return "HomeKit is recording this camera. Turn it off in the Home app (Camera settings → “Stream”), not here — iOS owns this switch and keeps it across reboots. While it is on, this camera runs a permanent prebuffer (~0.7% of one CPU core on the copy branch).";
91337
+ if (active === false) return "This camera offers recording and HomeKit has it off. Turn it on in the Home app (Camera settings → “Stream and Allow Recording”); it needs iCloud+ and a home hub. Nothing runs here until you do.";
91338
+ if (lastBuild !== null && !lastBuild.advertised) return `Recording is not offered for this camera: ${lastBuild.detail ?? "the accessory could not advertise it"}.`;
91339
+ return "Not known yet — this camera’s accessory has not published its recording service in this process. It appears once the accessory is built.";
91340
+ }
91341
+ //#endregion
91342
+ //#region src/hksv/recording-source.ts
91343
+ /** The tallest frame the recording path will hold in its prebuffer. */
91344
+ var MAX_RECORDING_HEIGHT = 1080;
91345
+ /** HKSV takes H.264 only — AAC audio and H.264 video, no negotiation. */
91346
+ var RECORDABLE_CODEC = "h264";
91347
+ /**
91348
+ * Pick the slot the recording child pulls.
91349
+ *
91350
+ * Deliberately NOT `pickPreferredRtspEntry`: that picker resolves the operator's
91351
+ * LIVE preference and, on `auto`, steers by the resolution iOS negotiated for a
91352
+ * live session — neither is a fact about recording, and on 615 it selects `mid`,
91353
+ * a 10 fps slot. Recording has one criterion, applied here and nowhere else:
91354
+ * the largest copyable frame that does not exceed {@link MAX_RECORDING_HEIGHT}.
91355
+ */
91356
+ function pickRecordingSource(entries, options = {}) {
91357
+ const enabled = entries.filter((e) => e.enabled);
91358
+ if (enabled.length === 0) return {
91359
+ ok: false,
91360
+ refusal: "no-enabled-stream"
91361
+ };
91362
+ const h264 = enabled.filter((e) => normaliseCodec(e.codec) === RECORDABLE_CODEC);
91363
+ const pool = h264.length > 0 ? h264 : options.allowTranscode === true ? enabled : [];
91364
+ if (pool.length === 0) return {
91365
+ ok: false,
91366
+ refusal: "no-h264-stream"
91367
+ };
91368
+ const transcode = h264.length === 0;
91369
+ const sized = pool.filter(hasUsableResolution);
91370
+ if (sized.length === 0) return {
91371
+ ok: false,
91372
+ refusal: "no-resolution"
91373
+ };
91374
+ const withinCeiling = sized.filter((e) => height(e) <= MAX_RECORDING_HEIGHT);
91375
+ const within = withinCeiling.length > 0;
91376
+ const best = [...within ? withinCeiling : sized].sort((a, b) => within ? height(b) - height(a) : height(a) - height(b))[0];
91377
+ if (best === void 0 || best.resolution === void 0) return {
91378
+ ok: false,
91379
+ refusal: "no-resolution"
91380
+ };
91381
+ return {
91382
+ ok: true,
91383
+ source: {
91384
+ brokerId: best.brokerId,
91385
+ profile: best.profile ?? best.brokerId,
91386
+ transcode,
91387
+ url: best.url,
91388
+ mutedUrl: best.mutedUrl,
91389
+ width: best.resolution.width,
91390
+ height: best.resolution.height
91391
+ }
91392
+ };
91393
+ }
91394
+ /** A one-line reason for the log — silence about a withdrawn service reads as a bug. */
91395
+ function refusalReason(refusal) {
91396
+ switch (refusal) {
91397
+ case "no-enabled-stream": return "the camera has no enabled RTSP profile";
91398
+ case "no-h264-stream": return "every enabled profile is H.265 — HKSV takes H.264 only, and a permanent transcode costs 128x a copy";
91399
+ case "no-resolution": return "no enabled profile declares a resolution, so nothing honest could be advertised";
91400
+ }
91401
+ }
91402
+ function height(entry) {
91403
+ return entry.resolution?.height ?? 0;
91404
+ }
91405
+ function hasUsableResolution(entry) {
91406
+ const r = entry.resolution;
91407
+ return r !== void 0 && r.width > 0 && r.height > 0;
91408
+ }
91409
+ /** Publishers spell H.265 four ways; the same normalisation the broker uses. */
91410
+ function normaliseCodec(codec) {
91411
+ return (codec ?? "").toLowerCase().replace(/[.\s-]/g, "");
91412
+ }
91413
+ //#endregion
91414
+ //#region src/hksv/recordability-token.ts
91415
+ /** No slot is recordable. A real answer, and a fingerprint-moving one. */
91416
+ var RECORDABILITY_NONE = "rec:none";
91417
+ /**
91418
+ * The slot HKSV would record from, as a fingerprint token.
91419
+ *
91420
+ * `null` when `entries` is null — the profiles could not be read, which is not
91421
+ * a claim about the camera.
91422
+ */
91423
+ function recordabilityToken(entries, options = {}) {
91424
+ if (entries === null) return null;
91425
+ const choice = pickRecordingSource(entries, options);
91426
+ return choice.ok ? `rec:${choice.source.profile}${choice.source.transcode ? "+x" : ""}` : RECORDABILITY_NONE;
91427
+ }
91428
+ var TOKEN_SUFFIX = /\|(rec:[^|]*)$/;
91429
+ /**
91430
+ * Attach the recording token to a device-shape fingerprint.
91431
+ *
91432
+ * On an unreadable read the persisted token is carried forward — but only onto
91433
+ * the base it was persisted against. If the device's SHAPE also changed, that
91434
+ * change is a rebuild in its own right and the token has nothing to say about
91435
+ * it; inventing one there would advertise a recording slot nobody read.
91436
+ */
91437
+ function withRecordabilityToken(input) {
91438
+ if (input.token !== null) return `${input.base}|${input.token}`;
91439
+ const carried = input.persisted?.match(TOKEN_SUFFIX);
91440
+ if (carried === null || carried === void 0) return input.base;
91441
+ return carried.input?.slice(0, carried.index) === input.base ? `${input.base}|${carried[1]}` : input.base;
91442
+ }
91443
+ /**
91444
+ * What this camera could record, as one fingerprint token.
91445
+ *
91446
+ * There used to be an `rec:off` here, for a per-camera switch of ours. That
91447
+ * switch is gone (D589): whether HomeKit RECORDS is HomeKit's own persisted
91448
+ * characteristic, it does not change the accessory's shape, and a rebuild is
91449
+ * about shape. What still belongs here is the CAPABILITY — which slot, and
91450
+ * whether serving it means running an encoder — because those are two
91451
+ * different accessories to build.
91452
+ */
91453
+ function recordabilityTokenFor(input) {
91454
+ return recordabilityToken(input.entries, { allowTranscode: input.allowTranscode !== false });
91455
+ }
91456
+ //#endregion
90663
91457
  //#region src/hksv/videoclips-plane.ts
90664
91458
  /**
90665
91459
  * The two data-plane routes behind the HomeKit clip source.
@@ -90922,8 +91716,9 @@ function createHksvClipPlanes(deps) {
90922
91716
  *
90923
91717
  * ## The source EXISTS because of HomeKit, and is not a switch
90924
91718
  *
90925
- * A camera has this source because it is exported to HomeKit AND HomeKit
90926
- * recording is on for it; turn either off and the row is gone (D550, D569 § 5).
91719
+ * A camera has this source because it is exported to HomeKit and HomeKit has
91720
+ * not said no to recording it; un-export it, or switch recording off in the
91721
+ * Home app, and the row is gone (D550, D569 § 5, D589).
90927
91722
  * There is deliberately no enable control of its own — a knob over somebody
90928
91723
  * else's decision is D62's defect. `keepClips` is NOT that gate either: it
90929
91724
  * governs the TEE, and the clips already kept remain the truth about this
@@ -90970,7 +91765,7 @@ function createHksvVideoclipsProvider(deps) {
90970
91765
  */
90971
91766
  async function measure(deviceId) {
90972
91767
  const facts = deps.describeCamera(deviceId);
90973
- if (!facts.exported || !facts.recording) return null;
91768
+ if (!facts.exported || facts.homekitRecording === false) return null;
90974
91769
  const catalog = await deps.readCatalog(deviceId);
90975
91770
  return {
90976
91771
  availability: availabilityFor(facts, catalog),
@@ -106325,6 +107120,72 @@ function buildRecordingOptions(input) {
106325
107120
  };
106326
107121
  }
106327
107122
  //#endregion
107123
+ //#region src/hksv/recording-invocation.ts
107124
+ /**
107125
+ * The resolution the accessory may advertise, which is the one it can deliver.
107126
+ *
107127
+ * A copy is advertised at its source size whatever that is — there is no
107128
+ * encoder to resize with, and advertising a size we cannot produce is the
107129
+ * "stream that does not match its own advertisement" defect in miniature.
107130
+ * A transcode above the ceiling is fitted down, because with an encoder in the
107131
+ * pipeline the 6.35 MB 4K fragment is a choice rather than a fact, and iOS
107132
+ * renders HKSV clips at 1080p regardless.
107133
+ */
107134
+ function recordingOutput(source) {
107135
+ if (!source.transcode || source.height <= 1080) return {
107136
+ width: source.width,
107137
+ height: source.height,
107138
+ scaled: false
107139
+ };
107140
+ return {
107141
+ width: Math.max(2, Math.round(source.width * MAX_RECORDING_HEIGHT / source.height / 2) * 2),
107142
+ height: MAX_RECORDING_HEIGHT,
107143
+ scaled: true
107144
+ };
107145
+ }
107146
+ /** The invocation, minus the sink and sidecar the child owns. */
107147
+ function buildRecordingInvocation(input) {
107148
+ const { source } = input;
107149
+ const audio = input.audioActive ? {
107150
+ kind: "encode",
107151
+ codec: "aac",
107152
+ bitrateKbps: 32,
107153
+ sampleRateHz: HKSV_AUDIO_SAMPLE_RATE_HZ,
107154
+ channels: 1
107155
+ } : { kind: "none" };
107156
+ const out = recordingOutput(source);
107157
+ const video = source.transcode ? {
107158
+ kind: "encode",
107159
+ encoder: "libx264",
107160
+ preset: "veryfast",
107161
+ profile: "main",
107162
+ level: "4.0",
107163
+ pixelFormat: "yuv420p",
107164
+ forceKeyFramesSeconds: Math.max(1, Math.round(input.fragmentMs / 1e3)),
107165
+ bf: 0,
107166
+ ...out.scaled ? { scale: {
107167
+ mode: "fit",
107168
+ width: out.width,
107169
+ height: out.height
107170
+ } } : {}
107171
+ } : { kind: "copy" };
107172
+ return {
107173
+ logLevel: "error",
107174
+ decodeHwAccel: source.transcode ? input.decodeHwAccel : null,
107175
+ input: {
107176
+ url: input.audioActive ? source.url : source.mutedUrl,
107177
+ rtspTransport: "tcp",
107178
+ ...source.transcode && input.decodeInputArgs !== void 0 && input.decodeInputArgs.length > 0 ? { extraArgs: input.decodeInputArgs } : {},
107179
+ analyzeDurationUs: 1e6,
107180
+ probeSizeBytes: 1e6
107181
+ },
107182
+ video,
107183
+ audio,
107184
+ threadCount: 0,
107185
+ outputArgs: []
107186
+ };
107187
+ }
107188
+ //#endregion
106328
107189
  //#region src/hksv/fragment-source.ts
106329
107190
  /**
106330
107191
  * How far back the prebuffer ring reaches.
@@ -106492,6 +107353,7 @@ var HksvFragmentSource = class {
106492
107353
  meta: {
106493
107354
  brokerId: this.input.source.brokerId,
106494
107355
  resolution: `${this.input.source.width}x${this.input.source.height}`,
107356
+ branch: this.input.source.transcode ? "transcode" : "copy",
106495
107357
  fragmentMs: this.input.fragmentMs,
106496
107358
  audioActive: this.audioActive,
106497
107359
  windowMs: PREBUFFER_WINDOW_MS
@@ -106556,27 +107418,13 @@ var HksvFragmentSource = class {
106556
107418
  * takes AAC only — which the same measurement priced at 0.4 % of a core.
106557
107419
  */
106558
107420
  buildInvocation() {
106559
- const audio = this.audioActive ? {
106560
- kind: "encode",
106561
- codec: "aac",
106562
- bitrateKbps: 32,
106563
- sampleRateHz: HKSV_AUDIO_SAMPLE_RATE_HZ,
106564
- channels: 1
106565
- } : { kind: "none" };
106566
- return {
106567
- logLevel: "error",
106568
- decodeHwAccel: null,
106569
- input: {
106570
- url: this.audioActive ? this.input.source.url : this.input.source.mutedUrl,
106571
- rtspTransport: "tcp",
106572
- analyzeDurationUs: 1e6,
106573
- probeSizeBytes: 1e6
106574
- },
106575
- video: { kind: "copy" },
106576
- audio,
106577
- threadCount: 0,
106578
- outputArgs: []
106579
- };
107421
+ return buildRecordingInvocation({
107422
+ source: this.input.source,
107423
+ audioActive: this.audioActive,
107424
+ fragmentMs: this.input.fragmentMs,
107425
+ decodeHwAccel: this.input.decodeHwAccel ?? null,
107426
+ ...this.input.decodeInputArgs === void 0 ? {} : { decodeInputArgs: this.input.decodeInputArgs }
107427
+ });
106580
107428
  }
106581
107429
  };
106582
107430
  //#endregion
@@ -106936,74 +107784,6 @@ var HksvRecordingDelegate = class {
106936
107784
  }
106937
107785
  };
106938
107786
  //#endregion
106939
- //#region src/hksv/recording-source.ts
106940
- /** The tallest frame the recording path will hold in its prebuffer. */
106941
- var MAX_RECORDING_HEIGHT = 1080;
106942
- /** HKSV takes H.264 only — AAC audio and H.264 video, no negotiation. */
106943
- var RECORDABLE_CODEC = "h264";
106944
- /**
106945
- * Pick the slot the recording child pulls.
106946
- *
106947
- * Deliberately NOT `pickPreferredRtspEntry`: that picker resolves the operator's
106948
- * LIVE preference and, on `auto`, steers by the resolution iOS negotiated for a
106949
- * live session — neither is a fact about recording, and on 615 it selects `mid`,
106950
- * a 10 fps slot. Recording has one criterion, applied here and nowhere else:
106951
- * the largest copyable frame that does not exceed {@link MAX_RECORDING_HEIGHT}.
106952
- */
106953
- function pickRecordingSource(entries) {
106954
- const enabled = entries.filter((e) => e.enabled);
106955
- if (enabled.length === 0) return {
106956
- ok: false,
106957
- refusal: "no-enabled-stream"
106958
- };
106959
- const h264 = enabled.filter((e) => normaliseCodec(e.codec) === RECORDABLE_CODEC);
106960
- if (h264.length === 0) return {
106961
- ok: false,
106962
- refusal: "no-h264-stream"
106963
- };
106964
- const sized = h264.filter(hasUsableResolution);
106965
- if (sized.length === 0) return {
106966
- ok: false,
106967
- refusal: "no-resolution"
106968
- };
106969
- const withinCeiling = sized.filter((e) => height(e) <= MAX_RECORDING_HEIGHT);
106970
- const best = [...withinCeiling.length > 0 ? withinCeiling : sized].sort((a, b) => withinCeiling.length > 0 ? height(b) - height(a) : height(a) - height(b))[0];
106971
- if (best === void 0 || best.resolution === void 0) return {
106972
- ok: false,
106973
- refusal: "no-resolution"
106974
- };
106975
- return {
106976
- ok: true,
106977
- source: {
106978
- brokerId: best.brokerId,
106979
- profile: best.profile ?? best.brokerId,
106980
- url: best.url,
106981
- mutedUrl: best.mutedUrl,
106982
- width: best.resolution.width,
106983
- height: best.resolution.height
106984
- }
106985
- };
106986
- }
106987
- /** A one-line reason for the log — silence about a withdrawn service reads as a bug. */
106988
- function refusalReason(refusal) {
106989
- switch (refusal) {
106990
- case "no-enabled-stream": return "the camera has no enabled RTSP profile";
106991
- case "no-h264-stream": return "every enabled profile is H.265 — HKSV takes H.264 only, and a permanent transcode costs 128x a copy";
106992
- case "no-resolution": return "no enabled profile declares a resolution, so nothing honest could be advertised";
106993
- }
106994
- }
106995
- function height(entry) {
106996
- return entry.resolution?.height ?? 0;
106997
- }
106998
- function hasUsableResolution(entry) {
106999
- const r = entry.resolution;
107000
- return r !== void 0 && r.width > 0 && r.height > 0;
107001
- }
107002
- /** Publishers spell H.265 four ways; the same normalisation the broker uses. */
107003
- function normaliseCodec(codec) {
107004
- return (codec ?? "").toLowerCase().replace(/[.\s-]/g, "");
107005
- }
107006
- //#endregion
107007
107787
  //#region src/hksv/build-recording.ts
107008
107788
  /**
107009
107789
  * Assemble HomeKit Secure Video for one camera — the ADVERTISEMENT and the
@@ -107055,7 +107835,7 @@ async function buildHksvRecording(input) {
107055
107835
  note("profiles-unreadable", "cameraStreams.getProfileRtspEntries could not be read");
107056
107836
  return null;
107057
107837
  }
107058
- const choice = pickRecordingSource(entries);
107838
+ const choice = pickRecordingSource(entries, { allowTranscode: bctx.options.hapDeviceSettings?.allowTranscode !== false });
107059
107839
  if (!choice.ok) {
107060
107840
  log.info("export-hap: HKSV withheld — no recordable stream", { meta: {
107061
107841
  refusal: choice.refusal,
@@ -107065,9 +107845,10 @@ async function buildHksvRecording(input) {
107065
107845
  return null;
107066
107846
  }
107067
107847
  const source = choice.source;
107848
+ const decodeHwAccel = source.transcode ? await resolveDecodeHwAccel(bctx, log) : null;
107068
107849
  const gopMs = await readSourceGopMs(bctx, source.width, source.height);
107069
- const fragmentLengthMs = deriveFragmentLengthMs(gopMs);
107070
- if (fragmentLengthMs === null) {
107850
+ const fragmentLengthMs = source.transcode ? HKSV_FRAGMENT_LENGTHS_MS[0] : deriveFragmentLengthMs(gopMs);
107851
+ if (fragmentLengthMs === void 0 || fragmentLengthMs === null) {
107071
107852
  log.warn("export-hap: HKSV withheld — the camera key-frame interval is longer than any fragment length we advertise", { meta: {
107072
107853
  gopMs,
107073
107854
  brokerId: source.brokerId
@@ -107078,9 +107859,10 @@ async function buildHksvRecording(input) {
107078
107859
  const parsedProfile = CamProfileSchema.safeParse(source.profile);
107079
107860
  const recordedProfile = parsedProfile.success ? parsedProfile.data : null;
107080
107861
  const fps = resolveFps(input.fpsByProfile, source.profile);
107862
+ const out = recordingOutput(source);
107081
107863
  const options = buildRecordingOptions({
107082
- width: source.width,
107083
- height: source.height,
107864
+ width: out.width,
107865
+ height: out.height,
107084
107866
  fps,
107085
107867
  fragmentLengthMs
107086
107868
  });
@@ -107090,8 +107872,8 @@ async function buildHksvRecording(input) {
107090
107872
  logger: log,
107091
107873
  deviceId: numericDeviceId,
107092
107874
  store: clipStore,
107093
- width: source.width,
107094
- height: source.height,
107875
+ width: out.width,
107876
+ height: out.height,
107095
107877
  fragmentMs: fragmentLengthMs,
107096
107878
  ...recordedProfile === null ? {} : { profile: recordedProfile }
107097
107879
  });
@@ -107110,14 +107892,21 @@ async function buildHksvRecording(input) {
107110
107892
  spawnFn: spawn,
107111
107893
  source,
107112
107894
  fragmentMs,
107113
- audioActive
107895
+ audioActive,
107896
+ decodeHwAccel: decodeHwAccel?.hwaccel ?? null,
107897
+ ...decodeHwAccel === null || decodeHwAccel.extraInputArgs.length === 0 ? {} : { decodeInputArgs: decodeHwAccel.extraInputArgs }
107114
107898
  })
107115
107899
  });
107116
107900
  const advertisedResolution = options.video.resolutions[0];
107117
107901
  log.info("export-hap: HKSV ADVERTISED — recording is offered for this camera", { meta: {
107118
107902
  brokerId: source.brokerId,
107119
107903
  profile: source.profile,
107120
- resolution: `${source.width}x${source.height}`,
107904
+ resolution: `${out.width}x${out.height}`,
107905
+ branch: source.transcode ? "transcode" : "copy",
107906
+ ...source.transcode ? {
107907
+ sourceResolution: `${source.width}x${source.height}`,
107908
+ decodeHwAccel: decodeHwAccel?.hwaccel ?? "software"
107909
+ } : {},
107121
107910
  measuredFps: fps,
107122
107911
  advertisedFps: advertisedResolution?.[2] ?? null,
107123
107912
  fragmentLengthMs,
@@ -107132,6 +107921,27 @@ async function buildHksvRecording(input) {
107132
107921
  dispose: () => delegate.dispose()
107133
107922
  };
107134
107923
  }
107924
+ /**
107925
+ * The `-hwaccel` value for a transcoding prebuffer, and the flags that travel
107926
+ * with it.
107927
+ *
107928
+ * Software is a legitimate answer and is always NAMED: a camera that quietly
107929
+ * stopped using the GPU and one that never had it look identical in a CPU
107930
+ * graph, and on this branch the difference is most of a core, permanently.
107931
+ */
107932
+ async function resolveDecodeHwAccel(bctx, log) {
107933
+ const decision = selectHwDecode({
107934
+ transcode: true,
107935
+ reading: await probeDecoderHwaccel({
107936
+ source: decoderInfoSourceFromContext(bctx.ctx),
107937
+ log,
107938
+ memo: bctx.options.decodeMemos.reading
107939
+ }),
107940
+ platform: process.platform
107941
+ });
107942
+ if (decision.kind === "software") log.warn("export-hap: HKSV will transcode in SOFTWARE — this costs most of a core, permanently", { meta: { reason: decision.reason } });
107943
+ return decision;
107944
+ }
107135
107945
  /** `cameraStreams.getProfileRtspEntries`, or `null` when the cap is unreachable. */
107136
107946
  async function readProfileEntries(bctx) {
107137
107947
  try {
@@ -108127,13 +108937,12 @@ async function buildCameraAccessory(input) {
108127
108937
  bctx,
108128
108938
  streamingOptions: streams.streamingOptions
108129
108939
  }));
108130
- const recordingEnabled = options.hapDeviceSettings.hksvRecording === true;
108131
108940
  let recordingAudioActive = true;
108132
- const recording = recordingEnabled ? await buildHksvRecording({
108941
+ const recording = await buildHksvRecording({
108133
108942
  bctx,
108134
108943
  fpsByProfile: advertisedVideo.fpsByProfile,
108135
108944
  isAudioActive: () => recordingAudioActive
108136
- }) : null;
108945
+ });
108137
108946
  const controller = new (isDoorbell ? import_dist.DoorbellController : import_dist.CameraController)({
108138
108947
  delegate: streams.delegate,
108139
108948
  streamingOptions: streams.streamingOptions,
@@ -108382,38 +109191,7 @@ function syncStateToJson(map) {
108382
109191
  * that matters about it: it must resolve to NOT recording, or "off by default"
108383
109192
  * is true only for cameras exposed before the flip.
108384
109193
  */
108385
- var DEFAULT_DEVICE_SETTINGS = {
108386
- streamPreference: "auto",
108387
- hksvRecording: false
108388
- };
108389
- /**
108390
- * OFF unless explicitly switched on — operator decision 2026-09-21, reversing
108391
- * the 2026-08-08 flip:
108392
- *
108393
- * > *"il recording deve essere off di default, non deve essere attivo quando
108394
- * > si esporta una camera su HKSV"*
108395
- *
108396
- * What changed is not the cost the earlier record argued about — recording on
108397
- * the copy branch really is 0.7 % of a core and ~30 MB (D84), and cheap it
108398
- * remains. What changed is that on-by-default made the switch **say something
108399
- * the system was not doing**. Measured on 2026-09-21: 592, 587, 618 and 640
108400
- * all reported HomeKit recording ON while the advertisement was WITHHELD
108401
- * (`no-enabled-stream` — no enabled RTSP profile), so HomeKit never recorded
108402
- * them and the tee had nothing to keep. The operator was being told four
108403
- * cameras were recording because nobody had turned them off.
108404
- *
108405
- * Exposing a camera to HomeKit is a decision about EXPOSURE. Recording is a
108406
- * second decision and now has to be made.
108407
- *
108408
- * ABSENT resolves OFF — the load-bearing case, for the same reason it was
108409
- * before and with the opposite answer: a camera whose settings predate the
108410
- * field, or that was exposed before today, was never asked about. Every read
108411
- * goes through this one resolver (`=== true`), never a scattered `!== false`,
108412
- * because those two disagree about exactly that case.
108413
- */
108414
- function resolveHksvRecording(settings) {
108415
- return settings?.hksvRecording === true;
108416
- }
109194
+ var DEFAULT_DEVICE_SETTINGS = { streamPreference: "auto" };
108417
109195
  /**
108418
109196
  * Whether this camera offers Apple's NEW camera service alongside the classic
108419
109197
  * one — `camera-multi-tier-rtp-stream-management` (0x8031), where HEVC and 4K
@@ -108456,11 +109234,11 @@ var EXPORT_HAP_ADDON_ID = "export-hap";
108456
109234
  * stays and says the copies are off, which is D62's other half: an off switch
108457
109235
  * is REPORTED off, never made to look like a broken camera.
108458
109236
  */
108459
- function describeHksvCamera(entry, lastBuild) {
109237
+ function describeHksvCamera(entry, lastBuild, homekitRecording) {
108460
109238
  const isCamera = entry !== null && entry.mapperKind === "camera";
108461
109239
  return {
108462
109240
  exported: isCamera,
108463
- recording: isCamera && resolveHksvRecording(entry.settings),
109241
+ homekitRecording: isCamera ? homekitRecording : null,
108464
109242
  keepingClips: isCamera && resolveKeepHomekitClips(entry.settings),
108465
109243
  lastBuild
108466
109244
  };
@@ -108704,7 +109482,7 @@ var ExportHapAddon = class extends BaseAddon {
108704
109482
  const clipsProvider = createHksvVideoclipsProvider({
108705
109483
  logger: this.ctx.logger.child("hksv-clips"),
108706
109484
  addonId: EXPORT_HAP_ADDON_ID,
108707
- describeCamera: (deviceId) => describeHksvCamera(this.findEntry(deviceId), this.hksvBuilds.lastFor(deviceId)),
109485
+ describeCamera: (deviceId) => describeHksvCamera(this.findEntry(deviceId), this.hksvBuilds.lastFor(deviceId), readHomekitRecordingActive(this.exposed.get(String(deviceId))?.accessories ?? [])),
108708
109486
  readCatalog: async (deviceId) => await this.hksvClipStore?.listCatalog(deviceId) ?? { kind: "no-root" },
108709
109487
  locationRefusal: () => this.hksvClipLocation?.lastRefusal ?? null,
108710
109488
  mediaUrl: (deviceId, stem) => this.hksvClipUrl("media", deviceId, stem),
@@ -108860,7 +109638,6 @@ var ExportHapAddon = class extends BaseAddon {
108860
109638
  },
108861
109639
  hapDeviceSettings: {
108862
109640
  streamPreference: entrySettings.streamPreference ?? "auto",
108863
- hksvRecording: resolveHksvRecording(entrySettings),
108864
109641
  keepHomekitClips: resolveKeepHomekitClips(entrySettings),
108865
109642
  multiTierService: resolveMultiTierService(entrySettings),
108866
109643
  allowTranscode: entrySettings.allowTranscode !== false
@@ -108941,15 +109718,40 @@ var ExportHapAddon = class extends BaseAddon {
108941
109718
  features: device.features
108942
109719
  });
108943
109720
  const persisted = syncStateFromJson(this.config.exportSyncState).get(deviceId)?.fingerprint;
109721
+ const withToken = withRecordabilityToken({
109722
+ base: fresh,
109723
+ token: await this.readRecordabilityToken(deviceId, device.type),
109724
+ persisted
109725
+ });
108944
109726
  return resolveExportFingerprint({
108945
109727
  ready: device.probed ?? false,
108946
- fresh,
109728
+ fresh: withToken,
108947
109729
  persisted
108948
109730
  });
108949
109731
  } catch {
108950
109732
  return null;
108951
109733
  }
108952
109734
  }
109735
+ /**
109736
+ * The slot HKSV would record this camera from, for the fingerprint.
109737
+ *
109738
+ * `null` — do not move the fingerprint — for anything that is not a camera
109739
+ * (a switch has no recording decision, and stamping a constant on it would
109740
+ * republish every non-camera accessory once for nothing) and for a camera
109741
+ * whose profiles could not be read right now.
109742
+ */
109743
+ async readRecordabilityToken(deviceId, type) {
109744
+ if (pickMapperKind(type) !== "camera") return null;
109745
+ try {
109746
+ const entries = await (await this.ctx.fetchDevice(deviceId)).cameraStreams?.getProfileRtspEntries({});
109747
+ return recordabilityTokenFor({
109748
+ allowTranscode: this.findEntry(deviceId)?.settings?.allowTranscode !== false,
109749
+ entries: entries ?? null
109750
+ });
109751
+ } catch {
109752
+ return null;
109753
+ }
109754
+ }
108953
109755
  /** Persist a device's current export fingerprint into `exportSyncState`
108954
109756
  * (best-effort; called after a successful attach). */
108955
109757
  async persistFingerprint(deviceId, fingerprint) {
@@ -109215,7 +110017,6 @@ var ExportHapAddon = class extends BaseAddon {
109215
110017
  const enabled = entry !== null;
109216
110018
  const enabledKey = `hap:${deviceId}:enabled`;
109217
110019
  const streamPreferenceKey = `hap:${deviceId}:streamPreference`;
109218
- const hksvKey = `hap:${deviceId}:hksvRecording`;
109219
110020
  const multiTierKey = `hap:${deviceId}:multiTierService`;
109220
110021
  const useBridgeKey = `hap:${deviceId}:useBridge`;
109221
110022
  const allowTranscodeKey = `hap:${deviceId}:allowTranscode`;
@@ -109266,17 +110067,14 @@ var ExportHapAddon = class extends BaseAddon {
109266
110067
  immediate: true
109267
110068
  },
109268
110069
  {
109269
- type: "boolean",
109270
- key: hksvKey,
110070
+ type: "info",
110071
+ key: `hap:${deviceId}:hksvRecordingStatus`,
109271
110072
  label: "HomeKit recording (Secure Video)",
109272
- description: "Offer “Stream and Allow Recording” in iOS Home. Requires iCloud+ and a home hub. Keeps a continuous 8s prebuffer for this camera (~0.7% of one CPU core, H.264 sources only).",
109273
- style: "switch",
109274
- value: resolveHksvRecording(settings),
110073
+ content: describeHomekitRecording(readHomekitRecordingActive(this.exposed.get(String(deviceId))?.accessories ?? []), this.hksvBuilds.lastFor(deviceId)),
109275
110074
  showWhen: {
109276
110075
  field: enabledKey,
109277
110076
  equals: true
109278
- },
109279
- immediate: true
110077
+ }
109280
110078
  },
109281
110079
  {
109282
110080
  type: "boolean",
@@ -109299,7 +110097,7 @@ var ExportHapAddon = class extends BaseAddon {
109299
110097
  style: "switch",
109300
110098
  value: resolveKeepHomekitClips(settings),
109301
110099
  showWhen: {
109302
- field: hksvKey,
110100
+ field: enabledKey,
109303
110101
  equals: true
109304
110102
  },
109305
110103
  immediate: true
@@ -109399,7 +110197,6 @@ var ExportHapAddon = class extends BaseAddon {
109399
110197
  const wasEnabled = this.exposed.has(deviceIdStr);
109400
110198
  const enabledKey = `hap:${deviceId}:enabled`;
109401
110199
  const streamPreferenceKey = `hap:${deviceId}:streamPreference`;
109402
- const hksvKey = `hap:${deviceId}:hksvRecording`;
109403
110200
  const useBridgeKey = `hap:${deviceId}:useBridge`;
109404
110201
  const allowTranscodeKey = `hap:${deviceId}:allowTranscode`;
109405
110202
  const multiTierKey = `hap:${deviceId}:multiTierService`;
@@ -109408,7 +110205,6 @@ var ExportHapAddon = class extends BaseAddon {
109408
110205
  const enabledValue = enabledKey in patch ? Boolean(patch[enabledKey]) : wasEnabled;
109409
110206
  const streamPreferenceRaw = streamPreferenceKey in patch ? patch[streamPreferenceKey] : current?.settings?.streamPreference;
109410
110207
  const streamPreference = typeof streamPreferenceRaw === "string" && streamPreferenceRaw.trim().length > 0 ? streamPreferenceRaw : "auto";
109411
- const hksvRecording = hksvKey in patch ? Boolean(patch[hksvKey]) : resolveHksvRecording(current?.settings);
109412
110208
  const useBridge = useBridgeKey in patch ? Boolean(patch[useBridgeKey]) : current?.settings?.useBridge !== false;
109413
110209
  const allowTranscode = allowTranscodeKey in patch ? Boolean(patch[allowTranscodeKey]) : current?.settings?.allowTranscode !== false;
109414
110210
  const multiTierService = multiTierKey in patch ? Boolean(patch[multiTierKey]) : resolveMultiTierService(current?.settings);
@@ -109420,7 +110216,6 @@ var ExportHapAddon = class extends BaseAddon {
109420
110216
  const nextSettings = {
109421
110217
  ...current?.settings ?? DEFAULT_DEVICE_SETTINGS,
109422
110218
  streamPreference,
109423
- hksvRecording,
109424
110219
  useBridge,
109425
110220
  allowTranscode,
109426
110221
  multiTierService,
@@ -109437,7 +110232,6 @@ var ExportHapAddon = class extends BaseAddon {
109437
110232
  return { success: true };
109438
110233
  }
109439
110234
  const currentPref = current?.settings?.streamPreference ?? "auto";
109440
- const currentHksv = resolveHksvRecording(current?.settings);
109441
110235
  const currentKeepClips = resolveKeepHomekitClips(current?.settings);
109442
110236
  const mapperKind = current?.mapperKind ?? "generic";
109443
110237
  const wasBridged = shouldBridge({
@@ -109449,16 +110243,12 @@ var ExportHapAddon = class extends BaseAddon {
109449
110243
  useBridge
109450
110244
  });
109451
110245
  await this.updateEntrySettings(deviceIdStr, nextSettings);
109452
- if (currentPref !== streamPreference || currentHksv !== hksvRecording || currentKeepClips !== keepClips || wasBridged !== willBridge) {
110246
+ if (currentPref !== streamPreference || currentKeepClips !== keepClips || wasBridged !== willBridge) {
109453
110247
  log.info("export-hap: per-camera export settings changed — refreshing accessory", { meta: {
109454
110248
  streamPreference: {
109455
110249
  from: currentPref,
109456
110250
  to: streamPreference
109457
110251
  },
109458
- hksvRecording: {
109459
- from: currentHksv,
109460
- to: hksvRecording
109461
- },
109462
110252
  keepClips: {
109463
110253
  from: currentKeepClips,
109464
110254
  to: keepClips
@@ -109544,4 +110334,4 @@ function errMsg(err) {
109544
110334
  return err instanceof Error ? err.message : String(err);
109545
110335
  }
109546
110336
  //#endregion
109547
- export { DEFAULT_CLIP_RETENTION_DAYS, DEFAULT_DEVICE_SETTINGS, EXPORT_HAP_ADDON_ID, ExportHapAddon, ExportHapAddon as default, describeHksvCamera, unpublishAccessory as i, initHapStorage as n, publishStandalone as r, resolveClipRetentionDays, resolveHksvRecording, resolveKeepHomekitClips, resolveMultiTierService, deriveUsername as t };
110337
+ export { DEFAULT_CLIP_RETENTION_DAYS, DEFAULT_DEVICE_SETTINGS, EXPORT_HAP_ADDON_ID, ExportHapAddon, ExportHapAddon as default, describeHksvCamera, unpublishAccessory as i, initHapStorage as n, publishStandalone as r, resolveClipRetentionDays, resolveKeepHomekitClips, resolveMultiTierService, deriveUsername as t };