fix(homekit): fix HKSV recording by correcting HDS protocol and adding GOP buffering

The HKSV recording was failing because:
1. The dataSend.data message structure was wrong - `packets` was a flat integer
   instead of an array of objects with `data` and `metadata` fields matching
   the HAP-NodeJS specification
2. Each video/audio frame was sent as a separate mediaFragment, but Home Hub
   expects GOP-based fragments (~2-4 seconds of accumulated data)
3. Large fragments were not chunked (max 256 KiB per chunk)

Changes:
- Fix HDS dataSend.data message structure to use proper packets array with
  nested data/metadata (dataType, dataSequenceNumber, dataChunkSequenceNumber,
  isLastDataChunk, dataTotalSize)
- Add 256 KiB chunking for large media fragments
- Buffer moof+mdat pairs in hksvConsumer and flush on keyframe boundaries
  (GOP-based fragmentation)
- Pre-start consumer at pair-verify for instant init segment delivery
- Add write-response support to HAP PUT handler for ch131 DataStream setup
- Fix HAP service linking to match HAP-NodeJS reference
- Add default SelectedCameraRecordingConfiguration (ch209) value
- Start continuous motion generator at pair-verify with dedup protection
This commit is contained in:
Sergey Krashevich
2026-03-05 06:25:00 +03:00
parent 35fd1383c8
commit 1856b7ace4
6 changed files with 322 additions and 67 deletions
+48 -16
View File
@@ -163,27 +163,59 @@ func (s *Session) WriteRequest(protocol, topic string, body map[string]any) (int
return id, s.WriteMessage(header, body)
}
// maxChunkSize is the maximum data chunk size for HDS media transfer (256 KiB)
const maxChunkSize = 0x40000
// SendMediaInit sends the fMP4 initialization segment (ftyp+moov)
func (s *Session) SendMediaInit(streamID int, initData []byte) error {
return s.WriteEvent(ProtoDataSend, TopicData, map[string]any{
"streamId": streamID,
"packets": 1,
"type": "mediaInitialization",
"data": initData,
})
return s.sendMediaData(streamID, "mediaInitialization", initData, 1)
}
// SendMediaFragment sends an fMP4 fragment (moof+mdat)
// SendMediaFragment sends an fMP4 fragment (moof+mdat), splitting into chunks if needed
func (s *Session) SendMediaFragment(streamID int, fragment []byte, sequence int) error {
return s.WriteEvent(ProtoDataSend, TopicData, map[string]any{
"streamId": streamID,
"packets": 1,
"type": "mediaFragment",
"data": fragment,
"dataSequenceNumber": sequence,
"isLastDataChunk": true,
"dataChunkSequenceNumber": 0,
})
return s.sendMediaData(streamID, "mediaFragment", fragment, sequence)
}
// sendMediaData sends media data with proper HAP-NodeJS compatible packet structure.
// Large data is split into chunks of maxChunkSize bytes.
func (s *Session) sendMediaData(streamID int, dataType string, data []byte, sequence int) error {
totalSize := len(data)
chunkSeq := 1
for offset := 0; offset < totalSize; offset += maxChunkSize {
end := offset + maxChunkSize
if end > totalSize {
end = totalSize
}
chunk := data[offset:end]
isLast := end >= totalSize
metadata := map[string]any{
"dataType": dataType,
"dataSequenceNumber": sequence,
"dataChunkSequenceNumber": chunkSeq,
"isLastDataChunk": isLast,
}
if chunkSeq == 1 {
metadata["dataTotalSize"] = totalSize
}
body := map[string]any{
"streamId": streamID,
"packets": []any{
map[string]any{
"data": chunk,
"metadata": metadata,
},
},
}
if err := s.WriteEvent(ProtoDataSend, TopicData, body); err != nil {
return err
}
chunkSeq++
}
return nil
}
// Run processes incoming HDS messages in a loop