Skip to content

Commit b44b003

Browse files
author
Datanoise
committed
feat: real-time queue updates via SSE and cross-playlist loading support
1 parent 1a2b996 commit b44b003

6 files changed

Lines changed: 133 additions & 185 deletions

File tree

playlists/abce.pls

Lines changed: 1 addition & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -1,145 +1,3 @@
11
[playlist]
2-
NumberOfEntries=71
3-
File1=/Users/syso/dev/tinyice/music/(Verse 1)(1).mp3
4-
Title1=(Verse 1)(1).mp3
5-
File2=/Users/syso/dev/tinyice/music/DEVASTATION - I Feel Love (Acapella) (Donna Summer Cover)-PN6sPWmZ1o4.mp3
6-
Title2=DEVASTATION - I Feel Love (Acapella) (Donna Summer Cover)-PN6sPWmZ1o4.mp3
7-
File3=/Users/syso/dev/tinyice/music/Der Zauberhut 1v2. Hörbuch von Terry Pratchett [_glTWbUIVEo].mp3
8-
Title3=Der Zauberhut 1v2. Hörbuch von Terry Pratchett [_glTWbUIVEo].mp3
9-
File4=/Users/syso/dev/tinyice/music/Lexyyysy42l.mp3
10-
Title4=Lexyyysy42l.mp3
11-
File5=/Users/syso/dev/tinyice/music/Lexyyysyl.mp3
12-
Title5=Lexyyysyl.mp3
13-
File6=/Users/syso/dev/tinyice/music/Lexyyysyl3.mp3
14-
Title6=Lexyyysyl3.mp3
15-
File7=/Users/syso/dev/tinyice/music/Linda Di Franco - TV Scene [261953612].mp3
16-
Title7=Linda Di Franco - TV Scene [261953612].mp3
17-
File8=/Users/syso/dev/tinyice/music/Live from Nuits sonores: Helena Hauff b2b DJ MELL G [2200736951].mp3
18-
Title8=Live from Nuits sonores: Helena Hauff b2b DJ MELL G [2200736951].mp3
19-
File9=/Users/syso/dev/tinyice/music/Madness [DvVP_nOgxQo].mp3
20-
Title9=Madness [DvVP_nOgxQo].mp3
21-
File10=/Users/syso/dev/tinyice/music/Oldskool Jungle Drum n Bass Mix 92-97 [VHXvfSmyjj4].mp3
22-
Title10=Oldskool Jungle Drum n Bass Mix 92-97 [VHXvfSmyjj4].mp3
23-
File11=/Users/syso/dev/tinyice/music/Omega Nebula - Dubplate - Little Lion Sound - Hero [CycPQZDEHtM].mp3
24-
Title11=Omega Nebula - Dubplate - Little Lion Sound - Hero [CycPQZDEHtM].mp3
25-
File12=/Users/syso/dev/tinyice/music/That sound your old pc speaker does when you receive a phone call [FYjs7vsaSEw].mp3
26-
Title12=That sound your old pc speaker does when you receive a phone call [FYjs7vsaSEw].mp3
27-
File13=/Users/syso/dev/tinyice/music/The Door in the Wall by H.G. Wells | Short Story Audiobook [Huam5sSRjwc].mp3
28-
Title13=The Door in the Wall by H.G. Wells | Short Story Audiobook [Huam5sSRjwc].mp3
29-
File14=/Users/syso/dev/tinyice/music/Video by songsthatshouldnotwork [DRXcvzJjrE-].mp3
30-
Title14=Video by songsthatshouldnotwork [DRXcvzJjrE-].mp3
31-
File15=/Users/syso/dev/tinyice/music/You're so precious - Ver 2 Different Take.mp3
32-
Title15=You're so precious - Ver 2 Different Take.mp3
33-
File16=/Users/syso/dev/tinyice/music/_tech2_crazy.mp3
34-
Title16=_tech2_crazy.mp3
35-
File17=/Users/syso/dev/tinyice/music/a_d.mp3
36-
Title17=a_d.mp3
37-
File18=/Users/syso/dev/tinyice/music/a_d_full.mp3
38-
Title18=a_d_full.mp3
39-
File19=/Users/syso/dev/tinyice/music/a_d_full_b.mp3
40-
Title19=a_d_full_b.mp3
41-
File20=/Users/syso/dev/tinyice/music/a_d_full_b_fixed.mp3
42-
Title20=a_d_full_b_fixed.mp3
43-
File21=/Users/syso/dev/tinyice/music/a_dwwe#s.mp3
44-
Title21=a_dwwe#s.mp3
45-
File22=/Users/syso/dev/tinyice/music/a_dwwe#sss4344ws5few3s.mp3
46-
Title22=a_dwwe#sss4344ws5few3s.mp3
47-
File23=/Users/syso/dev/tinyice/music/a_dwwe#sss4344ws_420.mp3
48-
Title23=a_dwwe#sss4344ws_420.mp3
49-
File24=/Users/syso/dev/tinyice/music/abc.mp3
50-
Title24=abc.mp3
51-
File25=/Users/syso/dev/tinyice/music/abedi.mp3
52-
Title25=abedi.mp3
53-
File26=/Users/syso/dev/tinyice/music/acapella.mp3
54-
Title26=acapella.mp3
55-
File27=/Users/syso/dev/tinyice/music/allie_cover_.mp3
56-
Title27=allie_cover_.mp3
57-
File28=/Users/syso/dev/tinyice/music/allie_cvr.mp3
58-
Title28=allie_cvr.mp3
59-
File29=/Users/syso/dev/tinyice/music/ambi1a.mp3
60-
Title29=ambi1a.mp3
61-
File30=/Users/syso/dev/tinyice/music/aos.mp3
62-
Title30=aos.mp3
63-
File31=/Users/syso/dev/tinyice/music/asd.mp3
64-
Title31=asd.mp3
65-
File32=/Users/syso/dev/tinyice/music/bis.mp3
66-
Title32=bis.mp3
67-
File33=/Users/syso/dev/tinyice/music/bjean.mp3
68-
Title33=bjean.mp3
69-
File34=/Users/syso/dev/tinyice/music/bla123.mp3
70-
Title34=bla123.mp3
71-
File35=/Users/syso/dev/tinyice/music/cabs.mp3
72-
Title35=cabs.mp3
73-
File36=/Users/syso/dev/tinyice/music/disco.mp3
74-
Title36=disco.mp3
75-
File37=/Users/syso/dev/tinyice/music/dnbass.mp3
76-
Title37=dnbass.mp3
77-
File38=/Users/syso/dev/tinyice/music/drbrk.mp3
78-
Title38=drbrk.mp3
79-
File39=/Users/syso/dev/tinyice/music/ero.mp3
80-
Title39=ero.mp3
81-
File40=/Users/syso/dev/tinyice/music/fmbeat2.mp3
82-
Title40=fmbeat2.mp3
83-
File41=/Users/syso/dev/tinyice/music/fuckdat.mp3
84-
Title41=fuckdat.mp3
85-
File42=/Users/syso/dev/tinyice/music/funkyshit.mp3
86-
Title42=funkyshit.mp3
87-
File43=/Users/syso/dev/tinyice/music/geilo3.mp3
88-
Title43=geilo3.mp3
89-
File44=/Users/syso/dev/tinyice/music/geilo32.mp3
90-
Title44=geilo32.mp3
91-
File45=/Users/syso/dev/tinyice/music/give.mp3
92-
Title45=give.mp3
93-
File46=/Users/syso/dev/tinyice/music/house2.mp3
94-
Title46=house2.mp3
95-
File47=/Users/syso/dev/tinyice/music/ladida2 2.mp3
96-
Title47=ladida2 2.mp3
97-
File48=/Users/syso/dev/tinyice/music/loop_hqhdho.mp3
98-
Title48=loop_hqhdho.mp3
99-
File49=/Users/syso/dev/tinyice/music/lup.mp3
100-
Title49=lup.mp3
101-
File50=/Users/syso/dev/tinyice/music/modem.mp3
102-
Title50=modem.mp3
103-
File51=/Users/syso/dev/tinyice/music/osasd.mp3
104-
Title51=osasd.mp3
105-
File52=/Users/syso/dev/tinyice/music/precious3.mp3
106-
Title52=precious3.mp3
107-
File53=/Users/syso/dev/tinyice/music/precious4.mp3
108-
Title53=precious4.mp3
109-
File54=/Users/syso/dev/tinyice/music/saliut.mp3
110-
Title54=saliut.mp3
111-
File55=/Users/syso/dev/tinyice/music/salut2.mp3
112-
Title55=salut2.mp3
113-
File56=/Users/syso/dev/tinyice/music/sd2.mp3
114-
Title56=sd2.mp3
115-
File57=/Users/syso/dev/tinyice/music/sess6_bef_edit46.mp3
116-
Title57=sess6_bef_edit46.mp3
117-
File58=/Users/syso/dev/tinyice/music/sess6_bef_edit463.mp3
118-
Title58=sess6_bef_edit463.mp3
119-
File59=/Users/syso/dev/tinyice/music/sess6_bef_edit49.mp3
120-
Title59=sess6_bef_edit49.mp3
121-
File60=/Users/syso/dev/tinyice/music/short.mp3
122-
Title60=short.mp3
123-
File61=/Users/syso/dev/tinyice/music/shtuff123.mp3
124-
Title61=shtuff123.mp3
125-
File62=/Users/syso/dev/tinyice/music/sj.mp3
126-
Title62=sj.mp3
127-
File63=/Users/syso/dev/tinyice/music/snip.mp3
128-
Title63=snip.mp3
129-
File64=/Users/syso/dev/tinyice/music/untitled1.mp3
130-
Title64=untitled1.mp3
131-
File65=/Users/syso/dev/tinyice/music/untitled12.mp3
132-
Title65=untitled12.mp3
133-
File66=/Users/syso/dev/tinyice/music/untitled13.mp3
134-
Title66=untitled13.mp3
135-
File67=/Users/syso/dev/tinyice/music/x82.mp3
136-
Title67=x82.mp3
137-
File68=/Users/syso/dev/tinyice/music/xaea_3.mp3
138-
Title68=xaea_3.mp3
139-
File69=/Users/syso/dev/tinyice/music/xbd.mp3
140-
Title69=xbd.mp3
141-
File70=/Users/syso/dev/tinyice/music/xfh.mp3
142-
Title70=xfh.mp3
143-
File71=/Users/syso/dev/tinyice/music/young.mp3
144-
Title71=young.mp3
2+
NumberOfEntries=0
1453
Version=2

playlists/foo.pls

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[playlist]
2+
NumberOfEntries=0
3+
Version=2

relay/streamer.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -262,12 +262,15 @@ func (s *Streamer) SavePlaylist() error {
262262
return nil
263263
}
264264

265-
func (s *Streamer) LoadPlaylist() error {
266-
path := filepath.Join("playlists", s.Name+".pls")
265+
func (s *Streamer) LoadPlaylist(filename string) error {
266+
if filename == "" {
267+
filename = s.Name + ".pls"
268+
}
269+
path := filepath.Join("playlists", filename)
267270
f, err := os.Open(path)
268271
if err != nil {
269-
if os.IsNotExist(err) {
270-
return s.SavePlaylist() // Create empty
272+
if os.IsNotExist(err) && filename == s.Name+".pls" {
273+
return s.SavePlaylist() // Create default empty
271274
}
272275
return err
273276
}
@@ -277,9 +280,6 @@ func (s *Streamer) LoadPlaylist() error {
277280
defer s.mu.Unlock()
278281
s.Playlist = []string{}
279282

280-
var entries int
281-
fmt.Fscanf(f, "[playlist]\nNumberOfEntries=%d\n", &entries)
282-
283283
// Simple PLS parser
284284
scanner := bufio.NewScanner(f)
285285
for scanner.Scan() {
@@ -430,7 +430,7 @@ func (sm *StreamerManager) StartStreamer(name, mount, musicDir string, loop bool
430430
}
431431

432432
sm.instances[mount] = s
433-
s.LoadPlaylist()
433+
s.LoadPlaylist("")
434434

435435
go sm.runStreamerLoop(ctx, s)
436436
return s, nil

server/server.go

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ func (s *Server) setupRoutes() *http.ServeMux {
303303
mux.HandleFunc("/admin/player/scan", s.handlePlayerScan)
304304
mux.HandleFunc("/admin/player/clear-playlist", s.handlePlayerClearPlaylist)
305305
mux.HandleFunc("/admin/player/save-playlist", s.handlePlayerSavePlaylist)
306+
mux.HandleFunc("/admin/player/load-playlist", s.handlePlayerLoadPlaylist)
306307
mux.HandleFunc("/admin/player/reorder", s.handlePlayerReorder)
307308
mux.HandleFunc("/admin/player/queue", s.handlePlayerQueue)
308309
mux.HandleFunc("/admin/player/shuffle", s.handlePlayerShuffle)
@@ -2020,15 +2021,16 @@ type relayEventInfo struct {
20202021
}
20212022

20222023
type streamerEventInfo struct {
2023-
Name string `json:"name"`
2024-
Mount string `json:"mount"`
2025-
State int `json:"state"`
2026-
CurrentSong string `json:"song"`
2027-
StartTime int64 `json:"start_time"`
2028-
Duration float64 `json:"duration"`
2029-
PlaylistPos int `json:"playlist_pos"`
2030-
PlaylistLen int `json:"playlist_len"`
2031-
Shuffle bool `json:"shuffle"`
2024+
Name string `json:"name"`
2025+
Mount string `json:"mount"`
2026+
State int `json:"state"`
2027+
CurrentSong string `json:"song"`
2028+
StartTime int64 `json:"start_time"`
2029+
Duration float64 `json:"duration"`
2030+
PlaylistPos int `json:"playlist_pos"`
2031+
PlaylistLen int `json:"playlist_len"`
2032+
Shuffle bool `json:"shuffle"`
2033+
Queue []relay.PlaylistItem `json:"queue"`
20322034
}
20332035

20342036
func (s *Server) collectStatsPayload(user *config.User) ([]byte, error) {
@@ -2086,6 +2088,7 @@ func (s *Server) collectStatsPayload(user *config.User) ([]byte, error) {
20862088
PlaylistPos: stats.PlaylistPos,
20872089
PlaylistLen: stats.PlaylistLen,
20882090
Shuffle: stats.Shuffle,
2091+
Queue: st.GetQueueInfo(),
20892092
})
20902093
}
20912094
}
@@ -2509,6 +2512,41 @@ func (s *Server) handleToggleAutoDJ(w http.ResponseWriter, r *http.Request) {
25092512
http.Redirect(w, r, "/admin#tab-streamer", http.StatusSeeOther)
25102513
}
25112514

2515+
func (s *Server) handlePlayerLoadPlaylist(w http.ResponseWriter, r *http.Request) {
2516+
if !s.isCSRFSafe(r) {
2517+
http.Error(w, "Forbidden", http.StatusForbidden)
2518+
return
2519+
}
2520+
if _, ok := s.checkAuth(r); !ok {
2521+
return
2522+
}
2523+
2524+
mount := r.FormValue("mount")
2525+
filename := r.FormValue("file")
2526+
streamer := s.StreamerM.GetStreamer(mount)
2527+
if streamer == nil {
2528+
http.Error(w, "Streamer not found", http.StatusNotFound)
2529+
return
2530+
}
2531+
2532+
if err := streamer.LoadPlaylist(filename); err != nil {
2533+
http.Error(w, err.Error(), http.StatusInternalServerError)
2534+
return
2535+
}
2536+
2537+
// Persist new playlist to config
2538+
playlistCopy := streamer.GetPlaylist()
2539+
for _, adj := range s.Config.AutoDJs {
2540+
if adj.Mount == mount {
2541+
adj.Playlist = playlistCopy
2542+
s.Config.SaveConfig()
2543+
break
2544+
}
2545+
}
2546+
2547+
w.WriteHeader(http.StatusOK)
2548+
}
2549+
25122550
func (s *Server) handlePlayerReorder(w http.ResponseWriter, r *http.Request) {
25132551
if !s.isCSRFSafe(r) {
25142552
http.Error(w, "Forbidden", http.StatusForbidden)
@@ -2690,8 +2728,27 @@ func (s *Server) handlePlayerFiles(w http.ResponseWriter, r *http.Request) {
26902728
Title string `json:"title"`
26912729
IsDir bool `json:"is_dir"`
26922730
Path string `json:"path"`
2731+
IsPLS bool `json:"is_pls"`
26932732
}
26942733
var res []fileEntry
2734+
2735+
// If we are at the root, also list available playlists
2736+
if subDir == "" {
2737+
if plsEntries, err := os.ReadDir("playlists"); err == nil {
2738+
for _, f := range plsEntries {
2739+
if !f.IsDir() && strings.HasSuffix(f.Name(), ".pls") {
2740+
res = append(res, fileEntry{
2741+
Name: f.Name(),
2742+
Title: "Playlist: " + f.Name(),
2743+
IsDir: false,
2744+
Path: f.Name(),
2745+
IsPLS: true,
2746+
})
2747+
}
2748+
}
2749+
}
2750+
}
2751+
26952752
for _, f := range entries {
26962753
ext := strings.ToLower(filepath.Ext(f.Name()))
26972754
if f.IsDir() || ext == ".mp3" {
@@ -2704,6 +2761,7 @@ func (s *Server) handlePlayerFiles(w http.ResponseWriter, r *http.Request) {
27042761
Title: title,
27052762
IsDir: f.IsDir(),
27062763
Path: filepath.Join(subDir, f.Name()),
2764+
IsPLS: false,
27072765
})
27082766
}
27092767
}

0 commit comments

Comments
 (0)