Rockbox Technical Forums

Rockbox Development => Feature Ideas => Topic started by: bahus on September 25, 2019, 01:39:27 AM

Title: Go to specific time in audio file
Post by: bahus on September 25, 2019, 01:39:27 AM
So I want to have a "Go to" option in context menu that allows to quickly enter and seek to specific time in currently playing file.
It seems the same interface that is used for setting time can be used here for entering time (Settings -> Time & Date -> Set Time)

Update In case anyone else is interested in this functionality thanks to Bilgus I've been able to implement it as lua plugin.

GoTo.lua (https://gist.github.com/bahusoid/480b40adb297f01dd2e0a7503246a4f8)

Up/Down: increase/decrease value by 1
Long Up/Down or  Volume Up/Down: increase/decrease value by 10
Left/Right: switch hours/mins/seconds
Select: Seek to entered time
Power/Home: Exit
Title: Re: Go to specific time feature
Post by: bahus on September 29, 2019, 01:42:25 AM
Any chance that this functionality is possible from lua? Maybe without fancy UI - just script that seeks to specific time will work for me.
Title: Re: Go to specific time feature
Post by: Bilgus on September 30, 2019, 01:39:38 AM
Should be possible all the time stuff is in there
Title: Re: Go to specific time feature
Post by: bahus on October 01, 2019, 02:52:22 AM
> all the time stuff is in there

Just to be clear. I want to start playing audio file from specific time.
I have pretty long files and sometime need to quickly seek to exact time. It's hard to find exact location using fast forward/rewind  on long files.
Title: Re: Go to specific time in audio file
Post by: Bilgus on October 05, 2019, 07:53:05 AM
I'm not done with it yet but it is functional

here is a player front end through lua that should get you started:

Code: [Select]

local print = require("print")

local _draw = require("draw")
local _poly = require("draw_poly")
local _clr  = require("color")

require("actions")
require("rbsettings")
require("settings")

local metadata = rb.settings.read
local cur_trk = "audio_current_track"
local track_data = {}
-- grab only settings we are interested in
track_data.title = rb.metadata.mp3_entry.title
track_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} -- rewind
t_icn[2] = {4,5,4,15,10,9,10,15,12,15,12,5,10,5,10,11,4,5} -- play/pause
t_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
    end
end

local function clear_actions()
    track_length = (rb.audio("length") or 0)
    local playback = rb.audio("status")
    if playback == 1 then
        set_active_icon(2)
    elseif playback == 3 then
        set_active_icon(4)
        return
    end
    rockev.trigger("timer", false, rb.current_tick() + rb.HZ * 60)
end

local audio_elapsed, audio_ff_rew
do
    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
    end
end

do
    local act = rb.actions
    local quit = false
    local last_action = 0
    local magnitude = 1
    local skip_ms = 1000
    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)
            magnitude = magnitude + 1
        elseif action == act.PLA_LEFT_REPEAT then
            event = pb.TRACK_REW
            audio_ff_rew(-skip_ms * magnitude)
            magnitude = magnitude + 1
        elseif action == act.PLA_SELECT then
            playback = rb.audio("status")
            if playback == 1 then
                rb.audio("pause")
                collectgarbage("collect")
            elseif playback == 3 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_quit()
        return quit
    end
end

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
            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)
    end
end

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 main()
    clear_actions()
    local lcd = rb.lcd_framebuffer()

    local eva = rockev.register("action", action_event)
    local evc = rockev.register("custom", rb.lcd_update)
    local evp = rockev.register("playback", playback_event)
    local evt = rockev.register("timer", clear_actions, rb.HZ)

    rockev.trigger("custom", true)
    rb.lcd_clear_display()

    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()
        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
            _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.sleep(rb.HZ / 2)
    end
    rockev.trigger("custom", false) -- have to manually reset custom events
    rb.splash(100, "exiting")
end

main()

Title: Re: Go to specific time in audio file
Post by: bahus on October 06, 2019, 12:59:55 AM
Thanks for your efforts! But from my understanding your script doesn't support actual seek to specific time? I don't need ff\rw functionality.
All I need is to enter somehow time (for instance 1530 seconds or ideally 25 min 30 sec) - and script should start playing track from this time.

Will try to dig in your script.
Title: Re: Go to specific time in audio file
Post by: bahus on October 06, 2019, 08:07:28 AM
I've been able to implement functionality I need. Thanks again!

For some reasons after plugin exit - sometime it gives me segmentation fault or Terminated [someNumber] error (AgpTeck Rocker device). So if you can tell me how it can be stabilized - would be great.

Attaching script I've made. It requires track to be played otherwise it exits immediately.
Up/Down: increase/decrease value by 1
Long Up/Down: increase/decrease value by 10 (Can Volume Up/Down be assigned for this action? I see no PLA_ values  for it)
Left/Right: switch hours/mins/seconds
Select: Seek to entered time

GoTo.lua (https://gist.github.com/bahusoid/480b40adb297f01dd2e0a7503246a4f8)
Title: Re: Go to specific time in audio file
Post by: Bilgus on October 07, 2019, 12:57:42 AM
What is the error code?
does the original script crash as well?


in your script:
you can use button_get + button codes for the volume buttons I don't think PLA actions are mapped for Vol+/-
or local evb = rockev.register("button", CALLBACK)
math.floor does nothing for integer math except make your script slower
lua is terrible with strings; each '..' you use creates another string copy that needs to be collected, use tables & table.concat or even string.format instead

line 341
Code: [Select]
print.f("Go To: %s", time_formatted(goToMs, goToMode), time_formatted(elapsed or 0), time_formatted(track_length or 0))
you only have one format specifier but three args?
I don't remember how lua handles it behind the scenes but I'm pretty sure time_formatted will still get called 3x
Title: Re: Go to specific time in audio file
Post by: Bilgus on October 07, 2019, 01:18:08 AM
one more thought..
you might want to check goToMs against TrackLength
I don't think rockbox checks for a valid value..
Code: [Select]
audio_seek(goToMs)
see audio_ff_rew() (which you should be able to use directly for your purpose...)
Title: Re: Go to specific time in audio file
Post by: bahus on October 07, 2019, 02:19:32 AM
Regarding errors. I see such issue with your initial script. In problematic scenario when I exit script - I see some artifacts on screen (see attached image). And rockbox become very unstable - do few actions and boom - Segmentation fault or Terminated error and reboot.
Title: Re: Go to specific time in audio file
Post by: bahus on October 07, 2019, 03:04:31 AM
I've uploaded  script GoTo.lua to Gist (https://gist.github.com/bahusoid/480b40adb297f01dd2e0a7503246a4f8) .

I've tried to clean it up and removed most not used code. But it's still pretty unstable - problem can be reproduced quite easily. Just stop playing track and execute the script multiple time (it should quit immediately) - I see  screen artifact and/or segmentation fault after 5-10 script executions.
Title: Re: Go to specific time in audio file
Post by: Bilgus on October 07, 2019, 03:32:48 AM
I only have the sim to test the agptek rocker and I can't reproduce it there

if you make a short lua script something like a single rb.sleep(0) does it do the same?
Title: Re: Go to specific time in audio file
Post by: Bilgus on October 07, 2019, 03:42:34 AM
also try putting os.exit(0) at the end of your goto script it might be somehow double freeing the lua state
Title: Re: Go to specific time in audio file
Post by: bahus on October 07, 2019, 03:46:01 AM
rb.sleep(0) is stable. I also tried with scripts present in rockbox (fade2sleep.lua) - also work OK. So seems like something specific in this script.
Title: Re: Go to specific time in audio file
Post by: Bilgus on October 07, 2019, 03:52:00 AM
probably the image routines if I had to guess.
If you can narrow it down to the minimally reproducible form
I can try and find/fix what ever is going on in the mean time I'll try to reproduce it on one of my players
Title: Re: Go to specific time in audio file
Post by: Bilgus on October 07, 2019, 03:54:25 AM
the event stuff is also pretty new lua should clean it up automatically at script exit but that doesn't mean it is you might try
rockev.unregister(evX) for each of the events as well
Title: Re: Go to specific time in audio file
Post by: bahus on October 07, 2019, 04:49:25 AM
I've updated GoTo.lua (https://gist.github.com/bahusoid/480b40adb297f01dd2e0a7503246a4f8) with your suggestions and more clean up.

>probably the image routines if I had to guess.

But I don't need  image processing. Can you take a look what else can be removed. I don't understand what's going on inside pbar_init. Can it be simplified somehow as I only need print strings?

Can you also clarify what it means in clear_actions: rockev.trigger("timer", false, rb.current_tick() + rb.HZ * 60)
In specification (https://www.rockbox.org/wiki/PluginLuaRockev):
Code: [Select]
trigger ("event", [true/false], [id]) CUSTOM_EVENT must be unset manually

So shouldn't it disable timer? Then why supply rb.current_tick() + rb.HZ * 60 for id?
Title: Re: Go to specific time in audio file
Post by: bahus on October 07, 2019, 05:12:38 AM
> you might try rockev.unregister(evX) for each of the events as well

Hm.. I still was able to reproduce the crash but it's definitely more stable now!! I was able to run script without crash more than 20 times

Also I needed to comment rockev.trigger("custom", false) as otherwise script is stuck on rockev.unregister(evt). Is it OK?
Title: Re: Go to specific time in audio file
Post by: bahus on October 07, 2019, 07:58:44 AM
I've been able to get rid of pbar_init and left single rockev.register call (GoTo.lua (https://gist.github.com/bahusoid/480b40adb297f01dd2e0a7503246a4f8))
But error is still reproducible (rare but still) even when no track is played

So it seems rockev.register is the cause of issue. If I modify script to skip registration I can't reproduce the issue when no track is played:
Code: [Select]
    local eva
    if(track_length > 0) then
      eva =rockev.register("action", action_event)
    end
...
    if(eva ~= nil) then
      rockev.unregister(eva)
    end


Title: Re: Go to specific time in audio file
Post by: bahus on October 07, 2019, 12:53:16 PM
I've finished button based version (link is the same GoTo.lua (https://gist.github.com/bahusoid/480b40adb297f01dd2e0a7503246a4f8)). It's quite stable for me and allows using Vol Up/Down buttons. So thanks again for you help!
Title: Re: Go to specific time in audio file
Post by: Bilgus on October 08, 2019, 02:26:32 AM
hmm that sucks

my changes fix the problem you mentioned of unregister hanging the script

does a simple script like..

Code: [Select]
function action_event(action)
end

local eva =rockev.register("action", action_event)
rb.sleep(100)

crash in the same manner?

All I can think is the timer callback is called after free but I'm pretty sure I negated that :/

Thanks for testing!
Title: Re: Go to specific time in audio file
Post by: bahus on October 08, 2019, 03:03:55 AM
> does a simple script crash in the same manner?
Yep. Eventually
Title: Re: Go to specific time in audio file
Post by: bahus on October 08, 2019, 05:30:40 AM
I'm not C++ expert and don't know hardware specifics - but still I'm looking into rocklib_events.c  and let me ask a few questions (maybe stupid one) :)

It seems ev_data.thread_state can be modified by multiple threads. And such modifications are done without locking. Am I right?

In this case  expressions like
Code: [Select]
ev_data.thread_state |= ((ev_data.thread_state & THREAD_INPUTMASK) >> 16);

looks unsafe to me. It's not atomic operation and between expression calculations ev_data.thread_state can be modified. All such calculations should be done on temp variable (that's if assignment is atomic):
Code: [Select]
auto thread_state = ev_data.thread_state;
thread_state  |= ((thread_state & THREAD_INPUTMASK) >> 16)
ev_data.thread_state  = thread_state;

And in places like:
Code: [Select]
    while(ev_data.thread_state != THREAD_QUIT && lua_status(ev_data.L) == LUA_SUCCESS)
    {
        rev_lock_mtx();
...

When lock is acquired ev_data.thread_state can also be already modified. And additional checks are required after lock.

Maybe it also would be safer to have some not zero THREAD_QUIT flag - as now it's too easy to loose it with assignments without locks.
Title: Re: Go to specific time in audio file
Post by: Bilgus on October 08, 2019, 09:12:46 AM
lua is strictly single threaded it allows coroutines (which are in the same thread) but they can't yield across the C->lua interface so are unavailable anyways.
The callbacks are done in a shared lua state with separate execution stack but still the same thread as the main lua state (which essentially makes them coroutines)

The way I've gotten events to work is by setting a hook when a event is ready, this hook stops the lua state at a safe point (read re-entrant).

Ok so we now have the lua state stopped we yield to the event thread.

The event thread could be yielded by any sleep or blocking function which
would cede control and allow the lua state to continue before we finished.

So we lock a mutex to keep the event thread active till we have done our callbacks.

Callbacks are finished mutex unlocked lua hook disabled program execution continues.

On top of all this rb is cooperatively multi-threaded so there shouldn't be any unplanned context switches
and we lock out the only other thread that could have access to the thread state

I agree on the non zero thread_quit though






Title: Re: Go to specific time in audio file
Post by: bahus on October 08, 2019, 11:40:05 AM
Ok thanks for explanation. But still something is not right on Rocker... :) What about rev_timer_isr timer. It seems it's destroyed only on event_error. Are you sure it's properly destroyed on script exit. Can't it be the case for memory corruption?
Title: Re: Go to specific time in audio file
Post by: Bilgus on October 08, 2019, 12:10:30 PM
The timer is also unregistered when the thread exits, that is what THREAD_QUIT is for it makes the loops fall through to the end of the function which then calls timer_unregister() and finally thread_exit()

exit_event_thread() is supposed to wait till the thread exits at thread_wait() which is not supposed to return till the thread calls thread_exit()

the thing is this all works fine on 3 arm devices, the simulators and the sdl app
so I'm not sure whats up with the rocker but it kinda throws a wrench in my whole events scheme!

I'll send you a version without the timer...
Title: Re: Go to specific time in audio file
Post by: bahus on October 08, 2019, 02:25:27 PM
Yep - crash is gone. Also my event based goto script stopped reacted to buttons. I assume it's due to disabled timer.
Title: Re: Go to specific time in audio file
Post by: Bilgus on October 08, 2019, 02:33:35 PM
yeah without the timer there are no events fired
I'll have to think on this a bit how to best get events to trigger in a periodic fashion without using a timer

Title: Re: Go to specific time in audio file
Post by: Bilgus on October 09, 2019, 01:19:03 PM
Ok so I figured out how I can do the calls without the timer but it's not very precise at all but it works

While working on that though I came to the realization there is a time when the thread could get called into and corrupt the lua state
so here is one last try at the previous method if you would be so kind as to test

Link Removed
Title: Re: Go to specific time in audio file
Post by: bahus on October 09, 2019, 01:44:03 PM
Crash is still reproducible... Ready to test any number of tries :)
Title: Re: Go to specific time in audio file
Post by: bahus on October 09, 2019, 02:00:48 PM
lua_notimer - crash is gone. But it feels less responsive (maybe just imagining)
Title: Re: Go to specific time in audio file
Post by: Bilgus on October 09, 2019, 02:05:59 PM
well its settled then the timer is causing the issues

oh Its definitely less responsive without the timer
Now you are at the whim of the lua vm.

I'll try and figure out a way to add a better mechanism (read more precise)

Title: Re: Go to specific time in audio file
Post by: Bilgus on October 09, 2019, 02:33:23 PM
I started a thread over here http://forums.rockbox.org/index.php/topic,53009.0.html
Title: Re: Go to specific time in audio file
Post by: Bilgus on October 10, 2019, 10:57:36 AM
The issue with rock events turned out to be a problem with the timer on the Rocker (and potentially other hosted devices)
See above thread for details.