forked from wildeyedskies/stmp
-
Notifications
You must be signed in to change notification settings - Fork 6
/
event_loop.go
198 lines (165 loc) · 5.56 KB
/
event_loop.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
// Copyright 2023 The STMPS Authors
// SPDX-License-Identifier: GPL-3.0-only
package main
import (
"time"
"github.com/spezifisch/stmps/mpvplayer"
)
type eventLoop struct {
// scrobbles are handled by background loop
scrobbleNowPlaying chan string
scrobbleSubmissionTimer *time.Timer
}
func (ui *Ui) initEventLoops() {
el := &eventLoop{
scrobbleNowPlaying: make(chan string, 5),
}
ui.eventLoop = el
// create reused timer to scrobble after delay
el.scrobbleSubmissionTimer = time.NewTimer(0)
if !el.scrobbleSubmissionTimer.Stop() {
<-el.scrobbleSubmissionTimer.C
}
}
func (ui *Ui) runEventLoops() {
go ui.guiEventLoop()
go ui.backgroundEventLoop()
}
// handle ui updates
func (ui *Ui) guiEventLoop() {
ui.addStarredToList()
events := 0.0
fpsTimer := time.NewTimer(0)
for {
events++
select {
case <-fpsTimer.C:
fpsTimer.Reset(10 * time.Second)
// ui.logger.Printf("guiEventLoop: %f events per second", events/10.0)
events = 0
case msg := <-ui.logger.Prints:
// handle log page output
ui.logPage.Print(msg)
case mpvEvent := <-ui.mpvEvents:
events++
// handle events from mpv wrapper
switch mpvEvent.Type {
case mpvplayer.EventStatus:
if mpvEvent.Data == nil {
continue
}
statusData := mpvEvent.Data.(mpvplayer.StatusData) // TODO is this safe to access? maybe we need a copy
ui.app.QueueUpdateDraw(func() {
ui.playerStatus.SetText(formatPlayerStatus(statusData.Volume, statusData.Position, statusData.Duration))
})
case mpvplayer.EventStopped:
ui.logger.Print("mpvEvent: stopped")
ui.app.QueueUpdateDraw(func() {
ui.startStopStatus.SetText("[red::b]Stopped[::-]")
ui.queuePage.UpdateQueue()
})
case mpvplayer.EventPlaying:
ui.logger.Print("mpvEvent: playing")
statusText := "[green::b]Playing[::-]"
var currentSong mpvplayer.QueueItem
if mpvEvent.Data != nil {
currentSong = mpvEvent.Data.(mpvplayer.QueueItem) // TODO is this safe to access? maybe we need a copy
statusText += formatSongForStatusBar(¤tSong)
// Update MprisPlayer with new track info
if ui.mprisPlayer != nil {
ui.mprisPlayer.OnSongChange(currentSong)
}
if ui.connection.Scrobble {
// scrobble "now playing" event (delegate to background event loop)
ui.eventLoop.scrobbleNowPlaying <- currentSong.Id
// scrobble "submission" after song has been playing a bit
// see: https://www.last.fm/api/scrobbling
// A track should only be scrobbled when the following conditions have been met:
// The track must be longer than 30 seconds. And the track has been played for
// at least half its duration, or for 4 minutes (whichever occurs earlier.)
if currentSong.Duration > 30 {
scrobbleDelay := currentSong.Duration / 2
if scrobbleDelay > 240 {
scrobbleDelay = 240
}
scrobbleDuration := time.Duration(scrobbleDelay) * time.Second
ui.eventLoop.scrobbleSubmissionTimer.Reset(scrobbleDuration)
ui.logger.Printf("scrobbler: timer started, %v", scrobbleDuration)
} else {
ui.logger.Printf("scrobbler: track too short")
}
}
}
ui.app.QueueUpdateDraw(func() {
ui.startStopStatus.SetText(statusText)
ui.queuePage.UpdateQueue()
})
case mpvplayer.EventPaused:
ui.logger.Print("mpvEvent: paused")
statusText := "[yellow::b]Paused[::-]"
var currentSong mpvplayer.QueueItem
if mpvEvent.Data != nil {
currentSong = mpvEvent.Data.(mpvplayer.QueueItem) // TODO is this safe to access? maybe we need a copy
statusText += formatSongForStatusBar(¤tSong)
}
ui.app.QueueUpdateDraw(func() {
ui.startStopStatus.SetText(statusText)
})
case mpvplayer.EventUnpaused:
ui.logger.Print("mpvEvent: unpaused")
statusText := "[green::b]Playing[::-]"
var currentSong mpvplayer.QueueItem
if mpvEvent.Data != nil {
currentSong = mpvEvent.Data.(mpvplayer.QueueItem) // TODO is this safe to access? maybe we need a copy
statusText += formatSongForStatusBar(¤tSong)
}
ui.app.QueueUpdateDraw(func() {
ui.startStopStatus.SetText(statusText)
})
default:
ui.logger.Printf("guiEventLoop: unhandled mpvEvent %v", mpvEvent)
}
}
}
}
// loop for blocking background tasks that would otherwise block the ui
func (ui *Ui) backgroundEventLoop() {
for {
select {
case songId := <-ui.eventLoop.scrobbleNowPlaying:
// scrobble now playing
if _, err := ui.connection.ScrobbleSubmission(songId, false); err != nil {
ui.logger.PrintError("scrobble nowplaying", err)
}
case <-ui.eventLoop.scrobbleSubmissionTimer.C:
// scrobble submission delay elapsed
if currentSong, err := ui.player.GetPlayingTrack(); err != nil {
// user paused/stopped
ui.logger.Printf("not scrobbling: %v", err)
} else {
// it's still playing
ui.logger.Printf("scrobbling: %s", currentSong.Id)
if _, err := ui.connection.ScrobbleSubmission(currentSong.Id, true); err != nil {
ui.logger.PrintError("scrobble submission", err)
}
}
}
}
}
func (ui *Ui) addStarredToList() {
response, err := ui.connection.GetStarred()
if err != nil {
ui.logger.PrintError("addStarredToList", err)
}
for _, e := range response.Starred.Song {
// We're storing empty struct as values as we only want the indexes
// It's faster having direct index access instead of looping through array values
ui.starIdList[e.Id] = struct{}{}
}
for _, e := range response.Starred.Album {
ui.starIdList[e.Id] = struct{}{}
}
for _, e := range response.Starred.Artist {
ui.starIdList[e.Id] = struct{}{}
}
}