#lang racket (require racket-webview racket/runtime-path racket/gui racket-sprintf "utils.rkt" "music-library.rkt" "translate.rkt" "playlist.rkt" "player.rkt" ) (provide (all-from-out racket-webview) rktplayer% ) (define-runtime-path rkt-gui-dir "gui") (define player-menu (λ () (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))) ) )) (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 closed #f) (define el-seeker #f) (define el-library #f) (define el-playlist #f) (define el-at #f) (define el-length #f) (define music-library (send settings get 'music-library (find-system-path 'home-dir))) (define current-music-path #f) (define playlist #f) (define current-at-seconds 0) (define current-length-seconds 0) (define (update-time at-seconds length-seconds) (let ((as (inexact->exact (round at-seconds))) (ls (inexact->exact (round length-seconds)))) (when (or (not (= current-at-seconds as)) (not (= current-length-seconds ls))) (set! current-at-seconds as) (set! current-length-seconds ls) (let ((as-str (sprintf "%02d:%02d:%02d" (quotient as 3600) (quotient (remainder as 3600) 60) (remainder (remainder as 3600) 60))) (ls-str (sprintf "%02d:%02d:%02d" (quotient ls 3600) (quotient (remainder ls 3600) 60) (remainder (remainder ls 3600) 60))) ) (unless closed (send el-at set-innerHTML! as-str) (send el-length set-innerHTML! ls-str) (let ((seeker (if (= ls 0) 0.0 (exact->inexact (/ (* 100 as) ls))))) (send el-seeker set! (format "~a" seeker))) ) ) ) ) ) (define current-track-nr #f) (define (update-track-nr nr) (unless (eq? playlist #f) (dbg-rktplayer "update-track-nr ~a" nr) (let ((id (λ () (send playlist track-id current-track-nr))) ;string->symbol (format "track-~a" (+ current-track-nr 1))))) (ct current-track-nr)) (dbg-rktplayer "Removing current") (unless (eq? current-track-nr #f) (dbg-rktplayer (format "current old track: ~a" (id))) (let ((el (send this element (id)))) (send el remove-class! "current"))) (set! current-track-nr nr) (dbg-rktplayer "Adding current") (unless (eq? current-track-nr #f) (dbg-rktplayer "current new track: ~a" (id)) (let ((el (send this element (id)))) (send el add-class! "current")) (dbg-rktplayer "Getting cover image") (let* ((track (send playlist track current-track-nr)) (img-file (if (eq? (system-type 'os) 'windows) "c:/tmp/cover-image" "/tmp/cover-image")) (stored-file (send track image->file img-file)) ) (dbg-rktplayer "image mimetype: ~a" (send track image->mimetype)) (unless (eq? stored-file #f) (let ((el (send this element 'album-art))) (when (eq? (system-type 'os) 'windows) (set! stored-file (string-append "/" stored-file))) (let ((html (format "" stored-file (current-milliseconds)))) (send el set-innerHTML! html)))) ;(send el set-attr! 'src stored-file)))) ) ) (dbg-rktplayer "Done updating track") ) ) ) (define state #f) (define (update-state st) (unless (eq? st state) (set! state st) (if (eq? st 'playing) (let ((btn (send this element 'play-img))) (send btn set-attr! '(src "buttons/stop.svg"))) (let ((btn (send this element 'play-img))) (send btn set-attr! '(src "buttons/play.svg")))))) (define player (new player% [time-updater update-time] [track-nr-updater update-track-nr] [state-updater update-state] [settings settings] )) (define inner-html-handlers (make-hash)) (define/override (page-loaded oke) (super page-loaded oke) (ww-connect 'play play-or-stop) (ww-connect 'pause pause) (ww-connect 'prev previous-track) (ww-connect 'next next-track) (ww-connect 'repeat repeat) (ww-connect 'volume volume) (set! el-seeker (send this element 'seek)) (dbg-rktplayer "el-seeker: ~a" (send el-seeker get)) (let ((seek-reactor (make-delayed-reactor 0.3 (λ (percentage) (send this seek-to percentage))))) (send el-seeker on-change! seek-reactor)) (set! el-library (send this element 'library)) (set! el-playlist (send this element 'tracks)) (set! el-at (send this element 'time)) (set! el-length (send this element 'totaltime)) (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 update-library) ) (define/public (update-playlist) (let* ((html (send playlist to-html)) (result (send el-playlist set-innerHTML! html)) ) (dbg-rktplayer "result: ~a" result) (send this bind! "table.tracks tr" 'click (λ (el evt data) (let* ((track-id (send el attr/symbol 'id)) (idx (send playlist index track-id)) ) (send this play-track idx) ) ) ) (update-track-nr current-track-nr) ) ) (define/public (scroll-top id) (send this run-js (format (string-append "{ let el_id = '~a';" " console.log('id = ' + el_id);" " let el = document.getElementById(el_id);" " console.log(el);" " el.scrollTop = 0;" "}") id))) (define/public (update-library) (when (eq? current-music-path #f) (set! current-music-path music-library)) (let* ((nr 0) (l (filter (λ (r) (music-lib-relevant? (cadr r))) (map (λ (e) (set! nr (+ nr 1)) (list (format "row-~a" nr) (build-path current-music-path e) (format "path-~a" nr))) (directory-list current-music-path))))) (unless (equal? (format "~a" current-music-path) (format "~a" music-library)) (set! l (cons (list "lib-up" "↰" "lib-up") l)) ) (let ((html (mktable l 'music-library library-formatter))) (let ((result (send el-library set-innerHTML! html))) (dbg-rktplayer "set-innerHTML!: ~a" result) (send this scroll-top 'library) (dbg-rktplayer "Binding...") (send this bind! "td.library-entry" 'click (λ (el evt data) (dbg-rktplayer "~a ~a" evt data) (dbg-rktplayer "id:~a, file:~a" (send el attr 'id) (send el attr 'file)) (let ((path (send el attr 'file))) (unless (eq? path #f) (send this path-choosen path))))) (send this bind! "td.library-entry" 'contextmenu (λ (el evt data) (dbg-rktplayer "~a ~a" evt data) (let ((path (send el attr 'file))) (unless (eq? path #f) (send this context-for-path data path))) )) (dbg-rktplayer "Done...") )) ) ) (define/public (path-choosen path) (let ((path-part (if (equal? path "↰") ".." (format "~a" path)))) (let ((npath (if (string=? path-part "..") (build-path current-music-path path-part) path))) (when (directory-exists? npath) (set! current-music-path (normalize-path npath)) (send this update-library) ) ) ) ) (define/public (context-for-path evt path) (let ((items (list (wv-menu-item 'm-play-this (tr "Play this") #:callback (λ () (send this play-path path)))))) (when (file-exists? path) (set! items (append items (list (wv-menu-item 'm-add-this (tr "Add this") #:callback (λ () (send this add-path path))))))) (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)) (clientX (hash-ref evt 'clientX 60)) (clientY (hash-ref evt 'clientY 60)) ) (send this popup-menu! mnu clientX clientY) ) ) ) (define/public (play-path path) (dbg-rktplayer "Playing ~a" path) (let ((pl (new playlist% [start-map path]))) (set! current-track-nr #f) (send pl read-tracks) (set! playlist pl) (send this update-playlist) (send player play pl) (dbg-rktplayer "number of tracks: ~a" (send playlist length)) ) ) (define/public (add-path path) (send playlist add-track path) (send this update-playlist) ) (define/public (open-booklet path) (dbg-rktplayer "Open booklet ~a" path)) (define/public (open-folder path) (dbg-rktplayer "path: ~a" 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-track idx) (send player play-track idx)) (define/public (pause) (send player pause-unpause)) (define/public (next-track) (send player next) ) (define/public (previous-track) (send player previous) ) (define/public (repeat) (dbg-rktplayer "Repeat") ) (define/public (volume) (dbg-rktplayer "Volume") ) (define/public (seek-to percentage) (dbg-rktplayer "Seeking to percentage: ~a" percentage) ) (define/public (quit) (dbg-rktplayer "Quitting") (send player quit) (set! closed #t) (send this close)) (define/public (select-library) (let ((dir (send this choose-dir (tr "Choose the folder containing your music library") (if (string? music-library) music-library (path->string music-library)) ))) (if (eq? dir 'showing) 'done (unless (eq? dir #f) (set! music-library dir) (send settings set! 'music-library dir) (set! current-music-path #f) (send this update-library) ) ) ) ) (begin (dbg-rktplayer "ICON: ~a" (get-field icon this)) (let ((lang (send settings get 'lang 'en))) (dbg-rktplayer "RktPlayer started, current language: ~a" lang)) ) ) )