A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 463 lines 14 kB view raw
1--[[ Lua RB Playback 2/*************************************************************************** 3 * __________ __ ___. 4 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 5 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 6 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 7 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 8 * \/ \/ \/ \/ \/ 9 * $Id$ 10 * 11 * Copyright (C) 2020 William Wilgus 12 * 13 * This program is free software; you can redistribute it and/or 14 * modify it under the terms of the GNU General Public License 15 * as published by the Free Software Foundation; either version 2 16 * of the License, or (at your option) any later version. 17 * 18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 19 * KIND, either express or implied. 20 * 21 ****************************************************************************/ 22]] 23 24local print = require("print") 25 26local _draw = require("draw") 27local _poly = require("draw_poly") 28local _clr = require("color") 29 30require("actions") 31require("rbsettings") 32require("settings") 33 34local scrpath = rb.current_path() 35 36local metadata = rb.settings.read 37local cur_trk = "audio_current_track" 38local track_data = {} 39-- grab only settings we are interested in 40track_data.title = rb.metadata.mp3_entry.title 41track_data.path = rb.metadata.mp3_entry.path 42 43do -- free up some ram by removing items we don't need 44 local function strip_functions(t, ...) 45 local t_keep = {...} 46 local keep 47 for key, val in pairs(t) do 48 keep = false 49 for _, v in ipairs(t_keep) do 50 if string.find (key, v) then 51 keep = true; break 52 end 53 end 54 if keep ~= true then 55 t[key] = nil 56 end 57 end 58 end 59 60 strip_functions(rb.actions, "PLA_", "TOUCHSCREEN", "_NONE") 61 rb.contexts = nil 62 63 strip_functions(_draw, "^rect$", "^rect_filled$") 64 strip_functions(_poly, "^polyline$") 65 strip_functions(print.opt, "line", "get") 66 67 _clr.inc = nil 68 rb.metadata = nil -- remove metadata settings 69 rb.system = nil -- remove system settings 70 rb.settings.dump = nil 71 collectgarbage("collect") 72end 73 74local t_icn = {} 75t_icn[1] = {16,16,16,4,10,10,10,4,4,10,10,16,10,10} -- rewind 76t_icn[2] = {4,5,4,15,10,9,10,15,12,15,12,5,10,5,10,11,4,5} -- play/pause 77t_icn[3] = {4,4,4,16,10,10,10,16,16,10,10,4,10,10} -- fast forward 78 79local pb = {} 80local track_length 81 82local track_name = metadata(cur_trk, track_data.title) or 83 metadata(cur_trk, track_data.path) 84 85local clr_active = _clr.set(1, 0, 255, 0) 86local clr_inactive = _clr.set(0, 255, 255, 255) 87local t_clr_icn = {clr_inactive, clr_inactive, clr_inactive} 88 89 90local function set_active_icon(idx) 91 local tClr = t_clr_icn 92 93 if idx == 4 and t_clr_icn[4] == clr_active then 94 idx = 2 95 end 96 for i = 1, 4 do 97 t_clr_icn[i] = clr_inactive 98 end 99 if idx >= 1 and idx <= 4 then 100 t_clr_icn[idx] = clr_active 101 end 102end 103 104local function clear_actions() 105 track_length = (rb.audio("length") or 0) 106 local playback = rb.audio("status") 107 if playback == 1 then 108 set_active_icon(2) 109 elseif playback == 3 then 110 set_active_icon(4) 111 return 112 end 113 rockev.trigger("timer", false, rb.current_tick() + rb.HZ * 60) 114end 115 116-------------------------------------------------------------------------------- 117--[[ returns a sorted tables of directories and (another) of files 118-- path is the starting path; norecurse == true.. only that path will be searched 119-- findfile & finddir are definable search functions 120-- if not defined all files/dirs are returned if false is passed.. none 121-- or you can provide your own function see below.. 122-- f_t and d_t allow you to pass your own tables for re-use but isn't necessary 123]] 124local function get_files(path, norecurse, finddir, findfile, sort_by, max_depth, f_t, d_t) 125 local quit = false 126 --local sort_by_function -- forward declaration 127 local filepath_function -- forward declaration 128 local files = f_t or {} 129 local dirs = d_t or {} 130 131 local function f_filedir(name) 132 --default find function 133 -- example: return name:find(".mp3", 1, true) ~= nil 134 if name:len() <= 2 and (name == "." or name == "..") then 135 return false 136 end 137 return true 138 end 139 local function d_filedir(name) 140 --default discard function 141 return false 142 end 143 144 if finddir == nil then 145 finddir = f_filedir 146 elseif type(finddir) ~= "function" then 147 finddir = d_filedir 148 end 149 150 if findfile == nil then 151 findfile = f_filedir 152 elseif type(findfile) ~= "function" then 153 findfile = d_filedir 154 end 155 156 if not max_depth then max_depth = 3 end 157 max_depth = max_depth + 1 -- determined by counting '/' 3 deep = /d1/d2/d3/ 158 159 local function _get_files(path) 160 local sep = "" 161 local filepath 162 local finfo_t 163 local count 164 if string.sub(path, - 1) ~= "/" then sep = "/" end 165 for fname, isdir, finfo_t in luadir.dir(path, true) do 166 if isdir and finddir(fname) then 167 path, count = string.gsub(path, "/", "/") 168 if count <= max_depth then 169 table.insert(dirs, path .. sep ..fname) 170 end 171 elseif not isdir and findfile(fname) then 172 filepath = filepath_function(path, sep, fname) 173 table.insert(files, filepath) 174 end 175 176 if action_quit() then 177 return true 178 end 179 end 180 end 181 182 rb.splash(10, "Searching for Files") 183 184 filepath_function = function(path, sep, fname) 185 return string.format("%s%s%s;", path, sep, fname) 186 end 187 table.insert(dirs, path) -- root 188 189 for key,value in pairs(dirs) do 190 --luadir.dir may error out so we need to do the call protected 191 -- _get_files(value, CANCEL_BUTTON) 192 _, quit = pcall(_get_files, value) 193 194 if quit == true or norecurse then 195 break; 196 end 197 end 198 199 return dirs, files 200end -- get_files 201 202local audio_elapsed, audio_ff_rew 203do 204 local elapsed = 0 205 local ff_rew = 0 206 audio_elapsed = function() 207 if ff_rew == 0 then elapsed = (rb.audio("elapsed") or 0) end 208 return elapsed 209 end 210 211 audio_ff_rew = function(time_ms) 212 if ff_rew ~= 0 and time_ms == 0 then 213 rb.audio("stop") 214 rb.audio("play", elapsed, 0) 215 rb.sleep(100) 216 elseif time_ms ~= 0 then 217 elapsed = elapsed + time_ms 218 if elapsed < 0 then elapsed = 0 end 219 if elapsed > track_length then elapsed = track_length end 220 end 221 ff_rew = time_ms 222 end 223end 224 225do 226 local act = rb.actions 227 local quit = false 228 local last_action = 0 229 local magnitude = 1 230 local skip_ms = 100 231 local playback 232 233 function action_event(action) 234 local event 235 if action == act.PLA_EXIT or action == act.PLA_CANCEL then 236 quit = true 237 elseif action == act.PLA_RIGHT_REPEAT then 238 event = pb.TRACK_FF 239 audio_ff_rew(skip_ms * magnitude) 240 if magnitude < 300 then 241 magnitude = magnitude + 1 242 end 243 elseif action == act.PLA_LEFT_REPEAT then 244 event = pb.TRACK_REW 245 audio_ff_rew(-skip_ms * magnitude) 246 if magnitude < 300 then 247 magnitude = magnitude + 1 248 end 249 elseif action == act.PLA_SELECT then 250 playback = rb.audio("status") 251 if playback == 1 then 252 rb.audio("pause") 253 collectgarbage("collect") 254 elseif playback == 3 then 255 rb.audio("resume") 256 end 257 event = rb.audio("status") + 1 258 elseif action == act.ACTION_NONE then 259 magnitude = 1 260 audio_ff_rew(0) 261 if last_action == act.PLA_RIGHT then 262 rb.audio("next") 263 elseif last_action == act.PLA_LEFT then 264 rb.audio("prev") 265 end 266 end 267 268 if event then -- pass event id to playback_event 269 rockev.trigger(pb.EV, true, event) 270 end 271 272 last_action = action 273 end 274 275 function action_set_quit(bQuit) 276 quit = bQuit 277 end 278 279 function action_quit() 280 return quit 281 end 282end 283 284do 285 pb.EV = "playback" 286 -- custom event ids 287 pb.STOPPED = 1 288 pb.PLAY = 2 289 pb.PAUSE = 3 290 pb.PAUSED = 4 291 pb.TRACK_REW = 5 292 pb.PLAY_ = 6 293 pb.TRACK_FF = 7 294 295 function playback_event(id, event_data) 296 if id == pb.PLAY then 297 id = pb.PLAY_ 298 elseif id == pb.PAUSE then 299 id = 8 300 elseif id == rb.PLAYBACK_EVENT_TRACK_BUFFER or 301 id == rb.PLAYBACK_EVENT_TRACK_CHANGE then 302 rb.lcd_scroll_stop() 303 track_name = metadata(cur_trk, track_data.title) or 304 metadata(cur_trk, track_data.path) 305 else 306 -- rb.splash(0, id) 307 end 308 309 set_active_icon(id - 4) 310 rockev.trigger("timer", false, rb.current_tick() + rb.HZ) 311 end 312end 313 314local function pbar_init(img, x, y, w, h, fgclr, bgclr, barclr) 315 local t 316 -- when initialized table is returned that points back to this function 317 if type(img) == "table" then 318 t = img 319 t.pct = x 320 if not t.set then error("not initialized", 2) end 321 else 322 t = {} 323 t.img = img or rb.lcd_framebuffer() 324 t.x = x or 1 325 t.y = y or 1 326 t.w = w or 10 327 t.h = h or 10 328 t.pct = 0 329 t.fgclr = fgclr or _clr.set(-1, 255, 255, 255) 330 t.bgclr = bgclr or _clr.set(0, 0, 50, 200) 331 t.barclr = barclr or t.fgclr 332 333 t.set = pbar_init 334 setmetatable(t,{__call = t.set}) 335 return t 336 end -- initalization 337--============================================================================-- 338 if t.pct < 0 then 339 t.pct = 0 340 elseif t.pct > 100 then 341 t.pct = 100 342 end 343 344 local wp = t.pct * (t.w - 4) / 100 345 346 if wp < 1 and t.pct > 0 then wp = 1 end 347 348 _draw.rect_filled(t.img, t.x, t.y, t.w, t.h, t.fgclr, t.bgclr, true) 349 _draw.rect_filled(t.img, t.x + 2, t.y + 2, wp, t.h - 4, t.barclr, nil, true) 350end -- pbar_init 351 352local function create_playlist(startdir, file_search, maxfiles) 353 354 function f_filedir_(name) 355 if name:len() <= 2 and (name == "." or name == "..") then 356 return false 357 end 358 if name:find(file_search) ~= nil then 359 maxfiles = maxfiles -1 360 if maxfiles < 1 then 361 action_set_quit(true) 362 return true 363 end 364 return true 365 else 366 return false 367 end 368 end 369 370 local norecurse = false 371 local f_finddir = nil 372 local f_findfile = f_filedir_ 373 local files = {} 374 local dirs = {} 375 local max_depth = 3 376 377 dirs, files = get_files(startdir, norecurse, f_finddir, f_findfile, nil, max_depth, dirs, files) 378 379 if #files > 0 then 380 rb.audio("stop") 381 rb.playlist("create", scrpath .. "/", "playback.m3u8") 382 end 383 384 for i = 1, #files do 385 rb.playlist("insert_track", string.match(files[i], "[^;]+") or "?") 386 end 387 if #files > 0 then 388 rb.playlist("start", 0, 0, 0) 389 end 390 391 for i=1, #dirs do dirs[i] = nil end -- empty table 392 for i=1, #files do files[i] = nil end -- empty table 393 action_set_quit(false) 394end -- create_playlist 395 396local function main() 397 clear_actions() 398 local lcd = rb.lcd_framebuffer() 399 400 local eva = rockev.register("action", action_event) 401 402 local evp = rockev.register("playback", playback_event) 403 404 if not track_name then -- Nothing loaded lets search for some mp3's 405 create_playlist("/", "%.mp3$", 10) 406 collectgarbage("collect") 407 end 408 409 local evt = rockev.register("timer", clear_actions, rb.HZ) 410 411 rb.lcd_clear_display() 412 rb.lcd_update() 413 do -- configure print function 414 local t_print = print.opt.get(true) 415 t_print.autoupdate = false 416 t_print.justify = "center" 417 t_print.col = 2 418 end 419 420 local progress = pbar_init(nil, 1, rb.LCD_HEIGHT - 5, rb.LCD_WIDTH - 1, 5) 421 422 local i = 0 423 424 local colw = (rb.LCD_WIDTH - 16) / 4 425 local scr_col = {colw, colw * 2, colw * 3} 426 --local mem = collectgarbage("count") 427 --local mem_min = mem 428 --local mem_max = mem 429 while not action_quit() do 430 elapsed = audio_elapsed() 431 432 -- control initialized with pbar_init now we set the value 433 progress(track_length > 0 and elapsed / (track_length / 100) or 0) 434 435 print.opt.line(1) 436 print.f() -- clear the line 437 print.f(track_name) 438 print.f() -- clear the line 439 _,_,_,h = print.f("%ds / %ds", elapsed / 1000, track_length / 1000) 440 441 for i = 1, 3 do -- draw the rew/play/fwd icons 442 _poly.polyline(lcd, scr_col[i], h * 2 + 1, t_icn[i], t_clr_icn[i], true) 443 end 444--[[ 445 mem = collectgarbage("count") 446 if mem < mem_min then mem_min = mem end 447 if mem > mem_max then mem_max = mem end 448 449 if i >= 10 then 450 rb.splash(0, mem_min .. " : " .. mem .. " : " ..mem_max) 451 i = 0 452 else 453 i = i + 1 454 end 455--]] 456 rb.lcd_update() 457 rb.sleep(rb.HZ / 2) 458 end 459 460 rb.splash(100, "Goodbye") 461end 462 463main() -- BILGUS