change to audio-player.rkt

This commit is contained in:
2026-05-18 10:35:14 +02:00
parent fccf019fe7
commit e60ecaeaef
4 changed files with 188 additions and 370 deletions
+152 -366
View File
@@ -3,12 +3,11 @@
(require racket/class
racket-audio
"utils.rkt"
lru-cache
)
(provide player%)
(define orig-current-seconds current-seconds)
(define player%
(class object%
(init-field [settings #f]
@@ -20,395 +19,182 @@
[buffer-max-seconds 10]
[buffer-min-seconds 4]
)
(define use-ao #t)
(define player #f)
(define playlist #f)
(define state 'stopped)
(define repeat 'no-repeat)
(define full-state (make-hash))
(define music-id -1)
(define track-cache (make-lru 10
#:cmp (λ (a b) (= (car a) (car b)))))
(define (music-id->track-nr id)
(let ((item (lru-use track-cache (list music-id) #f)))
(if (eq? item #f)
#f
(cadr item))))
(define (register-music-id&track-nr id track-nr)
(lru-add! track-cache (list id track-nr)))
(define (clear-music-ids!)
(lru-clear track-cache))
(define pl #f)
(define state 'stopped)
(define track -1)
(define current-track -1)
(define ct-data #f)
(define closing #f)
(define pause #f)
(define stop-in-pause 'nil)
(define repeat-state 'no-repeat)
(define volume (send settings get 'volume 100.0))
(define ao-handle #f)
(define audio-handle #f)
(define current-music-id -1)
(define current-track-id -1)
(define current-rate 0)
(define current-bits 0)
(define current-channels 0)
(define current-audio-format 'none)
(define current-length 0)
(define current-seconds 0)
(define repeat 'no-repeat) ;; no-repeat, repeat-1, repeat-all
(define play-time-updater-state 'stopped)
(define (set-state! st)
(set! state st)
(state-updater st))
(define (check-ao-handle)
(when (eq? ao-handle #f)
(dbg-rktplayer "ao-handle equals #f")
(unless (or (= current-rate 0) (= current-bits 0) (= current-channels 0))
(dbg-rktplayer "current-rate = ~a, current-bits = ~a, current-channels = ~a, ao-handle = ~a"
current-rate current-bits current-channels ao-handle)
(dbg-rktplayer "Opening ao-handle")
(when use-ao
(set! ao-handle (ao-open-live current-bits current-rate current-channels 'native-endian))
(start-play-time-updater)
)
(define (audio-state-cb handle st*)
(set! full-state st*)
(let ((st (audio-state player)))
(when (or (eq? st 'paused) (eq? st 'playing))
(time-updater (audio-at-second player)
(audio-duration player))
(when (not (= music-id (audio-music-id player)))
(set! music-id (audio-music-id player))
(let ((track-nr (music-id->track-nr music-id)))
(if (eq? track-nr #f)
(error "Unexpected: no track-nr for given music-id")
(track-nr-updater track-nr))))
)
)
(when (not (= (ao-volume ao-handle) volume))
(ao-set-volume! ao-handle volume))
)
(define (start-play-time-updater)
(when (eq? play-time-updater-state 'stopped)
(set! play-time-updater-state 'updating)
(dbg-rktplayer "Starting play-time-updater")
(thread (λ ()
(define (updater)
(if (or (eq? ao-handle #f) closing)
(begin
(set! play-time-updater-state 'stopped)
(dbg-rktplayer "Terminating play-time-updater")
'done)
(let ((seconds (ao-at-second ao-handle))
(duration (ao-music-duration ao-handle))
(music-id (ao-at-music-id ao-handle))
)
(set! current-seconds seconds)
(time-updater current-seconds duration)
(unless (= music-id current-music-id)
(dbg-rktplayer "a ~a ~a ~a" music-id current-track-id seconds)
(set! current-music-id music-id)
(track-nr-updater track))
(sleep 0.2)
(updater))))
(updater)
)
)
(state-updater st)
(repeat-updater repeat)
(audio-info-cb (audio-rate player) (audio-channels player)
(audio-bits player) (audio-decoder player))
)
)
(define (on-eof-stream-cb handle)
(let ((track-nr (music-id->track-nr music-id)))
(send this next)))
(define (stream-equal? rate bits channels)
(and (= current-rate rate)
(= current-bits bits)
(= current-channels channels)))
(define (audio-play type ao-type handle buf-info buffer buf-len)
(unless (eq? state 'quitted)
(let* ((sample (hash-ref buf-info 'sample))
(rate (hash-ref buf-info 'sample-rate))
(second (/ (* sample 1.0) (* rate 1.0)))
(bits-per-sample (hash-ref buf-info 'bits-per-sample))
(bytes-per-sample (/ bits-per-sample 8))
(channels (hash-ref buf-info 'channels))
(bytes-per-sample-all-channels (* channels bytes-per-sample))
(duration (hash-ref buf-info 'duration))
)
(unless (stream-equal? rate bits-per-sample channels)
(dbg-rktplayer "Stream has changed to ~a ~a ~a" rate bits-per-sample channels)
(unless (eq? ao-handle #f)
(dbg-rktplayer "Waiting for play buffer to reach empty state, buf-size = ~a" (ao-bufsize-async ao-handle))
(while (> (ao-bufsize-async ao-handle) 0)
(dbg-rktplayer "buffer size: ~a" (ao-bufsize-async ao-handle))
(sleep 1.0) ;0.25)
)
(dbg-rktplayer "buffer size: ~a" (ao-bufsize-async ao-handle))
(dbg-rktplayer "Empty state reached")
(dbg-rktplayer "Closing ao-handle")
(ao-close ao-handle)
(set! ao-handle #f))
)
(set! current-rate rate)
(set! current-bits bits-per-sample)
(set! current-channels channels)
(set! current-length duration)
(when (eq? ao-handle #f)
(audio-info-cb sample current-rate current-channels current-bits current-audio-format)
)
(check-ao-handle)
(when (not (eq? ao-handle #f))
(let ((buf-seconds-left (λ () (exact->inexact
(/ (ao-bufsize-async ao-handle)
bytes-per-sample-all-channels
rate)))))
(when (> (buf-seconds-left) buffer-max-seconds)
(dbg-rktplayer "waiting for buffer to get below ~as" buffer-min-seconds)
(while (and (not (eq? ao-handle #f))
(not closing)
(not pause)
(> (buf-seconds-left) buffer-min-seconds))
(sleep 0.25))
(dbg-rktplayer "under ~a seconds" buffer-min-seconds)))
(when (not (eq? ao-handle #f))
(ao-play ao-handle current-track-id second duration buffer buf-len ao-type)
)
)
(when pause
(dbg-rktplayer "Pausing now...")
(set-state! 'pauzed)
(ao-pause ao-handle #t)
(while (and (not (eq? ao-handle #f))
(not (eq? stop-in-pause 'stop-request))
(not closing)
pause)
(sleep 0.5))
(if (eq? stop-in-pause 'stop-request)
(set! stop-in-pause 'stop-ack)
(begin
(ao-pause ao-handle #f)
(dbg-rktplayer "Playing on...")
(set-state! 'playing))
)
)
)
)
)
(define (audio-meta type ao-type handle meta)
(set! current-audio-format type)
(dbg-rktplayer "type: ~a" type)
(dbg-rktplayer "ao-type: ~a" ao-type)
(dbg-rktplayer "meta: ~a" meta))
(define (play-track-worker)
(thread
(λ ()
(if (eq? ct-data #f)
'no-track-data
(let ((file (send ct-data get-file)))
(dbg-rktplayer "opening audios handle for file: ~a" file)
(set! audio-handle (audio-open file audio-meta audio-play))
(set! current-track-id (send ct-data get-id))
(dbg-rktplayer "Starting audio-read")
(audio-read audio-handle)
(unless (eq? state 'stopped)
(set-state! 'track-feeded)
(dbg-rktplayer "Audio read done")
)
'worker-done
)
)
)
)
(set-state! 'playing)
'playing
)
(define (close-player*)
(dbg-rktplayer "Closing audio handle")
(set! closing #t)
(unless (eq? audio-handle #f)
(audio-stop audio-handle)
(set! audio-handle #f))
(set! current-rate 0)
(set! current-channels 0)
(set! current-bits 0)
(set! ct-data #f)
(unless (eq? ao-handle #f)
(let ((h ao-handle))
(dbg-rktplayer "closing ao-handle")
(set! ao-handle #f)
(dbg-rktplayer "ao-handle = ~a" h)
(ao-close h)
))
(dbg-rktplayer "close-player*: ao-handle = ~a" ao-handle)
(dbg-rktplayer "Waiting for updater to stop")
(while (eq? play-time-updater-state 'updating)
(dbg-rktplayer "close-player*: ao-handle = ~a" ao-handle)
(sleep 0.1))
(dbg-rktplayer "resetting tracks")
(set! track -1)
(set! current-track -1)
(set! closing #f)
(dbg-rktplayer "done close-player*")
)
(define (quit-player)
(close-player*)
(set-state! 'quitted)
)
(define (stop-and-clear)
(info-rktplayer "STOP AND CLEAR")
(when pause
(set! stop-in-pause 'stop-request)
(set! pause #f)
(while (not (eq? stop-in-pause 'stop-ack))
(sleep 0.2)))
(set-state! 'stopped)
(info-rktplayer "calling close-player*")
(close-player*)
)
(define/public (next-track)
(unless (eq? repeat-state 'repeat-one)
(set! track (+ track 1)))
(when (eq? repeat-state 'repeat-all)
(when (>= track (send pl length))
(set! track 0)))
(if (>= track (send pl length))
(begin
(set-state! 'stopped)
(track-nr-updater #f))
(begin
(set! ct-data (send pl track track))
(set-state! 'play)
;(track-nr-updater track)
)
)
)
(define/public (play-track i)
(unless (= (send pl length) 0)
(dbg-rktplayer "play-track ~a" i)
(set! state 'stopped)
(close-player*)
(dbg-rktplayer "Player closed")
(set! track i)
(set! ct-data (send pl track i))
(set-state! 'play)
(dbg-rktplayer "Set state to 'play, updating to track ~a" track)
(track-nr-updater track)
(dbg-rktplayer "track-nr-updater called")
)
)
(define/public (stop)
(stop-and-clear)
)
(define/public (set-volume! percentage)
(set! volume percentage)
(send settings set! 'volume percentage)
(unless (eq? ao-handle #f)
(ao-set-volume! ao-handle volume))
)
(define (check-player)
(when (eq? player #f)
(set! player (make-audio-player audio-state-cb on-eof-stream-cb))))
(define/public (get-volume)
volume)
(check-player)
(audio-volume player))
(define/public (set-volume! percentage)
(check-player)
(audio-volume! player percentage))
(define/public (set-list! playlist*)
;; if the player exists and is playing, stop it.
(unless (eq? player #f)
(audio-stop! player))
;; Set the playlist to the new one.
(set! playlist playlist*)
;; reset music-id to -1, because the playlist has been reset.
(set! music-id -1)
;; clear lru cache, because the playlist has been reset.
(clear-music-ids!)
)
(define/public (play playlist*)
(check-player)
(set-list! playlist*)
(send this play-track 0))
(define/public (play-track nr)
(check-player)
(when (and (>= nr 0) (< nr (send playlist length)))
(let ((track (send playlist track nr)))
(let ((id (audio-play! player (send track get-file))))
(register-music-id&track-nr id nr)))))
(define/public (next)
(if (= (send pl length) 0)
#f
(let ((idx track))
(set! idx (+ idx 1))
(when (>= idx (send pl length))
(set! idx 0))
(send this play-track idx))
))
(check-player)
(if (= music-id -1)
(warn-rktplayer "No music-id set (yet), so can't play anything next")
(let ((track-nr (music-id->track-nr music-id)))
(if (eq? track-nr #f)
(error "Unexpected: no track-nr for given music-id")
(begin
(cond
((eq? repeat 'repeat-one) (play-track track-nr))
((eq? repeat 'repeat-all)
(set! track-nr (+ track-nr 1))
(when (>= track-nr (send playlist length))
(set! track-nr 0))
(play-track track-nr))
(else
(set! track-nr (+ track-nr 1))
(if (>= track-nr (send playlist length))
(stop)
(play-track track-nr)))
)
)
)
)
)
)
(define/public (previous)
(if (= (send pl length) 0)
#f
(let ((idx track))
(set! idx (- idx 1))
(when (< idx 0)
(set! idx (- (send pl length) 1)))
(send this play-track idx)
)))
(define/public (pause-unpause)
(set! pause (not pause))
(dbg-rktplayer "pauzed: ~a" pause)
(check-player)
(if (= music-id -1)
(warn-rktplayer "No music-id set (yet), so can't play anything previous")
(let ((track-nr (music-id->track-nr music-id)))
(if (eq? track-nr #f)
(error "Unexpected: no track-nr for given music-id")
(begin
(cond
((eq? repeat 'repeat-one) (play-track track-nr))
((eq? repeat 'repeat-all)
(set! track-nr (- track-nr 1))
(when (< track-nr 0)
(set! track-nr (- (send playlist length) 1)))
(play-track track-nr))
(else
(set! track-nr (- track-nr 1))
(when (< track-nr 0) (set! track-nr 0))
(play-track track-nr))
)
)
)
)
)
)
(define/public (pause!)
(set! pause #t))
(check-player)
(audio-pause! player #t))
(define/public (play!)
(set! pause #f))
(check-player)
(audio-pause! player #f))
(define/public (get-repeat)
repeat-state)
(define/public (repeat! state) ; no-repeat, repeat-all, repeat-one
(set! repeat-state state)
(repeat-updater state)
)
(define/public (pause-unpause)
(check-player)
(if (audio-paused? player)
(send this pause!)
(send this play!)))
(define/public (stop)
(check-player)
(audio-stop! player))
(define/public (seek percentage)
(ao-clear-async ao-handle)
(audio-seek audio-handle percentage))
(check-player)
(audio-seek! player percentage))
(define/public (get-repeat)
(check-player)
repeat)
(define/public (repeat! r)
(check-player)
(set! repeat r))
(define (state-machine)
(let ((st (orig-current-seconds))
(s (orig-current-seconds)))
(define (worker)
(if (eq? state 'quit)
(begin
(quit-player)
'done)
(begin
(cond
((eq? state 'stopped)
(sleep 0.1))
((eq? state 'play)
(if (eq? pl #f)
(set-state! 'stoppped)
(play-track-worker)))
((eq? state 'playing)
(sleep 0.1))
((eq? state 'track-feeded)
(send this next-track))
(else
(sleep 0.1))
)
;(let ((ns (orig-current-seconds)))
; (when (> (- ns 5) s)
; (displayln (format "state-machine: ~a" (- ns st)))
; (set! s ns)))
(worker)
)
))
(worker)))
(define/public (set-list! playlist)
(stop-and-clear)
(set! pl playlist)
)
(define/public (play playlist)
(send this set-list! playlist)
(send this play-track 0)
)
(define/public (quit)
(set-state! 'quit)
(while (not (eq? state 'quitted))
(sleep 0.1))
)
(unless (eq? player #f)
(audio-quit! player)))
(super-new)
(begin
(thread (λ () (state-machine)))
(dbg-rktplayer "player% initialized")
)
)
)