summaryrefslogtreecommitdiff
path: root/apps/plugins/lua_scripts
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/lua_scripts')
-rw-r--r--apps/plugins/lua_scripts/playback.lua459
1 files changed, 459 insertions, 0 deletions
diff --git a/apps/plugins/lua_scripts/playback.lua b/apps/plugins/lua_scripts/playback.lua
new file mode 100644
index 0000000000..bd2d0513f9
--- /dev/null
+++ b/apps/plugins/lua_scripts/playback.lua
@@ -0,0 +1,459 @@
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 = 1000
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 magnitude = magnitude + 1
241 elseif action == act.PLA_LEFT_REPEAT then
242 event = pb.TRACK_REW
243 audio_ff_rew(-skip_ms * magnitude)
244 magnitude = magnitude + 1
245 elseif action == act.PLA_SELECT then
246 playback = rb.audio("status")
247 if playback == 1 then
248 rb.audio("pause")
249 collectgarbage("collect")
250 elseif playback == 3 then
251 rb.audio("resume")
252 end
253 event = rb.audio("status") + 1
254 elseif action == act.ACTION_NONE then
255 magnitude = 1
256 audio_ff_rew(0)
257 if last_action == act.PLA_RIGHT then
258 rb.audio("next")
259 elseif last_action == act.PLA_LEFT then
260 rb.audio("prev")
261 end
262 end
263
264 if event then -- pass event id to playback_event
265 rockev.trigger(pb.EV, true, event)
266 end
267
268 last_action = action
269 end
270
271 function action_set_quit(bQuit)
272 quit = bQuit
273 end
274
275 function action_quit()
276 return quit
277 end
278end
279
280do
281 pb.EV = "playback"
282 -- custom event ids
283 pb.STOPPED = 1
284 pb.PLAY = 2
285 pb.PAUSE = 3
286 pb.PAUSED = 4
287 pb.TRACK_REW = 5
288 pb.PLAY_ = 6
289 pb.TRACK_FF = 7
290
291 function playback_event(id, event_data)
292 if id == pb.PLAY then
293 id = pb.PLAY_
294 elseif id == pb.PAUSE then
295 id = 8
296 elseif id == rb.PLAYBACK_EVENT_TRACK_BUFFER or
297 id == rb.PLAYBACK_EVENT_TRACK_CHANGE then
298 rb.lcd_scroll_stop()
299 track_name = metadata(cur_trk, track_data.title) or
300 metadata(cur_trk, track_data.path)
301 else
302 -- rb.splash(0, id)
303 end
304
305 set_active_icon(id - 4)
306 rockev.trigger("timer", false, rb.current_tick() + rb.HZ)
307 end
308end
309
310local function pbar_init(img, x, y, w, h, fgclr, bgclr, barclr)
311 local t
312 -- when initialized table is returned that points back to this function
313 if type(img) == "table" then
314 t = img
315 t.pct = x
316 if not t.set then error("not initialized", 2) end
317 else
318 t = {}
319 t.img = img or rb.lcd_framebuffer()
320 t.x = x or 1
321 t.y = y or 1
322 t.w = w or 10
323 t.h = h or 10
324 t.pct = 0
325 t.fgclr = fgclr or _clr.set(-1, 255, 255, 255)
326 t.bgclr = bgclr or _clr.set(0, 0, 50, 200)
327 t.barclr = barclr or t.fgclr
328
329 t.set = pbar_init
330 setmetatable(t,{__call = t.set})
331 return t
332 end -- initalization
333--============================================================================--
334 if t.pct < 0 then
335 t.pct = 0
336 elseif t.pct > 100 then
337 t.pct = 100
338 end
339
340 local wp = t.pct * (t.w - 4) / 100
341
342 if wp < 1 and t.pct > 0 then wp = 1 end
343
344 _draw.rect_filled(t.img, t.x, t.y, t.w, t.h, t.fgclr, t.bgclr, true)
345 _draw.rect_filled(t.img, t.x + 2, t.y + 2, wp, t.h - 4, t.barclr, nil, true)
346end -- pbar_init
347
348local function create_playlist(startdir, file_search, maxfiles)
349
350 function f_filedir_(name)
351 if name:len() <= 2 and (name == "." or name == "..") then
352 return false
353 end
354 if name:find(file_search) ~= nil then
355 maxfiles = maxfiles -1
356 if maxfiles < 1 then
357 action_set_quit(true)
358 return true
359 end
360 return true
361 else
362 return false
363 end
364 end
365
366 local norecurse = false
367 local f_finddir = nil
368 local f_findfile = f_filedir_
369 local files = {}
370 local dirs = {}
371 local max_depth = 3
372
373 dirs, files = get_files(startdir, norecurse, f_finddir, f_findfile, nil, max_depth, dirs, files)
374
375 if #files > 0 then
376 rb.audio("stop")
377 rb.playlist("create", scrpath .. "/", "playback.m3u8")
378 end
379
380 for i = 1, #files do
381 rb.playlist("insert_track", string.match(files[i], "[^;]+") or "?")
382 end
383 if #files > 0 then
384 rb.playlist("start", 0, 0, 0)
385 end
386
387 for i=1, #dirs do dirs[i] = nil end -- empty table
388 for i=1, #files do files[i] = nil end -- empty table
389 action_set_quit(false)
390end -- create_playlist
391
392local function main()
393 clear_actions()
394 local lcd = rb.lcd_framebuffer()
395
396 local eva = rockev.register("action", action_event)
397
398 local evp = rockev.register("playback", playback_event)
399
400 if not track_name then -- Nothing loaded lets search for some mp3's
401 create_playlist("/", "%.mp3$", 10)
402 collectgarbage("collect")
403 end
404
405 local evt = rockev.register("timer", clear_actions, rb.HZ)
406
407 rb.lcd_clear_display()
408 rb.lcd_update()
409 do -- configure print function
410 local t_print = print.opt.get(true)
411 t_print.autoupdate = false
412 t_print.justify = "center"
413 t_print.col = 2
414 end
415
416 local progress = pbar_init(nil, 1, rb.LCD_HEIGHT - 5, rb.LCD_WIDTH - 1, 5)
417
418 local i = 0
419
420 local colw = (rb.LCD_WIDTH - 16) / 4
421 local scr_col = {colw, colw * 2, colw * 3}
422 --local mem = collectgarbage("count")
423 --local mem_min = mem
424 --local mem_max = mem
425 while not action_quit() do
426 elapsed = audio_elapsed()
427
428 -- control initialized with pbar_init now we set the value
429 progress(track_length > 0 and elapsed / (track_length / 100) or 0)
430
431 print.opt.line(1)
432 print.f() -- clear the line
433 print.f(track_name)
434 print.f() -- clear the line
435 _,_,_,h = print.f("%ds / %ds", elapsed / 1000, track_length / 1000)
436
437 for i = 1, 3 do -- draw the rew/play/fwd icons
438 _poly.polyline(lcd, scr_col[i], h * 2 + 1, t_icn[i], t_clr_icn[i], true)
439 end
440--[[
441 mem = collectgarbage("count")
442 if mem < mem_min then mem_min = mem end
443 if mem > mem_max then mem_max = mem end
444
445 if i >= 10 then
446 rb.splash(0, mem_min .. " : " .. mem .. " : " ..mem_max)
447 i = 0
448 else
449 i = i + 1
450 end
451--]]
452 rb.lcd_update()
453 rb.sleep(rb.HZ / 2)
454 end
455
456 rb.splash(100, "Goodbye")
457end
458
459main() -- BILGUS