json.gno
4.53 Kb · 127 lines
1package radio
2
3import (
4 "strconv"
5 "strings"
6
7 "gno.land/p/nym-alexiscolin000/gnoradio/text/v0"
8 "gno.land/r/nym-alexiscolin000/gnoradio/catalog/v1"
9)
10
11// Read API for the GnoRadio app (vm/qeval).
12
13// ScheduleJSON returns what a station plays from now until now+horizon seconds
14// (60..7200, at most 60 entries): programmed slots and the rotation between
15// them (on Main, the hour's genre station, simulcast and cut at each hour). A client plays entry i from offset+max(0, clock-start) and resyncs on
16// "now". "relay" marks, on Main, an entry that is not Main's own slot but
17// the hour's genre station simulcast (its picks included). "sponsored" is the refund its artist pays the picker (0: a normal
18// pick), "at" the time a booked pick asked for (0: none). "booked" lists every
19// booked pick still to air, whatever the horizon, and those that aired in the
20// last hour (they count toward their clock hour's cap until they fold):
21// {"track","start","end","at","by","note"}. Fetch it again every few minutes, not
22// every second.
23func ScheduleJSON(stationID, horizon int) string {
24 st := rd().station(stationID)
25 if horizon < 60 || horizon > 7200 {
26 horizon = 3600
27 }
28 ts := now()
29 end := ts + int64(horizon)
30 var b strings.Builder
31 b.WriteString(`{"station":` + text.Itoa(st.id) + `,"now":` + text.Itoa64(ts) + `,"entries":[`)
32 n := 0
33 emit := func(track int, title string, start, stop, offset int64, queued bool, by, note string, pay, at int64, relay bool) {
34 if n > 0 {
35 b.WriteString(",")
36 }
37 b.WriteString(`{"track":` + text.Itoa(track) + `,"title":` + text.Str(title) +
38 `,"start":` + text.Itoa64(start) + `,"end":` + text.Itoa64(stop) + `,"offset":` + text.Itoa64(offset) +
39 `,"queued":` + strconv.FormatBool(queued) + `,"by":` + text.Str(by) + `,"note":` + text.Str(note) + `,"sponsored":` + text.Itoa64(pay) + `,"at":` + text.Itoa64(at) + `,"relay":` + strconv.FormatBool(relay) + `}`)
40 n++
41 }
42 cur := ts
43 for cur < end && n < 60 {
44 var slot *Slot
45 next := int64(-1)
46 sl := st.slots()
47 for i := range sl {
48 s := &sl[i]
49 if cur >= s.Start && cur < s.Start+s.Dur {
50 slot = s
51 break
52 }
53 if s.Start > cur && (next < 0 || s.Start < next) {
54 next = s.Start
55 }
56 }
57 if slot != nil {
58 stop := slot.Start + slot.Dur
59 if title := catalog.PlayableTitle(slot.Track); title != "" {
60 emit(slot.Track, title, slot.Start, stop, 0, true, slot.By.String(), slot.shownNote(), slot.Pay, slot.At, false)
61 }
62 cur = stop
63 continue
64 }
65 track, off, dur, ok := st.between(cur)
66 relay := st.id == MainStation // not Main's own slot: the hour's genre station, simulcast
67 if !ok {
68 if next < 0 {
69 break
70 }
71 cur = next
72 continue
73 }
74 stop := cur + dur - off
75 if next >= 0 && next < stop {
76 stop = next // the rotation (or Main's relay) pauses for the programmed slot
77 }
78 if title := catalog.PlayableTitle(track); title != "" {
79 if s, ok := st.pickAt(cur); ok && s.Track == track { // Main simulcasting a genre station's pick
80 emit(track, title, cur-off, stop, off, true, s.By.String(), s.shownNote(), s.Pay, s.At, relay)
81 } else {
82 emit(track, title, cur-off, stop, off, false, "", "", 0, 0, relay)
83 }
84 }
85 cur = stop
86 }
87 b.WriteString(`],"booked":[`)
88 n = 0
89 for _, s := range st.slots() {
90 if s.At > 0 && s.Start+s.Dur > ts-foldAfter {
91 if n > 0 {
92 b.WriteString(",")
93 }
94 b.WriteString(`{"track":` + text.Itoa(s.Track) + `,"start":` + text.Itoa64(s.Start) + `,"end":` + text.Itoa64(s.Start+s.Dur) + `,"at":` + text.Itoa64(s.At) + `,"by":` + text.Str(s.By.String()) + `,"note":` + text.Str(s.shownNote()) + `}`)
95 n++
96 }
97 }
98 b.WriteString(`]}`)
99 return b.String()
100}
101
102// StationsJSON lists every station with what it plays now.
103func StationsJSON() string {
104 t := rd()
105 var b strings.Builder
106 // newFloor: only tracks above this id may be picked on New this week.
107 synced := t.synced()
108 floor := synced - newCap
109 if floor < 0 {
110 floor = 0
111 }
112 ts := now()
113 b.WriteString(`{"pending":` + text.Itoa(catalog.TrackCount()-synced) + `,"newFloor":` + text.Itoa(floor) + `,"stations":[`)
114 for id := 0; id < numStations; id++ {
115 st := t.station(id)
116 if id > 0 {
117 b.WriteString(",")
118 }
119 track, offset, isQueued := st.onAir(ts)
120 live, loop := st.rotating()
121 b.WriteString(`{"id":` + text.Itoa(id) + `,"name":` + text.Str(StationName(id)) + `,"genre":` + text.Itoa(st.genre) +
122 `,"tracks":` + text.Itoa(live) + `,"loop":` + text.Itoa64(loop) + `,"queued":` + text.Itoa(st.queued(ts)) +
123 `,"now":{"track":` + text.Itoa(track) + `,"offset":` + text.Itoa64(offset) + `,"queued":` + strconv.FormatBool(isQueued) + `}}`)
124 }
125 b.WriteString(`]}`)
126 return b.String()
127}