99 lines
2.9 KiB
Go
99 lines
2.9 KiB
Go
package asteriskws
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"ai-operator/internal/media"
|
|
)
|
|
|
|
type ControlEventType string
|
|
|
|
const (
|
|
ControlMediaStart ControlEventType = "MEDIA_START"
|
|
ControlDTMFEnd ControlEventType = "DTMF_END"
|
|
ControlMediaXOFF ControlEventType = "MEDIA_XOFF"
|
|
ControlMediaXON ControlEventType = "MEDIA_XON"
|
|
ControlStatus ControlEventType = "STATUS"
|
|
ControlMediaBufferingCompleted ControlEventType = "MEDIA_BUFFERING_COMPLETED"
|
|
ControlMediaMarkProcessed ControlEventType = "MEDIA_MARK_PROCESSED"
|
|
ControlQueueDrained ControlEventType = "QUEUE_DRAINED"
|
|
ControlUnknown ControlEventType = "UNKNOWN"
|
|
)
|
|
|
|
type ControlEvent struct {
|
|
Type ControlEventType
|
|
Raw string
|
|
Fields map[string]string
|
|
}
|
|
|
|
func ParseControlEvent(text string) ControlEvent {
|
|
raw := strings.TrimSpace(text)
|
|
if raw == "" {
|
|
return ControlEvent{Type: ControlUnknown, Raw: text, Fields: map[string]string{}}
|
|
}
|
|
if strings.HasPrefix(raw, "{") {
|
|
return parseJSONControl(raw)
|
|
}
|
|
parts := strings.Fields(raw)
|
|
fields := map[string]string{}
|
|
for _, part := range parts[1:] {
|
|
if k, v, ok := strings.Cut(part, ":"); ok {
|
|
fields[k] = v
|
|
} else if fields["correlation_id"] == "" {
|
|
fields["correlation_id"] = part
|
|
}
|
|
}
|
|
return ControlEvent{Type: knownType(parts[0]), Raw: raw, Fields: fields}
|
|
}
|
|
|
|
func parseJSONControl(raw string) ControlEvent {
|
|
var data map[string]any
|
|
if err := json.Unmarshal([]byte(raw), &data); err != nil {
|
|
return ControlEvent{Type: ControlUnknown, Raw: raw, Fields: map[string]string{"error": "invalid_json"}}
|
|
}
|
|
typeName, _ := data["event"].(string)
|
|
if typeName == "" {
|
|
typeName, _ = data["type"].(string)
|
|
}
|
|
fields := map[string]string{}
|
|
for k, v := range data {
|
|
switch t := v.(type) {
|
|
case string:
|
|
fields[k] = t
|
|
case float64:
|
|
fields[k] = strconv.Itoa(int(t))
|
|
case bool:
|
|
fields[k] = strconv.FormatBool(t)
|
|
}
|
|
}
|
|
return ControlEvent{Type: knownType(typeName), Raw: raw, Fields: fields}
|
|
}
|
|
|
|
func knownType(v string) ControlEventType {
|
|
switch ControlEventType(v) {
|
|
case ControlMediaStart, ControlDTMFEnd, ControlMediaXOFF, ControlMediaXON, ControlStatus, ControlMediaBufferingCompleted, ControlMediaMarkProcessed, ControlQueueDrained:
|
|
return ControlEventType(v)
|
|
}
|
|
return ControlUnknown
|
|
}
|
|
|
|
func (e ControlEvent) MediaStart() *media.MediaStartInfo {
|
|
if e.Type != ControlMediaStart {
|
|
return nil
|
|
}
|
|
opt, _ := strconv.Atoi(value(e.Fields, "optimal_frame_size", "optimalFrameSize"))
|
|
ptime, _ := strconv.Atoi(value(e.Fields, "ptime", "ptime_ms"))
|
|
return &media.MediaStartInfo{ConnectionID: e.Fields["connection_id"], Channel: e.Fields["channel"], ChannelID: e.Fields["channel_id"], Format: e.Fields["format"], OptimalFrameSize: opt, PTimeMS: ptime}
|
|
}
|
|
|
|
func value(m map[string]string, keys ...string) string {
|
|
for _, k := range keys {
|
|
if m[k] != "" {
|
|
return m[k]
|
|
}
|
|
}
|
|
return ""
|
|
}
|