Fix playlist resume and SIMPLE-ARRAY pathname errors

- Add *resumed-from-saved-state* flag to prevent scheduler's db:connected
  trigger from overwriting resumed playlist position with full playlist
- Use sb-ext:parse-native-namestring in play-file to prevent SBCL from
  interpreting brackets in directory names (e.g. [WEB FLAC]) as wildcard
  patterns, which caused non-simple-string pathname components that broke
  cl-flac's CFFI calls
This commit is contained in:
Glenn Thompson 2026-03-05 18:21:32 +03:00
parent 1807e58971
commit f39abeb8f8
3 changed files with 26 additions and 7 deletions

View File

@ -290,12 +290,19 @@
FILE-PATH can be a string or pathname. FILE-PATH can be a string or pathname.
ON-END is passed to harmony:play (default :free). ON-END is passed to harmony:play (default :free).
UPDATE-METADATA controls whether ICY metadata is updated immediately." UPDATE-METADATA controls whether ICY metadata is updated immediately."
(let* ((path (pathname file-path)) (let* ((path-string (etypecase file-path
(string file-path)
(pathname (namestring file-path))))
;; Use parse-native-namestring to prevent SBCL from interpreting
;; brackets as wildcard patterns. Standard (pathname ...) turns
;; "[FLAC]" into a wild component with non-simple strings, which
;; causes SIMPLE-ARRAY errors in cl-flac's CFFI calls.
(path (sb-ext:parse-native-namestring path-string))
(server (pipeline-harmony-server pipeline)) (server (pipeline-harmony-server pipeline))
(harmony:*server* server) (harmony:*server* server)
(tags (read-audio-metadata path)) (tags (read-audio-metadata path))
(display-title (format-display-title path title)) (display-title (format-display-title path title))
(track-info (list :file (namestring path) (track-info (list :file path-string
:display-title display-title :display-title display-title
:artist (getf tags :artist) :artist (getf tags :artist)
:title (getf tags :title) :title (getf tags :title)

View File

@ -312,14 +312,21 @@
;;; This ensures the scheduler starts after the server is fully initialized ;;; This ensures the scheduler starts after the server is fully initialized
(define-trigger db:connected () (define-trigger db:connected ()
"Start the playlist scheduler after database connection is established" "Start the playlist scheduler after database connection is established.
Loads the current scheduled playlist only if the pipeline has no tracks
(i.e., we did NOT just resume from saved state)."
(handler-case (handler-case
(progn (progn
(load-schedule-from-db) (load-schedule-from-db)
(start-playlist-scheduler) (start-playlist-scheduler)
;; Only load scheduled playlist if we didn't just resume from saved state
(if *resumed-from-saved-state*
(progn
(setf *resumed-from-saved-state* nil)
(log:info "Playlist scheduler started (resumed from saved state, skipping initial load)"))
(let ((current-playlist (get-current-scheduled-playlist))) (let ((current-playlist (get-current-scheduled-playlist)))
(when current-playlist (when current-playlist
(load-scheduled-playlist current-playlist))) (load-scheduled-playlist current-playlist))
(log:info "Playlist scheduler started")) (log:info "Playlist scheduler started"))))
(error (e) (error (e)
(log:error "Scheduler failed to start: ~a" e)))) (log:error "Scheduler failed to start: ~a" e))))

View File

@ -27,6 +27,10 @@
(defvar *current-playlist-path* nil (defvar *current-playlist-path* nil
"Path of the currently active playlist file.") "Path of the currently active playlist file.")
(defvar *resumed-from-saved-state* nil
"Set to T when startup successfully resumed from saved playback state.
Prevents the scheduler from overwriting the resumed position.")
(defun save-playback-state (track-file-path) (defun save-playback-state (track-file-path)
"Save the current track file path and playlist to the state file. "Save the current track file path and playlist to the state file.
Called on each track change so we can resume after restart." Called on each track change so we can resume after restart."
@ -72,6 +76,7 @@
(m3u-to-file-list playlist-path)))) (m3u-to-file-list playlist-path))))
(when file-list (when file-list
(setf *current-playlist-path* playlist-path) (setf *current-playlist-path* playlist-path)
(setf *resumed-from-saved-state* t)
(let ((pos (when saved-file (let ((pos (when saved-file
(position saved-file file-list :test #'string=)))) (position saved-file file-list :test #'string=))))
(if pos (if pos