dlna
This commit is contained in:
@@ -1,3 +1,11 @@
|
|||||||
# rktplayer
|
# rktplayer
|
||||||
|
|
||||||
Racket Music Player
|
Racket Music Player
|
||||||
|
|
||||||
|
## DLNA playback
|
||||||
|
|
||||||
|
Install the `racket-upnp` package before starting rktplayer. Choose
|
||||||
|
**Player > Find DLNA renderers** and then select a renderer from the same menu.
|
||||||
|
RktPlayer publishes the selected local media files on TCP port 8080 and sends
|
||||||
|
their URLs to the renderer. The renderer must therefore be able to reach the
|
||||||
|
computer running rktplayer on that port.
|
||||||
|
|||||||
+244
@@ -0,0 +1,244 @@
|
|||||||
|
#lang racket
|
||||||
|
|
||||||
|
(require racket/class
|
||||||
|
racket/path
|
||||||
|
racket/udp
|
||||||
|
racket-upnp
|
||||||
|
"utils.rkt")
|
||||||
|
|
||||||
|
(provide dlna-player%)
|
||||||
|
|
||||||
|
(define dlna-player%
|
||||||
|
(class object%
|
||||||
|
(init-field renderer
|
||||||
|
[port 8080]
|
||||||
|
[time-updater (lambda (time-s length-s) #t)]
|
||||||
|
[track-nr-updater (lambda (nr) #t)]
|
||||||
|
[state-updater (lambda (state) #t)]
|
||||||
|
[track-ended (lambda () #t)]
|
||||||
|
[track-changed (lambda (nr) #t)])
|
||||||
|
|
||||||
|
(define lock (make-semaphore 1))
|
||||||
|
(define server #f)
|
||||||
|
(define poll-thread #f)
|
||||||
|
(define stopped #f)
|
||||||
|
(define requested-stop #t)
|
||||||
|
(define current-state 'stopped)
|
||||||
|
(define current-track-nr #f)
|
||||||
|
(define current-duration 0)
|
||||||
|
(define uri->track-nr (make-hash))
|
||||||
|
|
||||||
|
(define (with-renderer f)
|
||||||
|
(call-with-semaphore lock f))
|
||||||
|
|
||||||
|
(define (local-address)
|
||||||
|
(let ((socket (udp-open-socket)))
|
||||||
|
(dynamic-wind
|
||||||
|
void
|
||||||
|
(lambda ()
|
||||||
|
(udp-connect! socket (media-renderer-address renderer) 1900)
|
||||||
|
(let-values (((address local-port remote-address remote-port)
|
||||||
|
(udp-addresses socket #t)))
|
||||||
|
address))
|
||||||
|
(lambda ()
|
||||||
|
(udp-close socket)))))
|
||||||
|
|
||||||
|
(define (start-server)
|
||||||
|
(let ((address (local-address)))
|
||||||
|
(info-rktplayer
|
||||||
|
"Starting DLNA media server on ~a:~a for ~a"
|
||||||
|
address
|
||||||
|
port
|
||||||
|
(media-renderer-name renderer))
|
||||||
|
(start-media-file-server
|
||||||
|
(format "http://~a:~a/media/" address port)
|
||||||
|
#:listen-ip address)))
|
||||||
|
|
||||||
|
(define (file-url file track-nr)
|
||||||
|
(let* ((extension (path-get-extension file))
|
||||||
|
(name (if extension
|
||||||
|
(format "track-~a~a" track-nr extension)
|
||||||
|
(format "track-~a" track-nr)))
|
||||||
|
(uri (media-file-server-publish! server file name)))
|
||||||
|
(hash-set! uri->track-nr uri track-nr)
|
||||||
|
uri))
|
||||||
|
|
||||||
|
(define (renderer-call what f)
|
||||||
|
(with-handlers
|
||||||
|
((exn:fail?
|
||||||
|
(lambda (exception)
|
||||||
|
(warn-rktplayer
|
||||||
|
"Could not ~a on DLNA renderer ~a: ~a"
|
||||||
|
what
|
||||||
|
(media-renderer-name renderer)
|
||||||
|
(exn-message exception))
|
||||||
|
#f)))
|
||||||
|
(with-renderer f)))
|
||||||
|
|
||||||
|
(define (update-track uri)
|
||||||
|
(let ((nr (and uri (hash-ref uri->track-nr uri #f))))
|
||||||
|
(when (and nr
|
||||||
|
(or (not current-track-nr)
|
||||||
|
(not (= nr current-track-nr))))
|
||||||
|
(set! current-track-nr nr)
|
||||||
|
(track-nr-updater nr)
|
||||||
|
(track-changed nr))))
|
||||||
|
|
||||||
|
(define (poll)
|
||||||
|
(let ((reported-state
|
||||||
|
(renderer-call
|
||||||
|
"read playback state"
|
||||||
|
(lambda ()
|
||||||
|
(media-renderer-status renderer)))))
|
||||||
|
(when reported-state
|
||||||
|
(let ((state (if (eq? reported-state 'no-media)
|
||||||
|
'stopped
|
||||||
|
reported-state)))
|
||||||
|
(unless (eq? state current-state)
|
||||||
|
(set! current-state state)
|
||||||
|
(state-updater state))
|
||||||
|
(when (or (eq? state 'playing)
|
||||||
|
(eq? state 'paused)
|
||||||
|
(eq? state 'transitioning))
|
||||||
|
(let ((position
|
||||||
|
(renderer-call
|
||||||
|
"read playback position"
|
||||||
|
(lambda ()
|
||||||
|
(media-renderer-position renderer)))))
|
||||||
|
(when position
|
||||||
|
(let ((seconds (transport-position-seconds position))
|
||||||
|
(duration (transport-position-duration position)))
|
||||||
|
(when (and seconds duration)
|
||||||
|
(set! current-duration duration)
|
||||||
|
(time-updater seconds duration))
|
||||||
|
(update-track (transport-position-uri position))))))
|
||||||
|
(when (and (eq? state 'stopped)
|
||||||
|
(not requested-stop))
|
||||||
|
(set! requested-stop #t)
|
||||||
|
(track-ended))))))
|
||||||
|
|
||||||
|
(define (poll-loop)
|
||||||
|
(let loop ()
|
||||||
|
(unless stopped
|
||||||
|
(with-handlers
|
||||||
|
((exn:fail?
|
||||||
|
(lambda (exception)
|
||||||
|
(warn-rktplayer
|
||||||
|
"DLNA polling failed for ~a: ~a"
|
||||||
|
(media-renderer-name renderer)
|
||||||
|
(exn-message exception)))))
|
||||||
|
(poll))
|
||||||
|
(sleep 0.5)
|
||||||
|
(loop))))
|
||||||
|
|
||||||
|
(define/public (name)
|
||||||
|
(media-renderer-name renderer))
|
||||||
|
|
||||||
|
(define/public (next-uri-supported?)
|
||||||
|
(and (renderer-call
|
||||||
|
"inspect supported actions"
|
||||||
|
(lambda ()
|
||||||
|
(media-renderer-next-uri-supported? renderer)))
|
||||||
|
#t))
|
||||||
|
|
||||||
|
(define/public (play-file! file track-nr
|
||||||
|
#:next-file (next-file #f)
|
||||||
|
#:next-track-nr (next-track-nr #f))
|
||||||
|
(let ((uri (file-url file track-nr))
|
||||||
|
(next-uri (and next-file
|
||||||
|
next-track-nr
|
||||||
|
(file-url next-file next-track-nr))))
|
||||||
|
(set! requested-stop #f)
|
||||||
|
(set! current-track-nr track-nr)
|
||||||
|
(track-nr-updater track-nr)
|
||||||
|
(state-updater 'transitioning)
|
||||||
|
(if next-uri
|
||||||
|
(with-renderer
|
||||||
|
(lambda ()
|
||||||
|
(media-renderer-play-uri!
|
||||||
|
renderer
|
||||||
|
uri
|
||||||
|
#:next-uri next-uri)))
|
||||||
|
(with-renderer
|
||||||
|
(lambda ()
|
||||||
|
(media-renderer-play-uri! renderer uri))))))
|
||||||
|
|
||||||
|
(define/public (set-next-file! file track-nr)
|
||||||
|
(let ((uri (file-url file track-nr)))
|
||||||
|
(with-renderer
|
||||||
|
(lambda ()
|
||||||
|
(media-renderer-set-next-uri! renderer uri)))))
|
||||||
|
|
||||||
|
(define/public (clear-next!)
|
||||||
|
(with-renderer
|
||||||
|
(lambda ()
|
||||||
|
(media-renderer-set-next-uri! renderer ""))))
|
||||||
|
|
||||||
|
(define/public (pause!)
|
||||||
|
(with-renderer
|
||||||
|
(lambda ()
|
||||||
|
(media-renderer-pause! renderer))))
|
||||||
|
|
||||||
|
(define/public (play!)
|
||||||
|
(with-renderer
|
||||||
|
(lambda ()
|
||||||
|
(media-renderer-play! renderer))))
|
||||||
|
|
||||||
|
(define/public (stop!)
|
||||||
|
(set! requested-stop #t)
|
||||||
|
(with-renderer
|
||||||
|
(lambda ()
|
||||||
|
(media-renderer-stop! renderer))))
|
||||||
|
|
||||||
|
(define/public (seek! percentage)
|
||||||
|
(when (> current-duration 0)
|
||||||
|
(with-renderer
|
||||||
|
(lambda ()
|
||||||
|
(media-renderer-seek!
|
||||||
|
renderer
|
||||||
|
(* current-duration (/ percentage 100.0)))))))
|
||||||
|
|
||||||
|
(define/public (volume)
|
||||||
|
(or (renderer-call
|
||||||
|
"read volume"
|
||||||
|
(lambda ()
|
||||||
|
(media-renderer-volume renderer)))
|
||||||
|
0))
|
||||||
|
|
||||||
|
(define/public (set-volume! percentage)
|
||||||
|
(renderer-call
|
||||||
|
"set volume"
|
||||||
|
(lambda ()
|
||||||
|
(media-renderer-set-volume!
|
||||||
|
renderer
|
||||||
|
(max 0
|
||||||
|
(min 100
|
||||||
|
(inexact->exact
|
||||||
|
(round percentage))))))))
|
||||||
|
|
||||||
|
(define/public (quit)
|
||||||
|
(set! requested-stop #t)
|
||||||
|
(set! stopped #t)
|
||||||
|
(when poll-thread
|
||||||
|
(kill-thread poll-thread))
|
||||||
|
(with-handlers
|
||||||
|
((exn:fail?
|
||||||
|
(lambda (exception)
|
||||||
|
(dbg-rktplayer
|
||||||
|
"Could not stop DLNA renderer while quitting: ~a"
|
||||||
|
(exn-message exception)))))
|
||||||
|
(with-renderer
|
||||||
|
(lambda ()
|
||||||
|
(media-renderer-stop! renderer))))
|
||||||
|
(when server
|
||||||
|
(media-file-server-stop! server)
|
||||||
|
(set! server #f)))
|
||||||
|
|
||||||
|
(super-new)
|
||||||
|
|
||||||
|
(begin
|
||||||
|
(set! server (start-server))
|
||||||
|
(set! poll-thread (thread poll-loop))
|
||||||
|
(info-rktplayer
|
||||||
|
"DLNA player initialized for ~a"
|
||||||
|
(media-renderer-name renderer)))))
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
|
|
||||||
(define player-menu
|
(define player-menu
|
||||||
(λ ()
|
(λ (player-submenu)
|
||||||
(wv-menu 'main-menu
|
(wv-menu 'main-menu
|
||||||
(wv-menu-item 'm-file (tr 'file)
|
(wv-menu-item 'm-file (tr 'file)
|
||||||
#:submenu (wv-menu 'file-menu
|
#:submenu (wv-menu 'file-menu
|
||||||
@@ -33,6 +33,9 @@
|
|||||||
(wv-menu-item 'm-quit (tr 'quit) #:separator #t)
|
(wv-menu-item 'm-quit (tr 'quit) #:separator #t)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
(wv-menu-item 'm-player
|
||||||
|
(tr 'player)
|
||||||
|
#:submenu player-submenu)
|
||||||
)))
|
)))
|
||||||
|
|
||||||
(define rktplayer%
|
(define rktplayer%
|
||||||
@@ -82,6 +85,116 @@
|
|||||||
|
|
||||||
(define current-at-seconds 0)
|
(define current-at-seconds 0)
|
||||||
(define current-length-seconds 0)
|
(define current-length-seconds 0)
|
||||||
|
(define dlna-renderers '())
|
||||||
|
(define selected-dlna-renderer #f)
|
||||||
|
(define finding-dlna-renderers #f)
|
||||||
|
(define dlna-menu-items (make-hash))
|
||||||
|
|
||||||
|
(define (selected-dlna-renderer? renderer)
|
||||||
|
(and selected-dlna-renderer
|
||||||
|
(string=?
|
||||||
|
(send player dlna-renderer-name renderer)
|
||||||
|
(send player
|
||||||
|
dlna-renderer-name
|
||||||
|
selected-dlna-renderer))))
|
||||||
|
|
||||||
|
(define (make-player-submenu)
|
||||||
|
(hash-clear! dlna-menu-items)
|
||||||
|
(let ((items
|
||||||
|
(list
|
||||||
|
(wv-menu-item
|
||||||
|
'm-player-local
|
||||||
|
(if (eq? (send player kind) 'local)
|
||||||
|
(format "[x] ~a" (tr 'local-player))
|
||||||
|
(tr 'local-player)))
|
||||||
|
(wv-menu-item
|
||||||
|
'm-find-dlna
|
||||||
|
(if finding-dlna-renderers
|
||||||
|
(tr 'finding-dlna-renderers)
|
||||||
|
(tr 'find-dlna-renderers))
|
||||||
|
#:separator #t))))
|
||||||
|
(let ((nr 0))
|
||||||
|
(for-each
|
||||||
|
(lambda (renderer)
|
||||||
|
(let ((id (string->symbol (format "m-dlna-~a" nr)))
|
||||||
|
(name (send player dlna-renderer-name renderer)))
|
||||||
|
(hash-set! dlna-menu-items id renderer)
|
||||||
|
(set! items
|
||||||
|
(append
|
||||||
|
items
|
||||||
|
(list
|
||||||
|
(wv-menu-item
|
||||||
|
id
|
||||||
|
(if (selected-dlna-renderer? renderer)
|
||||||
|
(format "[x] ~a" name)
|
||||||
|
name)))))
|
||||||
|
(set! nr (+ nr 1))))
|
||||||
|
dlna-renderers))
|
||||||
|
(apply wv-menu 'player-menu items)))
|
||||||
|
|
||||||
|
(define (install-menu)
|
||||||
|
(send this set-menu! (player-menu (make-player-submenu)))
|
||||||
|
(send this connect-menu! 'm-quit (lambda () (send this quit)))
|
||||||
|
(send this connect-menu!
|
||||||
|
'm-select-library-dir
|
||||||
|
(lambda () (send this select-library)))
|
||||||
|
(send this connect-menu!
|
||||||
|
'm-settings
|
||||||
|
(lambda () (send this settings-dlg)))
|
||||||
|
(send this connect-menu!
|
||||||
|
'm-player-local
|
||||||
|
(lambda ()
|
||||||
|
(send player change-player 'local)
|
||||||
|
(set! selected-dlna-renderer #f)
|
||||||
|
(install-menu)))
|
||||||
|
(send this connect-menu!
|
||||||
|
'm-find-dlna
|
||||||
|
(lambda ()
|
||||||
|
(send this find-dlna-renderers)))
|
||||||
|
(hash-for-each
|
||||||
|
dlna-menu-items
|
||||||
|
(lambda (id renderer)
|
||||||
|
(send this connect-menu!
|
||||||
|
id
|
||||||
|
(lambda ()
|
||||||
|
(send this select-dlna-renderer renderer))))))
|
||||||
|
|
||||||
|
(define/public (find-dlna-renderers)
|
||||||
|
(unless finding-dlna-renderers
|
||||||
|
(set! finding-dlna-renderers #t)
|
||||||
|
(install-menu)
|
||||||
|
(thread
|
||||||
|
(lambda ()
|
||||||
|
(let ((renderers
|
||||||
|
(with-handlers
|
||||||
|
((exn:fail?
|
||||||
|
(lambda (exception)
|
||||||
|
(warn-rktplayer
|
||||||
|
"Could not find DLNA renderers: ~a"
|
||||||
|
(exn-message exception))
|
||||||
|
'())))
|
||||||
|
(send player query-dlna-renderers))))
|
||||||
|
(queue-callback
|
||||||
|
(lambda ()
|
||||||
|
(set! dlna-renderers renderers)
|
||||||
|
(set! finding-dlna-renderers #f)
|
||||||
|
(info-rktplayer
|
||||||
|
"Found ~a DLNA renderer(s)"
|
||||||
|
(length dlna-renderers))
|
||||||
|
(install-menu))))))))
|
||||||
|
|
||||||
|
(define/public (select-dlna-renderer renderer)
|
||||||
|
(with-handlers
|
||||||
|
((exn:fail?
|
||||||
|
(lambda (exception)
|
||||||
|
(message-box
|
||||||
|
(tr 'dlna-error)
|
||||||
|
(exn-message exception)
|
||||||
|
#f
|
||||||
|
'(ok stop)))))
|
||||||
|
(send player change-player 'dlna #:renderer renderer)
|
||||||
|
(set! selected-dlna-renderer renderer)
|
||||||
|
(install-menu)))
|
||||||
|
|
||||||
(define/public (update-volume)
|
(define/public (update-volume)
|
||||||
(let ((el (send this element 'volume-percentage)))
|
(let ((el (send this element 'volume-percentage)))
|
||||||
@@ -193,7 +306,9 @@
|
|||||||
(unless (eq? st state)
|
(unless (eq? st state)
|
||||||
(dbg-rktplayer "Changing to state ~a" st)
|
(dbg-rktplayer "Changing to state ~a" st)
|
||||||
(let ((el (send this element 'paused)))
|
(let ((el (send this element 'paused)))
|
||||||
(cond ((or (eq? st 'playing) (eq? st 'play))
|
(cond ((or (eq? st 'playing)
|
||||||
|
(eq? st 'play)
|
||||||
|
(eq? st 'transitioning))
|
||||||
(set-play-button "buttons/pause.svg")
|
(set-play-button "buttons/pause.svg")
|
||||||
(send el set-innerHTML! (list 'span (tr 'playing))))
|
(send el set-innerHTML! (list 'span (tr 'playing))))
|
||||||
((eq? st 'stopped)
|
((eq? st 'stopped)
|
||||||
@@ -407,11 +522,7 @@
|
|||||||
(set! el-channels (send this element 'channels))
|
(set! el-channels (send this element 'channels))
|
||||||
(set! el-format (send this element 'format))
|
(set! el-format (send this element 'format))
|
||||||
|
|
||||||
(send this set-menu! (player-menu))
|
(install-menu)
|
||||||
(send this connect-menu! 'm-quit (λ () (send this quit)))
|
|
||||||
(send this connect-menu! 'm-select-library-dir (λ () (send this select-library)))
|
|
||||||
(send this connect-menu! 'm-settings (λ () (send this settings-dlg)))
|
|
||||||
(send this connect-menu! 'm-add-tab (λ () (send this add-tab)))
|
|
||||||
|
|
||||||
(dbg-rktplayer "page-loaded, playlist = ~a" playlist)
|
(dbg-rktplayer "page-loaded, playlist = ~a" playlist)
|
||||||
(send this update-tabs)
|
(send this update-tabs)
|
||||||
@@ -735,5 +846,3 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+36
-5
@@ -1,13 +1,14 @@
|
|||||||
#lang racket/base
|
#lang racket/base
|
||||||
|
|
||||||
(require racket/class
|
(require racket/class)
|
||||||
"utils.rkt"
|
|
||||||
)
|
|
||||||
|
|
||||||
(provide libraries%
|
(provide libraries%
|
||||||
library%
|
library%
|
||||||
)
|
)
|
||||||
|
|
||||||
|
(define (new-id)
|
||||||
|
(string->symbol
|
||||||
|
(format "id-~a-~a" (current-milliseconds) (random 1000000))))
|
||||||
|
|
||||||
(define library%
|
(define library%
|
||||||
(class object%
|
(class object%
|
||||||
@@ -85,7 +86,7 @@
|
|||||||
(define/public (add-library l)
|
(define/public (add-library l)
|
||||||
(let ((libs (send this libraries)))
|
(let ((libs (send this libraries)))
|
||||||
(send this set-libraries!
|
(send this set-libraries!
|
||||||
(cons (send l ->list) libs))
|
(cons l libs))
|
||||||
(set! libs #f)
|
(set! libs #f)
|
||||||
(send l get-id)))
|
(send l get-id)))
|
||||||
|
|
||||||
@@ -111,4 +112,34 @@
|
|||||||
))
|
))
|
||||||
(f (send this libraries))))
|
(f (send this libraries))))
|
||||||
|
|
||||||
))
|
))
|
||||||
|
|
||||||
|
(module+ test
|
||||||
|
(require rackunit)
|
||||||
|
|
||||||
|
(define settings%
|
||||||
|
(class object%
|
||||||
|
(super-new)
|
||||||
|
(define values (make-hasheq))
|
||||||
|
(define/public (clone section) this)
|
||||||
|
(define/public (get key default)
|
||||||
|
(hash-ref values key default))
|
||||||
|
(define/public (set! key value)
|
||||||
|
(hash-set! values key value))))
|
||||||
|
|
||||||
|
(define settings (new settings%))
|
||||||
|
(define libraries (new libraries% [settings settings]))
|
||||||
|
(define library
|
||||||
|
(new library%
|
||||||
|
[id 'test-library]
|
||||||
|
[name "Test library"]
|
||||||
|
[local-path "/music"]
|
||||||
|
[host ""]
|
||||||
|
[prefixes "/music"]))
|
||||||
|
|
||||||
|
(check-eq? (send libraries add-library library) 'test-library)
|
||||||
|
(check-equal? (send settings get 'libraries #f)
|
||||||
|
'((test-library "Test library" "/music" "" "/music" #f)))
|
||||||
|
(check-equal? (send libraries count) 1)
|
||||||
|
(check-eq? (send libraries get-library 'test-library)
|
||||||
|
(car (send libraries libraries))))
|
||||||
|
|||||||
+169
-80
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
(require racket/class
|
(require racket/class
|
||||||
racket-audio
|
racket-audio
|
||||||
|
racket-upnp
|
||||||
"utils.rkt"
|
"utils.rkt"
|
||||||
|
"dlna-player.rkt"
|
||||||
lru-cache
|
lru-cache
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -25,9 +27,11 @@
|
|||||||
(define player-basepaths #f)
|
(define player-basepaths #f)
|
||||||
|
|
||||||
(define player #f)
|
(define player #f)
|
||||||
|
(define dlna-player #f)
|
||||||
(define playlist #f)
|
(define playlist #f)
|
||||||
(define state 'stopped)
|
(define state 'stopped)
|
||||||
(define repeat 'no-repeat)
|
(define repeat 'no-repeat)
|
||||||
|
(define current-track-nr #f)
|
||||||
|
|
||||||
(define full-state (make-hash))
|
(define full-state (make-hash))
|
||||||
(define music-id -1)
|
(define music-id -1)
|
||||||
@@ -56,6 +60,7 @@
|
|||||||
;; (set! x (+ x 1)))
|
;; (set! x (+ x 1)))
|
||||||
(unless (or (not (eq? player handle)) (eq? player #f))
|
(unless (or (not (eq? player handle)) (eq? player #f))
|
||||||
(let ((st (audio-state player)))
|
(let ((st (audio-state player)))
|
||||||
|
(set! state st)
|
||||||
(when (or (eq? st 'paused) (eq? st 'playing))
|
(when (or (eq? st 'paused) (eq? st 'playing))
|
||||||
(time-updater (audio-at-second player)
|
(time-updater (audio-at-second player)
|
||||||
(audio-duration player))
|
(audio-duration player))
|
||||||
@@ -64,7 +69,9 @@
|
|||||||
(let ((track-nr (music-id->track-nr music-id)))
|
(let ((track-nr (music-id->track-nr music-id)))
|
||||||
(if (eq? track-nr #f)
|
(if (eq? track-nr #f)
|
||||||
(warn-rktplayer "Unexpected: no track-nr for given music-id")
|
(warn-rktplayer "Unexpected: no track-nr for given music-id")
|
||||||
(track-nr-updater track-nr))))
|
(begin
|
||||||
|
(set! current-track-nr track-nr)
|
||||||
|
(track-nr-updater track-nr)))))
|
||||||
)
|
)
|
||||||
(state-updater st)
|
(state-updater st)
|
||||||
(repeat-updater repeat)
|
(repeat-updater repeat)
|
||||||
@@ -78,16 +85,63 @@
|
|||||||
|
|
||||||
(define (on-eof-stream-cb handle)
|
(define (on-eof-stream-cb handle)
|
||||||
(when (and (eq? player handle) (not (eq? player #f)))
|
(when (and (eq? player handle) (not (eq? player #f)))
|
||||||
(let ((track-nr (music-id->track-nr music-id)))
|
(send this next)))
|
||||||
(send this next))))
|
|
||||||
|
|
||||||
|
(define (next-track-nr nr)
|
||||||
|
(cond
|
||||||
|
((eq? repeat 'repeat-one) nr)
|
||||||
|
((< (+ nr 1) (send playlist length)) (+ nr 1))
|
||||||
|
((eq? repeat 'repeat-all) 0)
|
||||||
|
(else #f)))
|
||||||
|
|
||||||
|
(define (update-dlna-next nr)
|
||||||
|
(set! current-track-nr nr)
|
||||||
|
(let ((next-nr (next-track-nr nr)))
|
||||||
|
(when (send dlna-player next-uri-supported?)
|
||||||
|
(if next-nr
|
||||||
|
(let ((track (send playlist track next-nr)))
|
||||||
|
(send dlna-player
|
||||||
|
set-next-file!
|
||||||
|
(send track get-file)
|
||||||
|
next-nr))
|
||||||
|
(send dlna-player clear-next!)))))
|
||||||
|
|
||||||
|
(define (dlna-track-changed nr)
|
||||||
|
(update-dlna-next nr))
|
||||||
|
|
||||||
|
(define (make-dlna-player renderer port)
|
||||||
|
(new dlna-player%
|
||||||
|
[renderer renderer]
|
||||||
|
[port port]
|
||||||
|
[time-updater time-updater]
|
||||||
|
[track-nr-updater
|
||||||
|
(lambda (nr)
|
||||||
|
(set! current-track-nr nr)
|
||||||
|
(track-nr-updater nr))]
|
||||||
|
[state-updater
|
||||||
|
(lambda (new-state)
|
||||||
|
(set! state new-state)
|
||||||
|
(state-updater new-state))]
|
||||||
|
[track-ended (lambda () (send this next))]
|
||||||
|
[track-changed dlna-track-changed]))
|
||||||
|
|
||||||
|
(define (stop-current-player)
|
||||||
|
(unless (eq? player #f)
|
||||||
|
(let ((old-player player))
|
||||||
|
(set! player #f)
|
||||||
|
(audio-quit! old-player)))
|
||||||
|
(unless (eq? dlna-player #f)
|
||||||
|
(let ((old-player dlna-player))
|
||||||
|
(send old-player quit)
|
||||||
|
(set! dlna-player #f))))
|
||||||
|
|
||||||
;(define ap (make-audio-player audio-player-state audio-player-eof
|
;(define ap (make-audio-player audio-player-state audio-player-eof
|
||||||
; #:remote-host "hans@mahler.thuis.local"
|
; #:remote-host "hans@mahler.thuis.local"
|
||||||
; #:replace-base-paths '(("\\\\panderleou\\music" . "/muziek"))))
|
; #:replace-base-paths '(("\\\\panderleou\\music" . "/muziek"))))
|
||||||
(define (check-player)
|
(define (check-player)
|
||||||
;(displayln "check-player called")
|
;(displayln "check-player called")
|
||||||
(when (eq? player #f)
|
(when (and (not (eq? player-kind 'dlna))
|
||||||
|
(eq? player #f))
|
||||||
(set! player
|
(set! player
|
||||||
(if (eq? player-kind 'local)
|
(if (eq? player-kind 'local)
|
||||||
(make-audio-player audio-state-cb on-eof-stream-cb)
|
(make-audio-player audio-state-cb on-eof-stream-cb)
|
||||||
@@ -98,36 +152,72 @@
|
|||||||
(audio-buf-seconds! player buffer-min-seconds buffer-max-seconds)
|
(audio-buf-seconds! player buffer-min-seconds buffer-max-seconds)
|
||||||
))
|
))
|
||||||
|
|
||||||
(define/public (change-player kind #:host [host #f] #:basepaths [basepaths #f])
|
(define/public (change-player kind
|
||||||
(let ((op player))
|
#:host [host #f]
|
||||||
(unless (eq? player #f)
|
#:basepaths [basepaths #f]
|
||||||
(set! player #f)
|
#:renderer [renderer #f]
|
||||||
(audio-quit! op)
|
#:port [port 8080])
|
||||||
;(displayln "Player quit")
|
(unless (member kind '(local remote dlna))
|
||||||
)
|
(raise-argument-error
|
||||||
;(displayln "HE!")
|
'change-player
|
||||||
(set! player-kind kind)
|
"(or/c 'local 'remote 'dlna)"
|
||||||
(set! player-host host)
|
kind))
|
||||||
(set! player-basepaths basepaths)
|
(when (and (eq? kind 'dlna) (not renderer))
|
||||||
;(displayln (format "kind: ~a, host: ~a, bp: ~a, player: ~a" player-kind player-host player-basepaths player))
|
(raise-arguments-error
|
||||||
))
|
'change-player
|
||||||
|
"a media renderer is required for DLNA playback"))
|
||||||
|
(stop-current-player)
|
||||||
|
(set! player-kind kind)
|
||||||
|
(set! player-host host)
|
||||||
|
(set! player-basepaths basepaths)
|
||||||
|
(set! current-track-nr #f)
|
||||||
|
(set! state 'stopped)
|
||||||
|
(state-updater state)
|
||||||
|
(if (eq? player-kind 'dlna)
|
||||||
|
(with-handlers
|
||||||
|
((exn:fail?
|
||||||
|
(lambda (exception)
|
||||||
|
(set! player-kind 'local)
|
||||||
|
(when dlna-player
|
||||||
|
(send dlna-player quit))
|
||||||
|
(set! dlna-player #f)
|
||||||
|
(raise exception))))
|
||||||
|
(set! dlna-player (make-dlna-player renderer port))
|
||||||
|
(audio-info-cb 0 0 0 'dlna))
|
||||||
|
(audio-info-cb 0 0 0 'none)))
|
||||||
|
|
||||||
|
(define/public (kind)
|
||||||
|
player-kind)
|
||||||
|
|
||||||
|
(define/public (query-dlna-renderers)
|
||||||
|
(query-media-renderers))
|
||||||
|
|
||||||
|
(define/public (dlna-renderer-name renderer)
|
||||||
|
(media-renderer-name renderer))
|
||||||
|
|
||||||
(define/public (get-volume)
|
(define/public (get-volume)
|
||||||
(check-player)
|
(check-player)
|
||||||
(audio-volume player))
|
(if (eq? player-kind 'dlna)
|
||||||
|
(send dlna-player volume)
|
||||||
|
(audio-volume player)))
|
||||||
|
|
||||||
(define/public (set-volume! percentage)
|
(define/public (set-volume! percentage)
|
||||||
(check-player)
|
(check-player)
|
||||||
(audio-volume! player percentage))
|
(if (eq? player-kind 'dlna)
|
||||||
|
(send dlna-player set-volume! percentage)
|
||||||
|
(audio-volume! player percentage)))
|
||||||
|
|
||||||
(define/public (set-list! playlist*)
|
(define/public (set-list! playlist*)
|
||||||
;; if the player exists and is playing, stop it.
|
;; if the player exists and is playing, stop it.
|
||||||
(unless (eq? player #f)
|
(unless (and (eq? player #f) (eq? dlna-player #f))
|
||||||
(audio-stop! player))
|
(if (eq? player-kind 'dlna)
|
||||||
|
(send dlna-player stop!)
|
||||||
|
(audio-stop! player)))
|
||||||
;; Set the playlist to the new one.
|
;; Set the playlist to the new one.
|
||||||
(set! playlist playlist*)
|
(set! playlist playlist*)
|
||||||
;; reset music-id to -1, because the playlist has been reset.
|
;; reset music-id to -1, because the playlist has been reset.
|
||||||
(set! music-id -1)
|
(set! music-id -1)
|
||||||
|
(set! current-track-nr #f)
|
||||||
;; clear lru cache, because the playlist has been reset.
|
;; clear lru cache, because the playlist has been reset.
|
||||||
(clear-music-ids!)
|
(clear-music-ids!)
|
||||||
)
|
)
|
||||||
@@ -141,83 +231,79 @@
|
|||||||
(check-player)
|
(check-player)
|
||||||
(when (and (>= nr 0) (< nr (send playlist length)))
|
(when (and (>= nr 0) (< nr (send playlist length)))
|
||||||
(let ((track (send playlist track nr)))
|
(let ((track (send playlist track nr)))
|
||||||
(let ((id (audio-play! player (send track get-file))))
|
(set! current-track-nr nr)
|
||||||
(register-music-id&track-nr id nr)))))
|
(if (eq? player-kind 'dlna)
|
||||||
|
(let ((next-nr (next-track-nr nr)))
|
||||||
|
(if (and next-nr
|
||||||
|
(send dlna-player next-uri-supported?))
|
||||||
|
(let ((next-track (send playlist track next-nr)))
|
||||||
|
(send dlna-player
|
||||||
|
play-file!
|
||||||
|
(send track get-file)
|
||||||
|
nr
|
||||||
|
#:next-file (send next-track get-file)
|
||||||
|
#:next-track-nr next-nr))
|
||||||
|
(send dlna-player
|
||||||
|
play-file!
|
||||||
|
(send track get-file)
|
||||||
|
nr)))
|
||||||
|
(let ((id (audio-play! player (send track get-file))))
|
||||||
|
(register-music-id&track-nr id nr))))))
|
||||||
|
|
||||||
(define/public (next)
|
(define/public (next)
|
||||||
(check-player)
|
(check-player)
|
||||||
(if (= music-id -1)
|
(if current-track-nr
|
||||||
(warn-rktplayer "No music-id set (yet), so can't play anything next")
|
(let ((nr (next-track-nr current-track-nr)))
|
||||||
(let ((track-nr (music-id->track-nr music-id)))
|
(if nr
|
||||||
(if (eq? track-nr #f)
|
(play-track nr)
|
||||||
(error "Unexpected: no track-nr for given music-id")
|
(stop)))
|
||||||
(begin
|
(warn-rktplayer
|
||||||
(cond
|
"No current track set, so can't play anything next")))
|
||||||
((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)
|
(define/public (previous)
|
||||||
(check-player)
|
(check-player)
|
||||||
(if (= music-id -1)
|
(if current-track-nr
|
||||||
(warn-rktplayer "No music-id set (yet), so can't play anything previous")
|
(let ((nr (if (eq? repeat 'repeat-one)
|
||||||
(let ((track-nr (music-id->track-nr music-id)))
|
current-track-nr
|
||||||
(if (eq? track-nr #f)
|
(- current-track-nr 1))))
|
||||||
(error "Unexpected: no track-nr for given music-id")
|
(when (< nr 0)
|
||||||
(begin
|
(set! nr
|
||||||
(cond
|
(if (eq? repeat 'repeat-all)
|
||||||
((eq? repeat 'repeat-one) (play-track track-nr))
|
(- (send playlist length) 1)
|
||||||
((eq? repeat 'repeat-all)
|
0)))
|
||||||
(set! track-nr (- track-nr 1))
|
(play-track nr))
|
||||||
(when (< track-nr 0)
|
(warn-rktplayer
|
||||||
(set! track-nr (- (send playlist length) 1)))
|
"No current track set, so can't play anything previous")))
|
||||||
(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!)
|
(define/public (pause!)
|
||||||
(check-player)
|
(check-player)
|
||||||
(audio-pause! player #t))
|
(if (eq? player-kind 'dlna)
|
||||||
|
(send dlna-player pause!)
|
||||||
|
(audio-pause! player #t)))
|
||||||
|
|
||||||
(define/public (play!)
|
(define/public (play!)
|
||||||
(check-player)
|
(check-player)
|
||||||
(audio-pause! player #f))
|
(if (eq? player-kind 'dlna)
|
||||||
|
(send dlna-player play!)
|
||||||
|
(audio-pause! player #f)))
|
||||||
|
|
||||||
(define/public (pause-unpause)
|
(define/public (pause-unpause)
|
||||||
(check-player)
|
(check-player)
|
||||||
(if (audio-paused? player)
|
(if (eq? state 'paused)
|
||||||
(send this pause!)
|
(send this play!)
|
||||||
(send this play!)))
|
(send this pause!)))
|
||||||
|
|
||||||
(define/public (stop)
|
(define/public (stop)
|
||||||
(check-player)
|
(check-player)
|
||||||
(audio-stop! player))
|
(if (eq? player-kind 'dlna)
|
||||||
|
(send dlna-player stop!)
|
||||||
|
(audio-stop! player)))
|
||||||
|
|
||||||
(define/public (seek percentage)
|
(define/public (seek percentage)
|
||||||
(check-player)
|
(check-player)
|
||||||
(audio-seek! player percentage))
|
(if (eq? player-kind 'dlna)
|
||||||
|
(send dlna-player seek! percentage)
|
||||||
|
(audio-seek! player percentage)))
|
||||||
|
|
||||||
(define/public (get-repeat)
|
(define/public (get-repeat)
|
||||||
(check-player)
|
(check-player)
|
||||||
@@ -225,11 +311,14 @@
|
|||||||
|
|
||||||
(define/public (repeat! r)
|
(define/public (repeat! r)
|
||||||
(check-player)
|
(check-player)
|
||||||
(set! repeat r))
|
(set! repeat r)
|
||||||
|
(repeat-updater repeat)
|
||||||
|
(when (and (eq? player-kind 'dlna)
|
||||||
|
current-track-nr)
|
||||||
|
(update-dlna-next current-track-nr)))
|
||||||
|
|
||||||
(define/public (quit)
|
(define/public (quit)
|
||||||
(unless (eq? player #f)
|
(stop-current-player))
|
||||||
(audio-quit! player)))
|
|
||||||
|
|
||||||
(super-new)
|
(super-new)
|
||||||
|
|
||||||
|
|||||||
@@ -139,6 +139,21 @@
|
|||||||
('file
|
('file
|
||||||
('en "File")
|
('en "File")
|
||||||
('nl "Bestand"))
|
('nl "Bestand"))
|
||||||
|
('player
|
||||||
|
('en "Player")
|
||||||
|
('nl "Speler"))
|
||||||
|
('local-player
|
||||||
|
('en "Local")
|
||||||
|
('nl "Lokaal"))
|
||||||
|
('find-dlna-renderers
|
||||||
|
('en "Find DLNA renderers")
|
||||||
|
('nl "Zoek DLNA-renderers"))
|
||||||
|
('finding-dlna-renderers
|
||||||
|
('en "Finding DLNA renderers...")
|
||||||
|
('nl "DLNA-renderers zoeken..."))
|
||||||
|
('dlna-error
|
||||||
|
('en "DLNA error")
|
||||||
|
('nl "DLNA-fout"))
|
||||||
('playing
|
('playing
|
||||||
('en "playing")
|
('en "playing")
|
||||||
('nl "speelt"))
|
('nl "speelt"))
|
||||||
|
|||||||
Reference in New Issue
Block a user