Live video on the AT Protocol
1package model
2
3import (
4 "database/sql"
5 "encoding/json"
6 "fmt"
7 "math"
8 "time"
9
10 "github.com/google/uuid"
11 "gorm.io/datatypes"
12)
13
14type PlayerEventAPI struct {
15 ID string `json:"id"`
16 Time time.Time `json:"time"`
17 PlayerID string `json:"playerId"`
18 EventType string `json:"eventType"`
19 Meta map[string]any `json:"meta"`
20}
21
22type PlayerEvent struct {
23 ID string `gorm:"primarykey"`
24 Time time.Time
25 PlayerID string `gorm:"index"`
26 EventType string
27 Meta datatypes.JSON
28}
29
30func MaybeNull(s string) sql.NullString {
31 if s == "" {
32 return sql.NullString{Valid: false}
33 }
34 return sql.NullString{Valid: true, String: s}
35}
36
37func (m *DBModel) CreatePlayerEvent(event PlayerEventAPI) error {
38 uu, err := uuid.NewV7()
39 if err != nil {
40 return err
41 }
42 metaBs, err := json.Marshal(event.Meta)
43 if err != nil {
44 return err
45 }
46 err = m.DB.Model(PlayerEvent{}).Create(PlayerEvent{
47 ID: uu.String(),
48 Time: event.Time,
49 PlayerID: event.PlayerID,
50 EventType: event.EventType,
51 Meta: datatypes.JSON(metaBs),
52 }).Error
53 if err != nil {
54 return err
55 }
56 return nil
57}
58
59func (m *DBModel) ListPlayerEvents(playerID string) ([]PlayerEvent, error) {
60 events := []PlayerEvent{}
61 // err := m.DB.Find(&events).Error
62 err := m.DB.Where("player_id = ?", playerID).Find(&events).Error
63 if err != nil {
64 return nil, fmt.Errorf("error retrieving player events: %w", err)
65 }
66 return events, nil
67}
68
69func (m *DBModel) PlayerReport(playerID string) (map[string]any, error) {
70 events, err := m.ListPlayerEvents(playerID)
71 if err != nil {
72 return nil, err
73 }
74 whatHappenedReport := map[string]float64{}
75 retries := 0
76 for _, e := range events {
77 if e.EventType == "retry" {
78 retries++
79 continue
80 }
81 if e.EventType != "aq-played" {
82 continue
83 }
84 bs, err := e.Meta.MarshalJSON()
85 if err != nil {
86 return nil, err
87 }
88 meta := map[string]any{}
89 err = json.Unmarshal(bs, &meta)
90 if err != nil {
91 return nil, err
92 }
93 whatHappenedAny, ok := meta["whatHappened"]
94 if !ok {
95 continue
96 }
97 whatHappened, ok := whatHappenedAny.(map[string]any)
98 if !ok {
99 continue
100 }
101 for state, time := range whatHappened {
102 ms, ok := time.(float64)
103 if ok {
104 whatHappenedReport[state] = whatHappenedReport[state] + ms
105 }
106 }
107 }
108
109 avSyncs := []float64{}
110 for _, e := range events {
111 if e.EventType != "av-sync" {
112 continue
113 }
114 bs, err := e.Meta.MarshalJSON()
115 if err != nil {
116 return nil, err
117 }
118 meta := map[string]any{}
119 err = json.Unmarshal(bs, &meta)
120 if err != nil {
121 return nil, err
122 }
123 diff, ok := meta["diff"].(float64)
124 if !ok {
125 continue
126 }
127 avSyncs = append(avSyncs, diff)
128 }
129
130 report := map[string]any{
131 "whatHappened": whatHappenedReport,
132 }
133
134 if len(avSyncs) > 0 {
135 min := math.Inf(1)
136 max := math.Inf(-1)
137 sum := 0.0
138 for _, sync := range avSyncs {
139 if sync < min {
140 min = sync
141 }
142 if sync > max {
143 max = sync
144 }
145 sum += sync
146 }
147 avg := sum / float64(len(avSyncs))
148 report["avSync"] = map[string]float64{
149 "min": min,
150 "max": max,
151 "avg": avg,
152 }
153 }
154
155 report["retries"] = retries
156
157 return report, nil
158}
159
160func (m *DBModel) ClearPlayerEvents() error {
161 return m.DB.Where("1 = 1").Delete(&PlayerEvent{}).Error
162}