forked from hkc/cc-stuff
Added video+audio player
This commit is contained in:
parent
a00dca3be1
commit
f6b91af2ee
|
@ -0,0 +1,222 @@
|
||||||
|
local args = { ... }
|
||||||
|
local dfpwm = require("cc.audio.dfpwm")
|
||||||
|
local ccpi = require("ccpi")
|
||||||
|
|
||||||
|
local monitor = peripheral.find("monitor")
|
||||||
|
local speakers = {
|
||||||
|
l = peripheral.find("speaker"),
|
||||||
|
r = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
local delay = 0
|
||||||
|
local loading_concurrency = 8
|
||||||
|
local buffer_size = 8192
|
||||||
|
|
||||||
|
while args[1] ~= nil and string.sub(args[1], 1, 1) == "-" do
|
||||||
|
local k = table.remove(args, 1):sub(2)
|
||||||
|
if k == "m" or k == "monitor" then
|
||||||
|
monitor = peripheral.wrap(table.remove(args, 1))
|
||||||
|
elseif k == "l" or k == "speaker-left" then
|
||||||
|
speakers.l = peripheral.wrap(table.remove(args, 1))
|
||||||
|
elseif k == "r" or k == "speaker-right" then
|
||||||
|
speakers.l = peripheral.wrap(table.remove(args, 1))
|
||||||
|
elseif k == "d" or k == "delay" then
|
||||||
|
delay = tonumber(table.remove(args, 1))
|
||||||
|
elseif k == "t" or k == "threads" then
|
||||||
|
loading_concurrency = tonumber(table.remove(args, 1))
|
||||||
|
elseif k == "b" or k == "buffer-size" then
|
||||||
|
buffer_size = tonumber(table.remove(args, 1))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not monitor then
|
||||||
|
printError("No monitor connected or invalid one specified")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not speakers.l then
|
||||||
|
printError("No speaker connected or invalid one specified")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if #args < 3 then
|
||||||
|
printError("Usage: video [-m MONITOR] [-l SPK_L] [-r SPK_R] [-d FRAME_T] <N_FRAMES> <VIDEO_TEMPLATE> <LEFT_CHANNEL> [RIGHT_CHANNEL]")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
print(string.format("Using monitor %s", peripheral.getName(monitor)))
|
||||||
|
if speakers.r then
|
||||||
|
print("Stereo sound: L=%s R=%s", peripheral.getName(speakers.l), peripheral.getName(speakers.r))
|
||||||
|
else
|
||||||
|
print("Mono sound: " .. peripheral.getName(speakers.l))
|
||||||
|
end
|
||||||
|
|
||||||
|
local n_frames = tonumber(table.remove(args, 1))
|
||||||
|
local video_url = table.remove(args, 1)
|
||||||
|
local audio_url_l = table.remove(args, 1)
|
||||||
|
local audio_url_r = #args > 0 and table.remove(args, 1) or nil
|
||||||
|
|
||||||
|
if not speakers.r and audio_url_r then
|
||||||
|
printError("No right speaker found but right audio channel was specified")
|
||||||
|
printError("Right channel will not be played")
|
||||||
|
elseif speakers.r and not audio_url_r then
|
||||||
|
printError("No URL for right channel was specified but right speaker is set")
|
||||||
|
printError("Right speaker will remain silent")
|
||||||
|
end
|
||||||
|
|
||||||
|
print("\n\n")
|
||||||
|
local _, ty = term.getCursorPos()
|
||||||
|
local tw, _ = term.getSize()
|
||||||
|
|
||||||
|
local function draw_bar(y, c1, c2, p, fmt, ...)
|
||||||
|
local str = string.format(fmt, ...)
|
||||||
|
local w1 = math.ceil(p * tw)
|
||||||
|
local w2 = tw - w1
|
||||||
|
|
||||||
|
term.setCursorPos(1, y)
|
||||||
|
term.setBackgroundColor(c1)
|
||||||
|
term.write(str:sub(1, w1))
|
||||||
|
local rem = w1 - #str
|
||||||
|
if rem > 0 then
|
||||||
|
term.write(string.rep(" ", rem))
|
||||||
|
end
|
||||||
|
|
||||||
|
term.setBackgroundColor(c2)
|
||||||
|
term.write(str:sub(w1 + 1, w1 + w2))
|
||||||
|
|
||||||
|
rem = math.min(tw - #str, w2)
|
||||||
|
if rem > 0 then
|
||||||
|
term.write(string.rep(" ", rem))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function decode_s8(data)
|
||||||
|
local buffer = {}
|
||||||
|
for i = 1, #data do
|
||||||
|
local v = string.byte(data, i)
|
||||||
|
if bit32.band(v, 0x80) then
|
||||||
|
v = bit32.bxor(v, 0x7F) - 128
|
||||||
|
end
|
||||||
|
buffer[i] = v
|
||||||
|
end
|
||||||
|
return buffer
|
||||||
|
end
|
||||||
|
|
||||||
|
local frames = {}
|
||||||
|
local audio_frames = { l = {}, r = {} }
|
||||||
|
|
||||||
|
local subthreads = {}
|
||||||
|
local dl_channels = {
|
||||||
|
l = assert(http.get(audio_url_l, nil, true)),
|
||||||
|
r = audio_url_r and assert(http.get(audio_url_r, nil, true)) or nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
local n_audio_samples = math.ceil(dl_channels.l.seek("end") / buffer_size)
|
||||||
|
dl_channels.l.seek("set", 0)
|
||||||
|
|
||||||
|
table.insert(subthreads, function()
|
||||||
|
local chunk
|
||||||
|
repeat
|
||||||
|
chunk = dl_channels.l.read(buffer_size)
|
||||||
|
table.insert(audio_frames.l, chunk or {})
|
||||||
|
if (#audio_frames.l % 8) == 0 then os.sleep(0) end
|
||||||
|
until not chunk or #chunk == 0
|
||||||
|
end)
|
||||||
|
|
||||||
|
if dl_channels.r then
|
||||||
|
table.insert(subthreads, function()
|
||||||
|
local chunk
|
||||||
|
repeat
|
||||||
|
chunk = dl_channels.r.read(buffer_size)
|
||||||
|
table.insert(audio_frames.r, chunk or {})
|
||||||
|
if (#audio_frames.r % 8) == 0 then os.sleep(0) end
|
||||||
|
until not chunk or #chunk == 0
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, loading_concurrency do
|
||||||
|
table.insert(subthreads, function()
|
||||||
|
for ndx = i, n_frames, loading_concurrency do
|
||||||
|
local req = assert(http.get(string.format(video_url, ndx), nil, true))
|
||||||
|
local img = assert(ccpi.parse(req))
|
||||||
|
frames[ndx] = img
|
||||||
|
req.close()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(subthreads, function()
|
||||||
|
while #frames ~= n_frames or #audio_frames.l < n_audio_samples do
|
||||||
|
draw_bar(ty - 3, colors.blue, colors.gray, #frames / n_frames, "Loading video [%5d / %5d]", #frames, n_frames)
|
||||||
|
draw_bar(ty - 2, colors.red, colors.gray, #audio_frames.l / n_audio_samples, "Loading audio [%5d / %5d]", #audio_frames.l, n_audio_samples)
|
||||||
|
os.sleep(0.25)
|
||||||
|
end
|
||||||
|
print()
|
||||||
|
end)
|
||||||
|
|
||||||
|
local playback_locked = true
|
||||||
|
local playback_done = false
|
||||||
|
|
||||||
|
table.insert(subthreads, function()
|
||||||
|
while #frames < 60 or #audio_frames.l < n_audio_samples do
|
||||||
|
term.setCursorPos(1, ty - 1)
|
||||||
|
term.setBackgroundColor(colors.gray)
|
||||||
|
term.clearLine()
|
||||||
|
term.write(string.format("Waiting for frames... (V:%d, A:%d)", #frames, #audio_frames.l))
|
||||||
|
os.sleep(0.25)
|
||||||
|
end
|
||||||
|
playback_locked = false
|
||||||
|
end)
|
||||||
|
|
||||||
|
table.insert(subthreads, function()
|
||||||
|
while playback_locked do os.sleep(0) end -- spin
|
||||||
|
end)
|
||||||
|
|
||||||
|
table.insert(subthreads, function()
|
||||||
|
local is_dfpwm = ({ audio_url_l:find("%.dfpwm") })[2] == #audio_url_l
|
||||||
|
local decode = use_dfpwm and dfpwm.make_decoder() or decode_s8
|
||||||
|
while playback_locked do os.sleep(0) end -- spin
|
||||||
|
|
||||||
|
for i = 1, n_audio_samples do
|
||||||
|
local buffer = decode(audio_frames.l[i])
|
||||||
|
while not speakers.l.playAudio(buffer) do
|
||||||
|
os.pullEvent("speaker_audio_empty")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
playback_done = true
|
||||||
|
end)
|
||||||
|
|
||||||
|
table.insert(subthreads, function()
|
||||||
|
if not audio_url_r then return end
|
||||||
|
local is_dfpwm = ({ audio_url_r:find("%.dfpwm") })[2] == #audio_url_r
|
||||||
|
local decode = use_dfpwm and dfpwm.make_decoder() or decode_s8
|
||||||
|
|
||||||
|
while playback_locked do os.sleep(0) end -- spin
|
||||||
|
|
||||||
|
for i = 1, n_audio_samples do
|
||||||
|
local buffer = decode(audio_frames.r[i])
|
||||||
|
while not speakers.r.playAudio(buffer) do
|
||||||
|
os.pullEvent("speaker_audio_empty")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
playback_done = true
|
||||||
|
end)
|
||||||
|
|
||||||
|
table.insert(subthreads, function()
|
||||||
|
while playback_locked do os.sleep(0) end -- spin
|
||||||
|
|
||||||
|
local start_t = os.clock()
|
||||||
|
while not playback_done do
|
||||||
|
local frame = math.floor((os.clock() - start_t) / math.max(0.05, delay))
|
||||||
|
term.setCursorPos(1, ty - 1)
|
||||||
|
term.setBackgroundColor(colors.gray)
|
||||||
|
term.clearLine()
|
||||||
|
term.write(string.format("Playing frame: %d/%d", frame + 1, #frames))
|
||||||
|
ccpi.draw(frames[frame + 1], 1, 1, monitor)
|
||||||
|
os.sleep(delay)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
parallel.waitForAll(table.unpack(subthreads))
|
||||||
|
|
Loading…
Reference in New Issue