package audio import ( "context" "fmt" "io" ) // Sink-Namen (Teil des öffentlichen Vertrags; Spec: audio.sink = "oto"|"null"). const ( NameOto = "oto" NameNull = "null" ) // Sink gibt Audio-Bytes wieder. Die Bytes stammen aus Provider.Synthesize; // oto versteht RIFF/WAVE (PCM) — das Lieferformat von osbuiltin. type Sink interface { // Play spielt die Daten blockierend ab (bis zum Ende oder bis ctx // abgebrochen wird). Play(ctx context.Context, data []byte) error // Name liefert den Sink-Namen (audio.sink-Konfiguration). Name() string } // NewSink liefert den Sink zum konfigurierten Namen ("oto" oder "null"). // log ist ein optionaler Log-Output — die CLI übergibt ihr stderr, über den // der null-Sink seine Verwurf-Zeile schreibt (nil = still verwerfen); der // oto-Sink braucht kein Log. Unbekannte Namen sind ein klarer Fehler. func NewSink(name string, log io.Writer) (Sink, error) { switch name { case NameOto: return OtoSink{}, nil case NameNull: return NullSink{W: log}, nil default: return nil, fmt.Errorf("unbekannter audio sink %q (erlaubt: %q, %q)", name, NameOto, NameNull) } } // NullSink verwirft jede Ansage ohne Ton — für CI, Server und Tests. Wurde // ein W gesetzt (NewSink reicht dafür den Log-Output der CLI durch), loggt // Play eine Zeile darüber, damit „läuft ohne Ton durch" sichtbar bleibt. type NullSink struct { W io.Writer // optionaler Log-Output; nil = still verwerfen } // Name implementiert Sink. func (NullSink) Name() string { return NameNull } // Play implementiert Sink: verwirft die Daten. func (n NullSink) Play(ctx context.Context, data []byte) error { if n.W != nil { fmt.Fprintf(n.W, "audio: %d Bytes verworfen (null sink)\n", len(data)) } return nil }