Something went wrong. Try again.
A modern, network-enabled music player platform built on Rockbox technology. rockboxd.tsiry-sandratraina.com
rust deno navidrome airplay libadwaita zig mpris snapcast mpd rockbox audio subsonic
Something went wrong. Try again.
14 kB · 463 lines
Lua
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464--[[ Lua RB Playback/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * * Copyright (C) 2020 William Wilgus * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/]]
local print = require("print")
local _draw = require("draw")local _poly = require("draw_poly")local _clr = require("color")
require("actions")require("rbsettings")require("settings")require("audio_status")
local scrpath = rb.current_path()
local metadata = rb.settings.readlocal cur_trk = "audio_current_track"local track_data = {}-- grab only settings we are interested intrack_data.title = rb.metadata.mp3_entry.titletrack_data.path = rb.metadata.mp3_entry.path
do -- free up some ram by removing items we don't need local function strip_functions(t, ...) local t_keep = {...} local keep for key, val in pairs(t) do keep = false for _, v in ipairs(t_keep) do if string.find (key, v) then keep = true; break end end if keep ~= true then t[key] = nil end end end
strip_functions(rb.actions, "PLA_", "TOUCHSCREEN", "_NONE") rb.contexts = nil
strip_functions(_draw, "^rect$", "^rect_filled$") strip_functions(_poly, "^polyline$") strip_functions(print.opt, "line", "get")
_clr.inc = nil rb.metadata = nil -- remove metadata settings rb.system = nil -- remove system settings rb.settings.dump = nil collectgarbage("collect")end
local t_icn = {}t_icn[1] = {16,16,16,4,10,10,10,4,4,10,10,16,10,10} -- rewindt_icn[2] = {4,5,4,15,10,9,10,15,12,15,12,5,10,5,10,11,4,5} -- play/pauset_icn[3] = {4,4,4,16,10,10,10,16,16,10,10,4,10,10} -- fast forward
local pb = {}local track_length
local track_name = metadata(cur_trk, track_data.title) or metadata(cur_trk, track_data.path)
local clr_active = _clr.set(1, 0, 255, 0)local clr_inactive = _clr.set(0, 255, 255, 255)local t_clr_icn = {clr_inactive, clr_inactive, clr_inactive}
local function set_active_icon(idx) local tClr = t_clr_icn
if idx == 4 and t_clr_icn[4] == clr_active then idx = 2 end for i = 1, 4 do t_clr_icn[i] = clr_inactive end if idx >= 1 and idx <= 4 then t_clr_icn[idx] = clr_active endend
local function clear_actions() track_length = (rb.audio("length") or 0) local playback = rb.audio("status") if playback == rb.AUDIO_STATUS_PLAY then set_active_icon(2) elseif playback == (rb.AUDIO_STATUS_PAUSE + rb.AUDIO_STATUS_PLAY) then set_active_icon(4) return end rockev.trigger("timer", false, rb.current_tick() + rb.HZ * 60)end
----------------------------------------------------------------------------------[[ returns a sorted tables of directories and (another) of files-- path is the starting path; norecurse == true.. only that path will be searched-- findfile & finddir are definable search functions-- if not defined all files/dirs are returned if false is passed.. none-- or you can provide your own function see below..-- f_t and d_t allow you to pass your own tables for re-use but isn't necessary]]local function get_files(path, norecurse, finddir, findfile, sort_by, max_depth, f_t, d_t) local quit = false --local sort_by_function -- forward declaration local filepath_function -- forward declaration local files = f_t or {} local dirs = d_t or {}
local function f_filedir(name) --default find function -- example: return name:find(".mp3", 1, true) ~= nil if name:len() <= 2 and (name == "." or name == "..") then return false end return true end local function d_filedir(name) --default discard function return false end
if finddir == nil then finddir = f_filedir elseif type(finddir) ~= "function" then finddir = d_filedir end
if findfile == nil then findfile = f_filedir elseif type(findfile) ~= "function" then findfile = d_filedir end
if not max_depth then max_depth = 3 end max_depth = max_depth + 1 -- determined by counting '/' 3 deep = /d1/d2/d3/
local function _get_files(path) local sep = "" local filepath local finfo_t local count if string.sub(path, - 1) ~= "/" then sep = "/" end for fname, isdir, finfo_t in luadir.dir(path, true) do if isdir and finddir(fname) then path, count = string.gsub(path, "/", "/") if count <= max_depth then table.insert(dirs, path .. sep ..fname) end elseif not isdir and findfile(fname) then filepath = filepath_function(path, sep, fname) table.insert(files, filepath) end
if action_quit() then return true end end end
rb.splash(10, "Searching for Files")
filepath_function = function(path, sep, fname) return string.format("%s%s%s;", path, sep, fname) end table.insert(dirs, path) -- root
for key,value in pairs(dirs) do --luadir.dir may error out so we need to do the call protected -- _get_files(value, CANCEL_BUTTON) _, quit = pcall(_get_files, value)
if quit == true or norecurse then break; end end
return dirs, filesend -- get_files
local audio_elapsed, audio_ff_rewdo local elapsed = 0 local ff_rew = 0 audio_elapsed = function() if ff_rew == 0 then elapsed = (rb.audio("elapsed") or 0) end return elapsed end
audio_ff_rew = function(time_ms) if ff_rew ~= 0 and time_ms == 0 then rb.audio("stop") rb.audio("play", elapsed, 0) rb.sleep(100) elseif time_ms ~= 0 then elapsed = elapsed + time_ms if elapsed < 0 then elapsed = 0 end if elapsed > track_length then elapsed = track_length end end ff_rew = time_ms endend
do local act = rb.actions local quit = false local last_action = 0 local magnitude = 1 local skip_ms = 100 local playback
function action_event(action) local event if action == act.PLA_EXIT or action == act.PLA_CANCEL then quit = true elseif action == act.PLA_RIGHT_REPEAT then event = pb.TRACK_FF audio_ff_rew(skip_ms * magnitude) if magnitude < 300 then magnitude = magnitude + 1 end elseif action == act.PLA_LEFT_REPEAT then event = pb.TRACK_REW audio_ff_rew(-skip_ms * magnitude) if magnitude < 300 then magnitude = magnitude + 1 end elseif action == act.PLA_SELECT then playback = rb.audio("status") if playback == rb.AUDIO_STATUS_PLAY then rb.audio("pause") collectgarbage("collect") elseif playback == (rb.AUDIO_STATUS_PAUSE + rb.AUDIO_STATUS_PLAY) then rb.audio("resume") end event = rb.audio("status") + 1 elseif action == act.ACTION_NONE then magnitude = 1 audio_ff_rew(0) if last_action == act.PLA_RIGHT then rb.audio("next") elseif last_action == act.PLA_LEFT then rb.audio("prev") end end
if event then -- pass event id to playback_event rockev.trigger(pb.EV, true, event) end
last_action = action end
function action_set_quit(bQuit) quit = bQuit end
function action_quit() return quit endend
do pb.EV = "playback" -- custom event ids pb.STOPPED = 1 pb.PLAY = 2 pb.PAUSE = 3 pb.PAUSED = 4 pb.TRACK_REW = 5 pb.PLAY_ = 6 pb.TRACK_FF = 7
function playback_event(id, event_data) if id == pb.PLAY then id = pb.PLAY_ elseif id == pb.PAUSE then id = 8 elseif id == rb.PLAYBACK_EVENT_TRACK_BUFFER or id == rb.PLAYBACK_EVENT_TRACK_CHANGE then rb.lcd_scroll_stop() track_name = metadata(cur_trk, track_data.title) or metadata(cur_trk, track_data.path) else -- rb.splash(0, id) end
set_active_icon(id - 4) rockev.trigger("timer", false, rb.current_tick() + rb.HZ) endend
local function pbar_init(img, x, y, w, h, fgclr, bgclr, barclr) local t -- when initialized table is returned that points back to this function if type(img) == "table" then t = img t.pct = x if not t.set then error("not initialized", 2) end else t = {} t.img = img or rb.lcd_framebuffer() t.x = x or 1 t.y = y or 1 t.w = w or 10 t.h = h or 10 t.pct = 0 t.fgclr = fgclr or _clr.set(-1, 255, 255, 255) t.bgclr = bgclr or _clr.set(0, 0, 50, 200) t.barclr = barclr or t.fgclr
t.set = pbar_init setmetatable(t,{__call = t.set}) return t end -- initalization--============================================================================-- if t.pct < 0 then t.pct = 0 elseif t.pct > 100 then t.pct = 100 end
local wp = t.pct * (t.w - 4) / 100
if wp < 1 and t.pct > 0 then wp = 1 end
_draw.rect_filled(t.img, t.x, t.y, t.w, t.h, t.fgclr, t.bgclr, true) _draw.rect_filled(t.img, t.x + 2, t.y + 2, wp, t.h - 4, t.barclr, nil, true)end -- pbar_init
local function create_playlist(startdir, file_search, maxfiles)
function f_filedir_(name) if name:len() <= 2 and (name == "." or name == "..") then return false end if name:find(file_search) ~= nil then maxfiles = maxfiles -1 if maxfiles < 1 then action_set_quit(true) return true end return true else return false end end
local norecurse = false local f_finddir = nil local f_findfile = f_filedir_ local files = {} local dirs = {} local max_depth = 3
dirs, files = get_files(startdir, norecurse, f_finddir, f_findfile, nil, max_depth, dirs, files)
if #files > 0 then rb.audio("stop") rb.playlist("create", scrpath .. "/", "playback.m3u8") end
for i = 1, #files do rb.playlist("insert_track", string.match(files[i], "[^;]+") or "?") end if #files > 0 then rb.playlist("start", 0, 0, 0) end
for i=1, #dirs do dirs[i] = nil end -- empty table for i=1, #files do files[i] = nil end -- empty table action_set_quit(false)end -- create_playlist
local function main() clear_actions() local lcd = rb.lcd_framebuffer()
local eva = rockev.register("action", action_event)
local evp = rockev.register("playback", playback_event)
if not track_name then -- Nothing loaded lets search for some mp3's create_playlist("/", "%.mp3$", 10) collectgarbage("collect") end
local evt = rockev.register("timer", clear_actions, rb.HZ)
rb.lcd_clear_display() rb.lcd_update() do -- configure print function local t_print = print.opt.get(true) t_print.autoupdate = false t_print.justify = "center" t_print.col = 2 end
local progress = pbar_init(nil, 1, rb.LCD_HEIGHT - 5, rb.LCD_WIDTH - 1, 5)
local i = 0
local colw = (rb.LCD_WIDTH - 16) / 4 local scr_col = {colw, colw * 2, colw * 3} --local mem = collectgarbage("count") --local mem_min = mem --local mem_max = mem while not action_quit() do elapsed = audio_elapsed()
-- control initialized with pbar_init now we set the value progress(track_length > 0 and elapsed / (track_length / 100) or 0)
print.opt.line(1) print.f() -- clear the line print.f(track_name) print.f() -- clear the line _,_,_,h = print.f("%ds / %ds", elapsed / 1000, track_length / 1000)
for i = 1, 3 do -- draw the rew/play/fwd icons _poly.polyline(lcd, scr_col[i], h * 2 + 1, t_icn[i], t_clr_icn[i], true) end--[[ mem = collectgarbage("count") if mem < mem_min then mem_min = mem end if mem > mem_max then mem_max = mem end
if i >= 10 then rb.splash(0, mem_min .. " : " .. mem .. " : " ..mem_max) i = 0 else i = i + 1 end--]] rb.lcd_update() rb.sleep(rb.HZ / 2) end
rb.splash(100, "Goodbye")end
main() -- BILGUS