cddd11a0492b8d7c441110e37391c3bd12f1980c

Author
TheEdgeOfRage <git@theedgeofrage.com>
Committer
TheEdgeOfRage <git@theedgeofrage.com>
Date

Message

Fix iOS WAV uploads

Diff

 1diff --git a/internal/server/handlers.go b/internal/server/handlers.go
 2index b913a6150db64828f985b6b7eb2e03c7ad65c3f7..c92f3f8ba783ee4e1b21b00b083267391e2f2bfe 100644
 3--- a/internal/server/handlers.go
 4+++ b/internal/server/handlers.go
 5@@ -3,6 +3,7 @@ package server
 6 import (
 7 	"context"
 8 	"encoding/base64"
 9+	"encoding/binary"
10 	"encoding/json"
11 	"errors"
12 	"io"
13@@ -177,6 +178,7 @@ func (s *Server) postSpeech(w http.ResponseWriter, r *http.Request) {
14 		writeError(w, http.StatusBadRequest, "not a WAV file")
15 		return
16 	}
17+	normalizeWav(wav)
18 
19 	sess.mu.Lock()
20 	defer sess.mu.Unlock()
21@@ -187,6 +189,28 @@ func (s *Server) postSpeech(w http.ResponseWriter, r *http.Request) {
22 	finishTurn(w, sess, sess.orch.FinishSpeak(ctx))
23 }
24 
25+// normalizeWav repairs header sizes that AVAudioRecorder leaves stale: it
26+// preallocates a padded header, streams PCM after it, and never updates the
27+// RIFF size or the data chunk size (written as 0). Strict readers reject such
28+// files.
29+func normalizeWav(wav []byte) {
30+	off := 12
31+	for off+8 <= len(wav) {
32+		id := string(wav[off : off+4])
33+		size := int(binary.LittleEndian.Uint32(wav[off+4 : off+8]))
34+		if id == "data" {
35+			if size != len(wav)-off-8 {
36+				binary.LittleEndian.PutUint32(wav[off+4:], uint32(len(wav)-off-8))
37+			}
38+			break
39+		}
40+		off += 8 + size + size%2
41+	}
42+	if int(binary.LittleEndian.Uint32(wav[4:8])) != len(wav)-8 {
43+		binary.LittleEndian.PutUint32(wav[4:], uint32(len(wav)-8))
44+	}
45+}
46+
47 // finishTurn writes a successful turn response from the display entry the
48 // orchestrator just recorded. The session lock must be held.
49 func finishTurn(w http.ResponseWriter, sess *session, res game.TurnResult) {