sync: migrate ai-operator to Gitea (2026-08-10)
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
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 ""
|
||||
}
|
||||
Reference in New Issue
Block a user