@@ -15,6 +15,7 @@ import (
1515 "net/http"
1616 "net/url"
1717 "os"
18+ "path/filepath"
1819 "runtime"
1920 "sort"
2021 "strings"
@@ -266,6 +267,8 @@ func (s *Server) setupRoutes() *http.ServeMux {
266267 mux .HandleFunc ("/admin/player/reorder" , s .handlePlayerReorder )
267268 mux .HandleFunc ("/admin/player/queue" , s .handlePlayerQueue )
268269 mux .HandleFunc ("/admin/player/shuffle" , s .handlePlayerShuffle )
270+ mux .HandleFunc ("/admin/player/files" , s .handlePlayerFiles )
271+ mux .HandleFunc ("/admin/player/playlist-action" , s .handlePlayerPlaylistAction )
269272 mux .HandleFunc ("/admin/autodj/add" , s .handleAddAutoDJ )
270273 mux .HandleFunc ("/admin/autodj/delete" , s .handleDeleteAutoDJ )
271274 mux .HandleFunc ("/admin/autodj/toggle" , s .handleToggleAutoDJ )
@@ -392,20 +395,22 @@ func (s *Server) Start() error {
392395 // Start configured AutoDJs
393396 for _ , adj := range s .Config .AutoDJs {
394397 if adj .Enabled {
395- streamer , err := s .StreamerM .StartStreamer (adj .Name , adj .Mount , adj .MusicDir , adj .Loop , adj .Format , adj .Bitrate , adj .InjectMetadata )
398+ streamer , err := s .StreamerM .StartStreamer (adj .Name , adj .Mount , adj .MusicDir , adj .Loop , adj .Format , adj .Bitrate , adj .InjectMetadata , adj . Playlist )
396399 if err != nil {
397400 logrus .WithError (err ).Errorf ("Failed to start AutoDJ %s" , adj .Name )
398401 } else {
399402 logrus .Infof ("AutoDJ %s started on %s" , adj .Name , adj .Mount )
400- streamer .ScanMusicDir () // Initial scan
403+ if len (adj .Playlist ) == 0 {
404+ streamer .ScanMusicDir () // Initial scan
405+ }
401406 }
402407 }
403408 }
404409
405410 // Legacy/Default AutoDJ (backward compatibility)
406411 if s .Config .MusicDir != "" && s .StreamerM .GetStreamer ("/autodj" ) == nil {
407412 // Start a default streamer for /autodj
408- streamer , err := s .StreamerM .StartStreamer ("AutoDJ" , "/autodj" , s .Config .MusicDir , true , "mp3" , 128 , true )
413+ streamer , err := s .StreamerM .StartStreamer ("AutoDJ" , "/autodj" , s .Config .MusicDir , true , "mp3" , 128 , true , nil )
409414 if err == nil {
410415 if s .Config .MPDEnabled {
411416 port := s .Config .MPDPort
@@ -2146,6 +2151,16 @@ func (s *Server) handlePlayerScan(w http.ResponseWriter, r *http.Request) {
21462151 logrus .WithError (err ).Error ("Failed to scan music directory" )
21472152 }
21482153
2154+ // Persist
2155+ playlistCopy := streamer .GetPlaylist ()
2156+ for _ , adj := range s .Config .AutoDJs {
2157+ if adj .Mount == mount {
2158+ adj .Playlist = playlistCopy
2159+ s .Config .SaveConfig ()
2160+ break
2161+ }
2162+ }
2163+
21492164 http .Redirect (w , r , "/admin#tab-streamer" , http .StatusSeeOther )
21502165}
21512166
@@ -2198,7 +2213,7 @@ func (s *Server) handleAddAutoDJ(w http.ResponseWriter, r *http.Request) {
21982213 s .Config .SaveConfig ()
21992214
22002215 // Start it immediately
2201- streamer , err := s .StreamerM .StartStreamer (adj .Name , adj .Mount , adj .MusicDir , adj .Loop , adj .Format , adj .Bitrate , adj .InjectMetadata )
2216+ streamer , err := s .StreamerM .StartStreamer (adj .Name , adj .Mount , adj .MusicDir , adj .Loop , adj .Format , adj .Bitrate , adj .InjectMetadata , nil )
22022217 if err == nil {
22032218 streamer .ScanMusicDir ()
22042219 streamer .Play ()
@@ -2246,9 +2261,11 @@ func (s *Server) handleToggleAutoDJ(w http.ResponseWriter, r *http.Request) {
22462261 if adj .Mount == mount {
22472262 adj .Enabled = ! adj .Enabled
22482263 if adj .Enabled {
2249- streamer , err := s .StreamerM .StartStreamer (adj .Name , adj .Mount , adj .MusicDir , adj .Loop , adj .Format , adj .Bitrate , adj .InjectMetadata )
2264+ streamer , err := s .StreamerM .StartStreamer (adj .Name , adj .Mount , adj .MusicDir , adj .Loop , adj .Format , adj .Bitrate , adj .InjectMetadata , adj . Playlist )
22502265 if err == nil {
2251- streamer .ScanMusicDir ()
2266+ if len (adj .Playlist ) == 0 {
2267+ streamer .ScanMusicDir ()
2268+ }
22522269 streamer .Play ()
22532270 }
22542271 } else {
@@ -2267,7 +2284,7 @@ func (s *Server) handleToggleAutoDJ(w http.ResponseWriter, r *http.Request) {
22672284 } else {
22682285 // If it was the legacy one, we can try to restart it
22692286 if mount == "/autodj" && s .Config .MusicDir != "" {
2270- streamer , err := s .StreamerM .StartStreamer ("AutoDJ" , "/autodj" , s .Config .MusicDir , true , "mp3" , 128 , true )
2287+ streamer , err := s .StreamerM .StartStreamer ("AutoDJ" , "/autodj" , s .Config .MusicDir , true , "mp3" , 128 , true , nil )
22712288 if err == nil {
22722289 streamer .ScanMusicDir ()
22732290 streamer .Play ()
@@ -2304,6 +2321,17 @@ func (s *Server) handlePlayerReorder(w http.ResponseWriter, r *http.Request) {
23042321 fmt .Sscanf (toStr , "%d" , & to )
23052322
23062323 streamer .MovePlaylistItem (from , to )
2324+
2325+ // Persist
2326+ playlistCopy := streamer .GetPlaylist ()
2327+ for _ , adj := range s .Config .AutoDJs {
2328+ if adj .Mount == mount {
2329+ adj .Playlist = playlistCopy
2330+ s .Config .SaveConfig ()
2331+ break
2332+ }
2333+ }
2334+
23072335 w .WriteHeader (http .StatusOK )
23082336}
23092337
@@ -2357,6 +2385,79 @@ func (s *Server) handlePlayerShuffle(w http.ResponseWriter, r *http.Request) {
23572385 http .Redirect (w , r , "/admin#tab-streamer" , http .StatusSeeOther )
23582386}
23592387
2388+ func (s * Server ) handlePlayerFiles (w http.ResponseWriter , r * http.Request ) {
2389+ if _ , ok := s .checkAuth (r ); ! ok {
2390+ http .Error (w , "Unauthorized" , http .StatusUnauthorized )
2391+ return
2392+ }
2393+
2394+ mount := r .URL .Query ().Get ("mount" )
2395+ streamer := s .StreamerM .GetStreamer (mount )
2396+ if streamer == nil {
2397+ http .Error (w , "Streamer not found" , http .StatusNotFound )
2398+ return
2399+ }
2400+
2401+ musicDir := streamer .GetMusicDir ()
2402+
2403+ files , err := os .ReadDir (musicDir )
2404+ if err != nil {
2405+ http .Error (w , err .Error (), http .StatusInternalServerError )
2406+ return
2407+ }
2408+
2409+ var res []string
2410+ for _ , f := range files {
2411+ if ! f .IsDir () && strings .ToLower (filepath .Ext (f .Name ())) == ".mp3" {
2412+ res = append (res , f .Name ())
2413+ }
2414+ }
2415+
2416+ w .Header ().Set ("Content-Type" , "application/json" )
2417+ json .NewEncoder (w ).Encode (res )
2418+ }
2419+
2420+ func (s * Server ) handlePlayerPlaylistAction (w http.ResponseWriter , r * http.Request ) {
2421+ if ! s .isCSRFSafe (r ) {
2422+ http .Error (w , "Forbidden" , http .StatusForbidden )
2423+ return
2424+ }
2425+ if _ , ok := s .checkAuth (r ); ! ok {
2426+ return
2427+ }
2428+
2429+ mount := r .FormValue ("mount" )
2430+ action := r .FormValue ("action" )
2431+ fileName := r .FormValue ("file" )
2432+
2433+ streamer := s .StreamerM .GetStreamer (mount )
2434+ if streamer == nil {
2435+ http .Error (w , "Streamer not found" , http .StatusNotFound )
2436+ return
2437+ }
2438+
2439+ if action == "add" {
2440+ fullPath := filepath .Join (streamer .GetMusicDir (), fileName )
2441+ streamer .AddToPlaylist (fullPath )
2442+ } else if action == "remove" {
2443+ var idx int
2444+ fmt .Sscanf (r .FormValue ("index" ), "%d" , & idx )
2445+ streamer .RemoveFromPlaylist (idx )
2446+ }
2447+ playlistCopy := streamer .GetPlaylist ()
2448+
2449+ // Persist
2450+ for _ , adj := range s .Config .AutoDJs {
2451+ if adj .Mount == mount {
2452+ adj .Playlist = playlistCopy
2453+ s .Config .SaveConfig ()
2454+ break
2455+ }
2456+ }
2457+
2458+ http .Redirect (w , r , "/admin#tab-streamer" , http .StatusSeeOther )
2459+ }
2460+
23602461func (s * Server ) handleGetStats (w http.ResponseWriter , r * http.Request ) {
23612462 if _ , ok := s .checkAuth (r ); ! ok {
23622463 http .Error (w , "Unauthorized" , http .StatusUnauthorized )
0 commit comments