small adjustments, many enhancements to rktplayer
This commit is contained in:
218
gui.rkt
218
gui.rkt
@@ -4,6 +4,8 @@
|
||||
racket/runtime-path
|
||||
racket/gui
|
||||
racket-sprintf
|
||||
open-app
|
||||
xml
|
||||
"utils.rkt"
|
||||
"music-library.rkt"
|
||||
"translate.rkt"
|
||||
@@ -24,27 +26,37 @@
|
||||
(wv-menu 'main-menu
|
||||
(wv-menu-item 'm-file (tr "File")
|
||||
#:submenu
|
||||
(wv-menu (wv-menu-item 'm-select-library-dir (tr "Select Music Library Folder"))
|
||||
(wv-menu-item 'm-set-lang (tr "Set language"))
|
||||
(wv-menu-item 'm-quit (tr "Quit") #:separator #t)))
|
||||
(wv-menu
|
||||
(wv-menu-item 'm-add-tab (tr "Add Playlist"))
|
||||
(wv-menu-item 'm-select-library-dir (tr "Select Music Library Folder"))
|
||||
(wv-menu-item 'm-set-lang (tr "Set language"))
|
||||
(wv-menu-item 'm-quit (tr "Quit") #:separator #t)))
|
||||
)
|
||||
))
|
||||
|
||||
(define rktplayer%
|
||||
(class wv-window%
|
||||
(inherit-field settings icon)
|
||||
|
||||
(super-new
|
||||
[html-path "rktplayer.html"]
|
||||
[title "Racket Music Player"]
|
||||
[icon (build-path rkt-gui-dir "rktplayer.png")]
|
||||
)
|
||||
|
||||
(define initialized (make-semaphore 0))
|
||||
|
||||
(define closed #f)
|
||||
(define el-seeker #f)
|
||||
(define el-library #f)
|
||||
(define el-playlist #f)
|
||||
(define el-at #f)
|
||||
(define el-length #f)
|
||||
(define el-rate #f)
|
||||
(define el-channels #f)
|
||||
(define el-bits #f)
|
||||
|
||||
(define current-tab 0)
|
||||
|
||||
(define music-library
|
||||
(let ((path (format "~a" (send settings get 'music-library (find-system-path 'home-dir)))))
|
||||
@@ -123,7 +135,7 @@
|
||||
(dbg-rktplayer "Setting album art")
|
||||
(let ((el (send this element 'album-art)))
|
||||
(let ((html (format "<img src=\"/get-image?~a&~a\" />"
|
||||
(string-replace (format "~a" stored-file) "\\" "/")
|
||||
(format "~a" stored-file)
|
||||
(current-milliseconds))))
|
||||
(dbg-rktplayer "Html = ~a" html)
|
||||
(send el set-innerHTML! html)
|
||||
@@ -148,32 +160,163 @@
|
||||
)
|
||||
|
||||
(define (update-state st)
|
||||
(dbg-rktplayer "state: ~a" st)
|
||||
(unless (eq? st state)
|
||||
(dbg-rktplayer "Changing to state ~a" st)
|
||||
(unless (eq? state #f) ; Prevent setting src twice very fast
|
||||
(if (eq? st 'playing)
|
||||
(set-play-button "buttons/stop.svg")
|
||||
(set-play-button "buttons/pause.svg")
|
||||
(set-play-button "buttons/play.svg")
|
||||
)
|
||||
)
|
||||
(set! state st)
|
||||
)
|
||||
)
|
||||
|
||||
(define/public (update-tabs)
|
||||
(displayln (format "playlist = ~a" playlist))
|
||||
(let* ((tabs (send playlist tab-count))
|
||||
(html "")
|
||||
(tab-el (send this element 'tabs))
|
||||
(idx 0)
|
||||
)
|
||||
(while (< idx tabs)
|
||||
(let ((tab-name (send playlist get-tab-name idx)))
|
||||
(set! html (string-append
|
||||
html
|
||||
(xexpr->string
|
||||
(list 'span (list (list 'id (format "tab~a" idx))
|
||||
'(class "tab"))
|
||||
tab-name))))
|
||||
)
|
||||
(set! idx (+ idx 1)))
|
||||
|
||||
(send tab-el set-innerHTML! html)
|
||||
|
||||
(send this bind! "#tabs > span" 'click
|
||||
(λ (el evt data)
|
||||
(let* ((tab-id (send el id))
|
||||
(tab-idx (string->number (substring (format "~a" tab-id) 3)))
|
||||
)
|
||||
(send this set-tab! tab-idx))))
|
||||
|
||||
(send this bind! "#tabs > span" 'contextmenu
|
||||
(λ (el evt data)
|
||||
(let* ((tab-id (send el id))
|
||||
(tab-idx (string->number (substring (format "~a" tab-id) 3)))
|
||||
)
|
||||
(send this tab-context data tab-id tab-idx))))
|
||||
|
||||
(let ((id (string->symbol (format "tab~a" current-tab))))
|
||||
(let ((el (send this element id)))
|
||||
(send el add-class! 'current))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(define/public (tab-context evt tab-id tab-idx)
|
||||
(let ((items (list
|
||||
(wv-menu-item 'm-tab-rename (tr "Rename playlist") #:callback (λ () (send this rename-tab! tab-id tab-idx)))
|
||||
(wv-menu-item 'm-tab-drop (tr "Remove playlist") #:callback (λ () (send this drop-tab! tab-id tab-idx)))
|
||||
(wv-menu-item 'm-tab-add (tr "Add playlist") #:callback (λ () (send this add-tab)))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(let* ((mnu (wv-menu 'tab-popup items))
|
||||
(clientX (hash-ref evt 'clientX 60))
|
||||
(clientY (hash-ref evt 'clientY 60))
|
||||
)
|
||||
(send this popup-menu! mnu clientX clientY)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(define/public (drop-tab! tab-id tab-idx)
|
||||
(when (= current-tab tab-idx)
|
||||
(send this stop))
|
||||
(send playlist drop-tab! tab-idx)
|
||||
(send this set-tab! 0)
|
||||
)
|
||||
|
||||
(define/public (rename-tab! tab-id tab-idx)
|
||||
(let* ((inp-id (string->symbol (format "tab-input~a" tab-idx)))
|
||||
(tab-el-id (string->symbol (format "tab~a" tab-idx)))
|
||||
(html (list 'input (list (list 'id (format "~a" inp-id))
|
||||
(list 'name (format "~a" inp-id))
|
||||
'(type "text")
|
||||
(list 'value (send playlist get-tab-name tab-idx))
|
||||
)))
|
||||
(tab-el (send this element tab-el-id))
|
||||
(unbind-events (λ ()
|
||||
(send this unbind! inp-id 'change)
|
||||
(send this unbind! inp-id 'blur)))
|
||||
)
|
||||
(send tab-el set-innerHTML! html)
|
||||
(send this unbind! tab-el-id '(click contextmenu))
|
||||
(send this bind! inp-id 'change
|
||||
(λ (el evt data)
|
||||
(let ((tab-name (hash-ref data 'value (send playlist get-tab-name tab-idx))))
|
||||
(send playlist set-tab-name! tab-idx tab-name)
|
||||
(unbind-events)
|
||||
(send this update-tabs))))
|
||||
(send this bind! inp-id 'blur
|
||||
(λ (el evt data)
|
||||
(unbind-events)
|
||||
(send this update-tabs)))
|
||||
(let ((inp-el (send this element inp-id)))
|
||||
(send inp-el focus!))
|
||||
)
|
||||
)
|
||||
|
||||
(define/public (set-tab! tab-idx)
|
||||
(send this stop)
|
||||
(set! current-tab tab-idx)
|
||||
(send playlist load-tab tab-idx)
|
||||
(send this update-tabs)
|
||||
(send this update-playlist)
|
||||
)
|
||||
|
||||
(define/public (add-tab)
|
||||
(send playlist add-tab!)
|
||||
(send this update-tabs))
|
||||
|
||||
(define (update-audio-info samples rate channels bits)
|
||||
(send el-bits set-innerHTML! (format "~a ~a" bits (tr "bits")))
|
||||
(send el-channels set-innerHTML! (format "~a ~a" channels (tr "channels")))
|
||||
(send el-rate set-innerHTML! (format "~a Hz" rate))
|
||||
)
|
||||
|
||||
(define (update-repeat state)
|
||||
(let ((img (if (eq? state 'no-repeat)
|
||||
"buttons/repeat-off.svg"
|
||||
(if (eq? state 'repeat-one)
|
||||
"buttons/repeat-one.svg"
|
||||
"buttons/repeat.svg"))))
|
||||
(let ((el (send this element 'repeat-img)))
|
||||
(send el set-attr! (list 'src img)))
|
||||
)
|
||||
)
|
||||
|
||||
(define player (new player%
|
||||
[time-updater update-time]
|
||||
[track-nr-updater update-track-nr]
|
||||
[state-updater update-state]
|
||||
[repeat-updater update-repeat]
|
||||
[audio-info-cb update-audio-info]
|
||||
[settings settings]
|
||||
))
|
||||
|
||||
(define inner-html-handlers (make-hash))
|
||||
|
||||
(define/override (page-loaded oke)
|
||||
(semaphore-wait initialized)
|
||||
(semaphore-post initialized)
|
||||
|
||||
(super page-loaded oke)
|
||||
|
||||
(ww-connect 'play play-or-stop)
|
||||
(ww-connect 'pause pause)
|
||||
(ww-connect 'play play-or-pause)
|
||||
(ww-connect 'stop stop)
|
||||
(ww-connect 'prev previous-track)
|
||||
(ww-connect 'next next-track)
|
||||
(ww-connect 'repeat repeat)
|
||||
@@ -190,11 +333,19 @@
|
||||
(set! el-at (send this element 'time))
|
||||
(set! el-length (send this element 'totaltime))
|
||||
|
||||
(set! el-rate (send this element 'rate))
|
||||
(set! el-bits (send this element 'bits))
|
||||
(set! el-channels (send this element 'channels))
|
||||
|
||||
(send this set-menu! (player-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-add-tab (λ () (send this add-tab)))
|
||||
|
||||
(displayln (format "page-loaded, playlist = ~a" playlist))
|
||||
(send this update-tabs)
|
||||
(send this update-library)
|
||||
(send this update-playlist)
|
||||
)
|
||||
|
||||
|
||||
@@ -284,9 +435,14 @@
|
||||
(set! items (append items
|
||||
(list
|
||||
(wv-menu-item 'm-add-this (tr "Add this") #:callback (λ () (send this add-path path)))))))
|
||||
(when (file-exists? (build-path path "booklet.pdf"))
|
||||
(set! items (append items
|
||||
(list
|
||||
(wv-menu-item 'm-booklet (tr "Open booklet") #:callback (λ () (send this open-booklet path))) ;; todo check if pdf file exists
|
||||
))))
|
||||
|
||||
(set! items (append items
|
||||
(list
|
||||
(wv-menu-item 'm-booklet (tr "Open booklet") #:callback (λ () (send this open-booklet path))) ;; todo check if pdf file exists
|
||||
(wv-menu-item 'm-folder (tr "Open containing folder") #:callback (λ () (send this open-folder path)))
|
||||
)))
|
||||
(let* ((mnu (wv-menu 'library-popup items))
|
||||
@@ -300,7 +456,7 @@
|
||||
|
||||
(define/public (play-path path)
|
||||
(dbg-rktplayer "Playing ~a" path)
|
||||
(let ((pl (new playlist% [start-map path])))
|
||||
(let ((pl (new playlist% [start-map path] [settings (send settings clone 'playlists)] [id current-tab])))
|
||||
(set! current-track-nr #f)
|
||||
(send pl read-tracks)
|
||||
(set! playlist pl)
|
||||
@@ -316,21 +472,32 @@
|
||||
)
|
||||
|
||||
(define/public (open-booklet path)
|
||||
(dbg-rktplayer "Open booklet ~a" path))
|
||||
(let ((booklet (build-path path "booklet.pdf")))
|
||||
(dbg-rktplayer "Open booklet ~a" path)
|
||||
(open-app booklet)))
|
||||
|
||||
(define/public (open-folder path)
|
||||
(dbg-rktplayer "path: ~a" path)
|
||||
(let ((folder (if (file-exists? path) (path-only path) path)))
|
||||
(open-file-manager folder)))
|
||||
(open-file-manager path))
|
||||
;(let ((folder (if (file-exists? path) (path-only path) path)))
|
||||
; (open-file-manager folder)))
|
||||
|
||||
(define/public (play-or-stop)
|
||||
(if (eq? state 'playing)
|
||||
(begin
|
||||
(send player stop)
|
||||
(update-time 0.0 0.0))
|
||||
(send player play-track current-track-nr))
|
||||
(define/public (play-or-pause)
|
||||
(cond
|
||||
((eq? state 'playing)
|
||||
(send player pause!))
|
||||
((eq? state 'pauzed)
|
||||
(send player play!))
|
||||
(else
|
||||
(play-track 0))
|
||||
)
|
||||
)
|
||||
|
||||
(define/public (stop)
|
||||
(dbg-rktplayer "Stop")
|
||||
(send player stop)
|
||||
(update-track-nr #f))
|
||||
|
||||
(define/public (play-track idx)
|
||||
(send player play-track idx))
|
||||
|
||||
@@ -346,7 +513,14 @@
|
||||
)
|
||||
|
||||
(define/public (repeat)
|
||||
(dbg-rktplayer "Repeat")
|
||||
(let ((r (send player get-repeat)))
|
||||
(let ((nr (cond
|
||||
((eq? r 'no-repeat) 'repeat-all)
|
||||
((eq? r 'repeat-all) 'repeat-one)
|
||||
(else 'no-repeat))))
|
||||
(send player repeat! nr)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(define/public (volume)
|
||||
@@ -355,6 +529,7 @@
|
||||
|
||||
(define/public (seek-to percentage)
|
||||
(dbg-rktplayer "Seeking to percentage: ~a" percentage)
|
||||
(send player seek percentage)
|
||||
)
|
||||
|
||||
(define/public (quit)
|
||||
@@ -381,9 +556,14 @@
|
||||
)
|
||||
|
||||
(begin
|
||||
(displayln "Initalizing gui")
|
||||
(dbg-rktplayer "ICON: ~a" (get-field icon this))
|
||||
(let ((lang (send settings get 'lang 'en)))
|
||||
(dbg-rktplayer "RktPlayer started, current language: ~a" lang))
|
||||
(set! playlist (new playlist% [settings (send settings clone 'playlists)]))
|
||||
(send player set-list! playlist)
|
||||
(displayln (format "playlist = ~a" playlist))
|
||||
(semaphore-post initialized)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
<div class="pane">
|
||||
<div class="buttons">
|
||||
<button id="prev" class="command"><img id="previous-img" src="buttons/previous.svg" /></button>
|
||||
<button id="pause" class="command"><img id="pause-img" src="buttons/pause.svg" /></button>
|
||||
<button id="play" class="command"><img id="play-img" src="buttons/play.svg" /></button>
|
||||
<button id="stop" class="command"><img id="stop-img" src="buttons/stop.svg" /></button>
|
||||
<button id="next" class="command"><img id="next-img" src="buttons/next.svg" /></button>
|
||||
<input type="range" min="0" max="100" value="0" class="h-slider" id="seek" step="0.01" />
|
||||
<div class="text-block"><span id="time" class="time">00:00:00</span></div>
|
||||
@@ -34,11 +34,17 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="music-playing">
|
||||
<div id="tabs" class="tabs">
|
||||
<span id="tab1" class="tab">Default</span>
|
||||
</div>
|
||||
<div id="tracks" class="content scrolly">
|
||||
<!-- Music playing -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status">
|
||||
<span class="info" id="bits"></span><span class="info" id="rate"></span><span class="info" id="channels"></span>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,7 +6,7 @@ body {
|
||||
}
|
||||
|
||||
.pane {
|
||||
height: calc(100vh - 40px - 2em - 10px);
|
||||
height: calc(100vh - 2em - 20px);
|
||||
width: calc(100% - 10px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -31,6 +31,7 @@ button {
|
||||
|
||||
button:hover {
|
||||
background: #909090;
|
||||
transition: all 0.5s ease-in;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +70,7 @@ input.h-slider {
|
||||
}
|
||||
|
||||
.hpane {
|
||||
height: 100%;
|
||||
height: calc(100% - 40px - 2.5em);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
@@ -99,12 +100,45 @@ input.h-slider {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
width: 100%;
|
||||
height: 1.5em;
|
||||
border-bottom: 1px solid #505050;
|
||||
}
|
||||
|
||||
.tabs span.tab {
|
||||
width: 50px;
|
||||
overflow: hide;
|
||||
text-overflow: hide;
|
||||
white-space: nowrap;
|
||||
border: 1px solid #505050;
|
||||
cursor: default;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.tabs span.tab.current {
|
||||
font-weight: bold;
|
||||
color: #f3961e;
|
||||
}
|
||||
|
||||
.tabs span.tab:hover {
|
||||
background: #e0e0e0;
|
||||
color: black;
|
||||
transition: all 0.5s ease-in;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.music-playing .content {
|
||||
height: calc(100% - 1.5em);
|
||||
}
|
||||
|
||||
.scrolly {
|
||||
overflow-y: auto;
|
||||
}
|
||||
@@ -127,6 +161,7 @@ table.music-library tr td {
|
||||
table.music-library tr td:hover {
|
||||
background: #e0e0e0;
|
||||
color: black;
|
||||
transition: all 0.5s ease-in;
|
||||
}
|
||||
|
||||
.popup-menu, .popup-submenu {
|
||||
@@ -195,6 +230,7 @@ table.tracks tr, table.tracks td {
|
||||
table.tracks tr:hover {
|
||||
background: #e0e0e0;
|
||||
color: black;
|
||||
transition: all 0.5s ease-in;
|
||||
}
|
||||
|
||||
table.tracks tr:hover.current {
|
||||
@@ -218,4 +254,18 @@ table.tracks tr.current {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
div.status {
|
||||
width: 100%;
|
||||
height: 2em;
|
||||
padding-top: 0.25em;
|
||||
border-left: 1px solid #505050;
|
||||
}
|
||||
|
||||
div.status span.info {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
border-right: 1px solid #505050;
|
||||
color: #d0d0d0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
96
player.rkt
96
player.rkt
@@ -15,6 +15,8 @@
|
||||
[time-updater (λ (time-s length-s) #t)]
|
||||
[track-nr-updater (λ (nr) #t)]
|
||||
[state-updater (λ (state) #t)]
|
||||
[repeat-updater (λ (state) #t)]
|
||||
[audio-info-cb (λ (current-sample rate channels bits) #t)]
|
||||
[buffer-max-seconds 10]
|
||||
[buffer-min-seconds 4]
|
||||
)
|
||||
@@ -27,9 +29,13 @@
|
||||
(define ct-data #f)
|
||||
(define closing #f)
|
||||
(define pause #f)
|
||||
(define repeat-state 'no-repeat)
|
||||
|
||||
(define ao-handle #f)
|
||||
(define flac-handle #f)
|
||||
|
||||
(define current-music-id -1)
|
||||
(define current-track-id -1)
|
||||
|
||||
(define current-rate 0)
|
||||
(define current-bits 0)
|
||||
@@ -53,10 +59,9 @@
|
||||
current-rate current-bits current-channels ao-handle)
|
||||
(dbg-rktplayer "Opening ao-handle")
|
||||
(when use-ao
|
||||
(let ((fmt (ao-mk-format current-bits current-rate current-channels 'big-endian)))
|
||||
(set! ao-handle (ao-open-live #f fmt))
|
||||
(start-play-time-updater)
|
||||
))
|
||||
(set! ao-handle (ao-open-live current-bits current-rate current-channels 'big-endian))
|
||||
(start-play-time-updater)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -74,10 +79,15 @@
|
||||
'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)
|
||||
(sleep 0.1)
|
||||
(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)
|
||||
)
|
||||
@@ -85,6 +95,12 @@
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
(define (stream-equal? rate bits channels)
|
||||
(and (= current-rate rate)
|
||||
(= current-bits bits)
|
||||
(= current-channels channels)))
|
||||
|
||||
(define (flac-play frame buffer buf-len)
|
||||
(unless (eq? state 'quitted)
|
||||
(let* ((sample (hash-ref frame 'number))
|
||||
@@ -96,10 +112,27 @@
|
||||
(bytes-per-sample-all-channels (* channels bytes-per-sample))
|
||||
(duration (hash-ref frame '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")
|
||||
(while (> (ao-bufsize-async ao-handle) 0)
|
||||
(sleep 0.25)
|
||||
)
|
||||
(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)
|
||||
)
|
||||
|
||||
(check-ao-handle)
|
||||
(when (not (eq? ao-handle #f))
|
||||
@@ -115,19 +148,21 @@
|
||||
(sleep 0.25))))
|
||||
|
||||
(when (not (eq? ao-handle #f))
|
||||
(ao-play ao-handle second duration buffer buf-len 'flac)
|
||||
(ao-play ao-handle current-track-id second duration buffer buf-len 'flac)
|
||||
)
|
||||
)
|
||||
|
||||
(when pause
|
||||
(dbg-rktplayer "Pauzing now...")
|
||||
(dbg-rktplayer "Pausing now...")
|
||||
(set-state! 'pauzed)
|
||||
(ao-pause ao-handle #t)
|
||||
(while (and (not (eq? ao-handle #f))
|
||||
(not closing)
|
||||
pause)
|
||||
(sleep 0.25))
|
||||
(sleep 0.5))
|
||||
(ao-pause ao-handle #f)
|
||||
(dbg-rktplayer "Playing on...")
|
||||
(set-state! 'playing)
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -144,6 +179,7 @@
|
||||
(let ((file (send ct-data get-file)))
|
||||
(dbg-rktplayer "opening flac handle for file: ~a" file)
|
||||
(set! flac-handle (flac-open file flac-meta flac-play))
|
||||
(set! current-track-id (send ct-data get-id))
|
||||
(dbg-rktplayer "Starting flac-read")
|
||||
(let ((result (flac-read flac-handle)))
|
||||
(if (eq? result 'end-of-stream)
|
||||
@@ -202,7 +238,13 @@
|
||||
)
|
||||
|
||||
(define/public (next-track)
|
||||
(set! track (+ track 1))
|
||||
(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)
|
||||
@@ -210,7 +252,7 @@
|
||||
(begin
|
||||
(set! ct-data (send pl track track))
|
||||
(set-state! 'play)
|
||||
(track-nr-updater track)
|
||||
;(track-nr-updater track)
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -256,6 +298,24 @@
|
||||
(set! pause (not pause))
|
||||
(dbg-rktplayer "pauzed: ~a" pause)
|
||||
)
|
||||
|
||||
(define/public (pause!)
|
||||
(set! pause #t))
|
||||
|
||||
(define/public (play!)
|
||||
(set! pause #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 (seek percentage)
|
||||
(ao-clear-async ao-handle)
|
||||
(flac-seek flac-handle percentage))
|
||||
|
||||
(define (state-machine)
|
||||
(let ((st (orig-current-seconds))
|
||||
@@ -268,15 +328,17 @@
|
||||
(begin
|
||||
(cond
|
||||
((eq? state 'stopped)
|
||||
(sleep 0.01))
|
||||
(sleep 0.1))
|
||||
((eq? state 'play)
|
||||
(if (eq? pl #f)
|
||||
(set-state! 'stoppped)
|
||||
(play-track-worker)))
|
||||
((eq? state 'playing)
|
||||
(sleep 0.01))
|
||||
(sleep 0.1))
|
||||
((eq? state 'track-feeded)
|
||||
(send this next-track))
|
||||
(else
|
||||
(sleep 0.1))
|
||||
)
|
||||
;(let ((ns (orig-current-seconds)))
|
||||
; (when (> (- ns 5) s)
|
||||
@@ -286,12 +348,14 @@
|
||||
)
|
||||
))
|
||||
(worker)))
|
||||
|
||||
(define/public (set-list! playlist)
|
||||
(stop-and-clear)
|
||||
(set! pl playlist)
|
||||
)
|
||||
|
||||
(define/public (play playlist)
|
||||
(stop-and-clear)
|
||||
;(unless (eq? pl #f) (send pl display-tracks))
|
||||
(set! pl playlist)
|
||||
;(unless (eq? pl #f) (send pl display-tracks))
|
||||
(send this set-list! playlist)
|
||||
(send this play-track 0)
|
||||
)
|
||||
|
||||
|
||||
137
playlist.rkt
137
playlist.rkt
@@ -5,6 +5,7 @@
|
||||
racket-sound
|
||||
"utils.rkt"
|
||||
racket-sprintf
|
||||
"keystore.rkt"
|
||||
)
|
||||
|
||||
(provide track%
|
||||
@@ -12,6 +13,10 @@
|
||||
)
|
||||
|
||||
(define the-displayln displayln)
|
||||
(define list-for-each for-each)
|
||||
(define list-length length)
|
||||
|
||||
(define next-track-id 0)
|
||||
|
||||
(define track%
|
||||
(class object%
|
||||
@@ -31,12 +36,19 @@
|
||||
album
|
||||
length)))
|
||||
|
||||
(define my-id (begin
|
||||
(set! next-track-id (+ next-track-id 1))
|
||||
(when (> next-track-id 10000000)
|
||||
(set! next-track-id 1))
|
||||
next-track-id))
|
||||
|
||||
(define/public (get-file) file)
|
||||
(define/public (get-title) title)
|
||||
(define/public (get-artist) artist)
|
||||
(define/public (get-album) album)
|
||||
(define/public (get-number) number)
|
||||
(define/public (get-length) length)
|
||||
(define/public (get-id) my-id)
|
||||
|
||||
(define (read-tags)
|
||||
(let* ((f (if (path? file) (path->string file) file))
|
||||
@@ -148,8 +160,12 @@
|
||||
(init-field
|
||||
[start-map #f]
|
||||
[max-tracks 100]
|
||||
[name "Default"]
|
||||
[id #f]
|
||||
[settings #f]
|
||||
)
|
||||
|
||||
|
||||
(define store (new keystore% [filename "rktplayer.store"]))
|
||||
(define tracks '())
|
||||
|
||||
(define (can-add? file)
|
||||
@@ -183,16 +199,125 @@
|
||||
)
|
||||
)
|
||||
|
||||
;(define/public (set-name! n)
|
||||
; (set! name n))
|
||||
|
||||
;(define/public (set-id! id*)
|
||||
; (set! id id*))
|
||||
|
||||
;(define/public (get-id)
|
||||
; id)
|
||||
|
||||
;(define/public (get-name)
|
||||
; name)
|
||||
|
||||
(define/public (tabs)
|
||||
(map (λ (k)
|
||||
(if (string? k)
|
||||
(string->symbol k)
|
||||
k))
|
||||
(send store get 'tabs '(tabkey-default)))
|
||||
)
|
||||
|
||||
(define/public (tab-count)
|
||||
(list-length (tabs)))
|
||||
|
||||
(define/public (make-tab-key)
|
||||
(string->symbol
|
||||
(format "tabkey-~a-~a" (current-milliseconds) (random 10000))))
|
||||
|
||||
(define/public (get-tab-name idx)
|
||||
(let* ((t (tabs))
|
||||
(entry (list-ref t idx)))
|
||||
(let ((v (send store get entry (list (format "Playlist-~a" idx) '()))))
|
||||
(car v))))
|
||||
|
||||
(define/public (set-tab-name! idx name)
|
||||
(let* ((t (tabs))
|
||||
(entry (list-ref t idx))
|
||||
(v (send store get entry (list (format "Playlist-~a" idx) '())))
|
||||
)
|
||||
(send store set! entry (list name (cadr v)))
|
||||
)
|
||||
)
|
||||
|
||||
(define/public (tab-id idx)
|
||||
(let ((t (tabs)))
|
||||
(list-ref t idx)))
|
||||
|
||||
(define/public (tab-index id)
|
||||
(let ((t (tabs)))
|
||||
(letrec ((f (λ (t idx)
|
||||
(if (null? t)
|
||||
#f
|
||||
(if (eq? (car t) id)
|
||||
idx
|
||||
(f (cdr t) (+ idx 1)))))))
|
||||
(f t 0))))
|
||||
|
||||
(define/public (drop-tab! idx)
|
||||
(let* ((t (tabs))
|
||||
(entry (list-ref t idx))
|
||||
)
|
||||
(send store set! 'tabs (list-drop! t idx))
|
||||
(send store drop! entry)
|
||||
))
|
||||
|
||||
(define/public (add-tab!)
|
||||
(let* ((t (tabs))
|
||||
(new-entry (send this make-tab-key)))
|
||||
(send store set! 'tabs (append t (list new-entry)))
|
||||
)
|
||||
)
|
||||
|
||||
(define/public (save-tab!)
|
||||
(let* ((entry id)
|
||||
(idx (send this tab-index entry))
|
||||
)
|
||||
(displayln (format "entry id = ~a, ~a" entry idx))
|
||||
(if (eq? idx #f)
|
||||
(err-rktplayer "Cannot get tab for id ~a" entry)
|
||||
(let ((value (list (send this get-tab-name idx)
|
||||
(map (λ (track)
|
||||
(format "~a" (send track get-file)))
|
||||
tracks))))
|
||||
(send store set! entry value)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(define/public (load-tab idx)
|
||||
(let* ((t (tabs))
|
||||
(entry (list-ref t idx))
|
||||
)
|
||||
(displayln (format "loading ~a" entry))
|
||||
(set! id entry)
|
||||
(set! tracks '())
|
||||
(let ((value (send store get entry (list "Default" '()))))
|
||||
(set! name (car value))
|
||||
(list-for-each (λ (file)
|
||||
(send this add-track file #f))
|
||||
(cadr value))
|
||||
)
|
||||
)
|
||||
#t
|
||||
)
|
||||
|
||||
(define/public (read-tracks)
|
||||
(set! tracks '())
|
||||
(read-tracks-internal start-map)
|
||||
(send this save-tab!)
|
||||
)
|
||||
|
||||
(define/public (length)
|
||||
(list-len tracks))
|
||||
|
||||
(define/public (add-track file)
|
||||
(add-track* file))
|
||||
(define/public (add-track file . args)
|
||||
(add-track* file)
|
||||
(when (null? args)
|
||||
(send this save-tab!))
|
||||
)
|
||||
|
||||
(define/public (track i)
|
||||
(list-ref tracks i))
|
||||
@@ -244,9 +369,11 @@
|
||||
(mktable rows 'tracks formatter))))
|
||||
|
||||
(super-new)
|
||||
|
||||
(begin
|
||||
(when (eq? start-map #f)
|
||||
(error "Initialize playlist% with a starting map"))
|
||||
(if (eq? start-map #f)
|
||||
(send this load-tab 0)
|
||||
(set! id (send this tab-id id)))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
))
|
||||
(window (new rktplayer% [wv-context context]))
|
||||
)
|
||||
(send window devtools)
|
||||
;(send window devtools)
|
||||
window)
|
||||
)
|
||||
|
||||
|
||||
@@ -51,3 +51,5 @@
|
||||
('nl "Kies de map met de Muziek Bibliotheek"))
|
||||
(add "Quit"
|
||||
('nl "Beëindigen"))
|
||||
(add "channels"
|
||||
('nl "kanalen"))
|
||||
|
||||
24
utils.rkt
24
utils.rkt
@@ -19,6 +19,7 @@
|
||||
warn-rktplayer
|
||||
fatal-rktplayer
|
||||
(all-from-out simple-log)
|
||||
list-drop!
|
||||
)
|
||||
|
||||
|
||||
@@ -50,6 +51,13 @@
|
||||
)
|
||||
)
|
||||
|
||||
(define (list-drop! l idx)
|
||||
(if (null? l)
|
||||
l
|
||||
(if (= idx 0)
|
||||
(cdr l)
|
||||
(cons (car l) (list-drop! (cdr l) (- idx 1))))))
|
||||
|
||||
(define (make-delayed-reactor seconds closure)
|
||||
(let* ((last-val #f)
|
||||
(last-time -1)
|
||||
@@ -87,11 +95,17 @@
|
||||
)
|
||||
|
||||
(define (open-file-manager path)
|
||||
(let ((folder (if (path? path) (path->string path) path)))
|
||||
(case (system-type 'os)
|
||||
[(windows) (process (string-append "explorer.exe " folder))]
|
||||
[(macosx) (process (string-append "open " folder))]
|
||||
[else (process (string-append "xdg-open " folder))]))
|
||||
(let ((folder (if (path? path) (path->string path) path))
|
||||
(do-open (λ (prg arg)
|
||||
(let ((exe (find-executable-path prg)))
|
||||
(dbg-rktplayer "(process* ~a ~a)" exe arg)
|
||||
(process* exe arg))))
|
||||
)
|
||||
(dbg-rktplayer "open-file-manager ~a" folder)
|
||||
(case (system-type 'os)
|
||||
[(windows) (do-open "explorer.exe" folder)]
|
||||
[(macosx) (do-open "open" folder)]
|
||||
[else (do-open "xdg-open" folder)]))
|
||||
)
|
||||
|
||||
(define (basedir file)
|
||||
|
||||
Reference in New Issue
Block a user