Added functions to be able to play on more renderers.

This commit is contained in:
2026-08-08 14:22:21 +02:00
parent f026e51bd1
commit cc6757d0f6
4 changed files with 201 additions and 31 deletions
+1
View File
@@ -15,3 +15,4 @@ compiled/
# Dependency tracking files # Dependency tracking files
*.dep *.dep
/doc
+167 -31
View File
@@ -15,7 +15,9 @@
(provide make-dlna-player (provide make-dlna-player
dlna-player? dlna-player?
dlna-player-play! dlna-player-play!
dlna-player-play-uri!
dlna-player-set-next-file! dlna-player-set-next-file!
dlna-player-set-next-uri!
dlna-player-pause! dlna-player-pause!
dlna-player-resume! dlna-player-resume!
dlna-player-stop! dlna-player-stop!
@@ -39,7 +41,7 @@
#:transparent) #:transparent)
(struct publication (struct publication
(track uri metadata) (track uri metadata owned? protocol-info mime-type)
#:transparent) #:transparent)
(struct dlna-player (struct dlna-player
@@ -214,10 +216,14 @@
#:genre (dlna-track-info-genre track) #:genre (dlna-track-info-genre track)
#:year (dlna-track-info-year track) #:year (dlna-track-info-year track)
#:track (dlna-track-info-track track) #:track (dlna-track-info-track track)
#:duration (dlna-track-info-duration track)))))) #:duration (dlna-track-info-duration track))
#t
#f
#f))))
(define (safe-unpublish! player item) (define (safe-unpublish! player item)
(when item (when (and item
(publication-owned? item))
(with-handlers ([exn:fail? (lambda (exception) (with-handlers ([exn:fail? (lambda (exception)
(warn-dlna-player "Could not unpublish ~a: ~a" (publication-uri item) (exn-message exception)) (warn-dlna-player "Could not unpublish ~a: ~a" (publication-uri item) (exn-message exception))
(void))]) (void))])
@@ -271,7 +277,9 @@
([exn:fail? ([exn:fail?
(lambda (exception) (lambda (exception)
(err-dlna-player "Renderer operation failed: ~a" (exn-message exception)) (err-dlna-player "Renderer operation failed: ~a" (exn-message exception))
(set-reachable! player #f) (set-reachable!
player
(exn:fail:upnp? exception))
(raise exception))]) (raise exception))])
(let ([result (let ([result
(call-with-semaphore (call-with-semaphore
@@ -339,7 +347,11 @@
([exn:fail? ([exn:fail?
(lambda (exception) (lambda (exception)
(warn-dlna-player "Could not refresh renderer state: ~a" (exn-message exception)) (warn-dlna-player "Could not refresh renderer state: ~a" (exn-message exception))
(set-reachable! player #f) ;; A SOAP/UPnP error is still a valid response from the renderer.
;; Only transport failures mean that it is unreachable.
(set-reachable!
player
(exn:fail:upnp? exception))
#f)]) #f)])
(call-with-semaphore (call-with-semaphore
(dlna-player-transport-lock player) (dlna-player-transport-lock player)
@@ -479,11 +491,8 @@
(thread (lambda () (poll-player player)))) (thread (lambda () (poll-player player))))
player)) player))
(define (dlna-player-play! player file) (define (play-publication! player item)
(info-dlna-player "Play requested file=~a" file) (let ((track (publication-track item)))
(check-player 'dlna-player-play! player)
(let* ([track (player-track-info 'dlna-player-play! player file)]
[item (publish-track! player track)])
(with-handlers (with-handlers
([exn:fail? ([exn:fail?
(lambda (exception) (lambda (exception)
@@ -492,10 +501,33 @@
(call-renderer (call-renderer
player player
(lambda () (lambda ()
(media-renderer-play-uri! (if (or (publication-protocol-info item)
(dlna-player-renderer player) (publication-mime-type item))
(publication-uri item) (media-renderer-play-resource!
#:metadata (publication-metadata item)))) (dlna-player-renderer player)
(publication-uri item)
#:protocol-info (publication-protocol-info item)
#:mime-type (publication-mime-type item)
#:title
(if track
(dlna-track-info-title track)
"Media")
#:artist
(and track (dlna-track-info-artist track))
#:album
(and track (dlna-track-info-album track))
#:genre
(and track (dlna-track-info-genre track))
#:year
(and track (dlna-track-info-year track))
#:track
(and track (dlna-track-info-track track))
#:duration
(and track (dlna-track-info-duration track)))
(media-renderer-play-uri!
(dlna-player-renderer player)
(publication-uri item)
#:metadata (publication-metadata item)))))
(let ([old-current #f] (let ([old-current #f]
[old-next #f]) [old-next #f])
(call-with-semaphore (call-with-semaphore
@@ -514,7 +546,8 @@
#f #f
#f #f
0 0
(dlna-track-info-duration track) (and track
(dlna-track-info-duration track))
(dlna-info-volume (dlna-player-cached-info player)) (dlna-info-volume (dlna-player-cached-info player))
(dlna-info-muted? (dlna-player-cached-info player)) (dlna-info-muted? (dlna-player-cached-info player))
#t)) #t))
@@ -523,15 +556,44 @@
(safe-unpublish! player old-next)) (safe-unpublish! player old-next))
(void)))) (void))))
(define (dlna-player-set-next-file! player file) (define (dlna-player-play! player file)
(info-dlna-player "Set next track requested file=~a" file) (info-dlna-player "Play requested file=~a" file)
(check-player 'dlna-player-set-next-file! player) (check-player 'dlna-player-play! player)
(let* ([track (let* ([track (player-track-info 'dlna-player-play! player file)]
(player-track-info
'dlna-player-set-next-file!
player
file)]
[item (publish-track! player track)]) [item (publish-track! player track)])
(play-publication! player item)))
(define (dlna-player-play-uri! player uri
#:metadata [metadata ""]
#:protocol-info [protocol-info #f]
#:mime-type [mime-type #f]
#:track [track #f])
(info-dlna-player "Play requested URI=~a" uri)
(check-player 'dlna-player-play-uri! player)
(unless (and (string? uri)
(not (string=? (string-trim uri) "")))
(raise-argument-error
'dlna-player-play-uri!
"non-empty-string?"
uri))
(unless (or (not track)
(dlna-track-info? track))
(raise-argument-error
'dlna-player-play-uri!
"(or/c #f dlna-track-info?)"
track))
(for ([value (in-list (list protocol-info mime-type))])
(unless (or (not value) (string? value))
(raise-argument-error
'dlna-player-play-uri!
"(or/c #f string?)"
value)))
(play-publication!
player
(publication track uri metadata #f protocol-info mime-type)))
(define (set-next-publication! player item)
(let ((track (publication-track item)))
(with-handlers (with-handlers
([exn:fail? ([exn:fail?
(lambda (exception) (lambda (exception)
@@ -540,10 +602,33 @@
(call-renderer (call-renderer
player player
(lambda () (lambda ()
(media-renderer-set-next-uri! (if (or (publication-protocol-info item)
(dlna-player-renderer player) (publication-mime-type item))
(publication-uri item) (media-renderer-set-next-resource!
#:metadata (publication-metadata item)))) (dlna-player-renderer player)
(publication-uri item)
#:protocol-info (publication-protocol-info item)
#:mime-type (publication-mime-type item)
#:title
(if track
(dlna-track-info-title track)
"Media")
#:artist
(and track (dlna-track-info-artist track))
#:album
(and track (dlna-track-info-album track))
#:genre
(and track (dlna-track-info-genre track))
#:year
(and track (dlna-track-info-year track))
#:track
(and track (dlna-track-info-track track))
#:duration
(and track (dlna-track-info-duration track)))
(media-renderer-set-next-uri!
(dlna-player-renderer player)
(publication-uri item)
#:metadata (publication-metadata item)))))
(let ([old-next #f]) (let ([old-next #f])
(call-with-semaphore (call-with-semaphore
(dlna-player-cache-lock player) (dlna-player-cache-lock player)
@@ -560,6 +645,46 @@
(safe-unpublish! player old-next)) (safe-unpublish! player old-next))
(void)))) (void))))
(define (dlna-player-set-next-file! player file)
(info-dlna-player "Set next track requested file=~a" file)
(check-player 'dlna-player-set-next-file! player)
(let* ([track
(player-track-info
'dlna-player-set-next-file!
player
file)]
[item (publish-track! player track)])
(set-next-publication! player item)))
(define (dlna-player-set-next-uri! player uri
#:metadata [metadata ""]
#:protocol-info [protocol-info #f]
#:mime-type [mime-type #f]
#:track [track #f])
(info-dlna-player "Set next track requested URI=~a" uri)
(check-player 'dlna-player-set-next-uri! player)
(unless (and (string? uri)
(not (string=? (string-trim uri) "")))
(raise-argument-error
'dlna-player-set-next-uri!
"non-empty-string?"
uri))
(unless (or (not track)
(dlna-track-info? track))
(raise-argument-error
'dlna-player-set-next-uri!
"(or/c #f dlna-track-info?)"
track))
(for ([value (in-list (list protocol-info mime-type))])
(unless (or (not value) (string? value))
(raise-argument-error
'dlna-player-set-next-uri!
"(or/c #f string?)"
value)))
(set-next-publication!
player
(publication track uri metadata #f protocol-info mime-type)))
(define (dlna-player-pause! player) (define (dlna-player-pause! player)
(info-dlna-player "Pause requested") (info-dlna-player "Pause requested")
(check-player 'dlna-player-pause! player) (check-player 'dlna-player-pause! player)
@@ -595,10 +720,21 @@
(define (dlna-player-stop! player) (define (dlna-player-stop! player)
(info-dlna-player "Stop requested") (info-dlna-player "Stop requested")
(check-player 'dlna-player-stop! player) (check-player 'dlna-player-stop! player)
(call-renderer (with-handlers
player ([exn:fail:upnp?
(lambda () (lambda (exception)
(media-renderer-stop! (dlna-player-renderer player)))) (unless
(equal?
(exn:fail:upnp-code exception)
"701")
(raise exception))
(dbg-dlna-player
"Renderer was already in a state where Stop is unavailable"))])
(call-renderer
player
(lambda ()
(media-renderer-stop!
(dlna-player-renderer player)))))
(update-cache! (update-cache!
player player
(lambda (info) (lambda (info)
+31
View File
@@ -60,6 +60,37 @@ Publishes and preloads the file with @tt{SetNextAVTransportURI}. Replacing the
next file removes the previous next-file publication. next file removes the previous next-file publication.
} }
@defproc[(dlna-player-play-uri!
[player dlna-player?]
[uri string?]
[#:metadata metadata string? ""]
[#:protocol-info protocol-info (or/c #f string?) #f]
[#:mime-type mime-type (or/c #f string?) #f]
[#:track track (or/c #f dlna-track-info?) #f])
void?]{
Installs an existing HTTP media URI directly on the renderer and starts
playback. The URI is not published or owned by this player. Optional
DIDL-Lite metadata and track information are retained in the player state.
When @racket[protocol-info] or @racket[mime-type] is supplied, the resource
capabilities are resolved by @racketmodname[racket-upnp] immediately before
playback and matching DIDL-Lite is generated automatically. In that case
@racket[metadata] is ignored.
}
@defproc[(dlna-player-set-next-uri!
[player dlna-player?]
[uri string?]
[#:metadata metadata string? ""]
[#:protocol-info protocol-info (or/c #f string?) #f]
[#:mime-type mime-type (or/c #f string?) #f]
[#:track track (or/c #f dlna-track-info?) #f])
void?]{
Preloads an existing HTTP media URI with @tt{SetNextAVTransportURI}. Replacing
it never attempts to remove the external resource.
}
@defproc[(dlna-player-pause! [player dlna-player?]) void?] @defproc[(dlna-player-pause! [player dlna-player?]) void?]
@defproc[(dlna-player-resume! [player dlna-player?]) void?] @defproc[(dlna-player-resume! [player dlna-player?]) void?]
@defproc[(dlna-player-stop! [player dlna-player?]) void?] @defproc[(dlna-player-stop! [player dlna-player?]) void?]
+2
View File
@@ -38,6 +38,8 @@
(check-equal? (dlna-info-position info) 12) (check-equal? (dlna-info-position info) 12)
(check-equal? (dlna-info-volume info) 35) (check-equal? (dlna-info-volume info) 35)
(check-true (dlna-info-reachable? info)) (check-true (dlna-info-reachable? info))
(check-true (procedure? dlna-player-play-uri!))
(check-true (procedure? dlna-player-set-next-uri!))
(check-exn exn:fail:contract? (check-exn exn:fail:contract?
(lambda () (make-dlna-player #f))) (lambda () (make-dlna-player #f)))