HTTP缓存改为异步
This commit is contained in:
146
play.lua
146
play.lua
@@ -3,6 +3,7 @@ local gpu = peripheral.wrap("tm_gpu_0")
|
|||||||
gpu.refreshSize()
|
gpu.refreshSize()
|
||||||
gpu.setSize(64)
|
gpu.setSize(64)
|
||||||
local w, h = gpu.getSize()
|
local w, h = gpu.getSize()
|
||||||
|
|
||||||
if not fs.exists("speakerlib.lua") then shell.run("wget https://git.liulikeji.cn/xingluo/computer_craft_video_play/raw/branch/main/speakerlib.lua") end
|
if not fs.exists("speakerlib.lua") then shell.run("wget https://git.liulikeji.cn/xingluo/computer_craft_video_play/raw/branch/main/speakerlib.lua") end
|
||||||
|
|
||||||
server_url = "https://newgmapi.liulikeji.cn"
|
server_url = "https://newgmapi.liulikeji.cn"
|
||||||
@@ -15,7 +16,7 @@ if not videoUrl then
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
_G.audio_ready = false
|
||||||
local task_id
|
local task_id
|
||||||
local status_url
|
local status_url
|
||||||
|
|
||||||
@@ -240,10 +241,12 @@ print("Initial caching completed.")
|
|||||||
local frameDelay = tonumber(string.format("%.3f",1 / videoInfo.fps))
|
local frameDelay = tonumber(string.format("%.3f",1 / videoInfo.fps))
|
||||||
print("Starting playback (FPS: " .. videoInfo.fps .. ")")
|
print("Starting playback (FPS: " .. videoInfo.fps .. ")")
|
||||||
print(frameDelay * #videoInfo.frame_urls)
|
print(frameDelay * #videoInfo.frame_urls)
|
||||||
|
|
||||||
|
repeat
|
||||||
|
sleep(0.05)
|
||||||
|
until _G.audio_ready
|
||||||
|
|
||||||
local starttime1 = os.clock()
|
local starttime1 = os.clock()
|
||||||
sleep(4)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- 播放前已缓存 10 秒
|
-- 播放前已缓存 10 秒
|
||||||
@@ -257,95 +260,116 @@ local running = true
|
|||||||
-- 全局帧索引(由播放线程更新)
|
-- 全局帧索引(由播放线程更新)
|
||||||
local frameIndex = 1
|
local frameIndex = 1
|
||||||
|
|
||||||
-- 缓存协程:边播边下(并发下载多个 framepack)
|
|
||||||
|
-- 共享状态(用于 cacheAhead 和 httpResponseHandler 通信)
|
||||||
|
local pendingRequests = {} -- url -> { batch = {...}, retry = N }
|
||||||
|
local downloadedFrames = {} -- frameIndex -> true (防止重复调度)
|
||||||
|
|
||||||
|
-- 缓存协程:边播边下(使用异步 http.request)
|
||||||
local function cacheAhead()
|
local function cacheAhead()
|
||||||
while running do
|
while running do
|
||||||
-- 计算当前应缓存的范围 [frameIndex, frameIndex + maxCachedFrames)
|
|
||||||
local currentStart = frameIndex
|
local currentStart = frameIndex
|
||||||
local currentEnd = math.min(frameIndex + videoInfo.fps * 20 - 1, totalFramesToPlay)
|
local currentEnd = math.min(frameIndex + videoInfo.fps * 20 - 1, totalFramesToPlay)
|
||||||
|
|
||||||
-- 找出尚未缓存的帧段,按 BATCH_SIZE 切分
|
|
||||||
local batches = {}
|
local batches = {}
|
||||||
local i = currentStart
|
local i = currentStart
|
||||||
while i <= currentEnd do
|
while i <= currentEnd do
|
||||||
if not allFrameData[i] then
|
if not allFrameData[i] and not downloadedFrames[i] then
|
||||||
local batchStart = i
|
local batchStart = i
|
||||||
local batchEnd = math.min(batchStart + BATCH_SIZE - 1, currentEnd)
|
local batchEnd = math.min(batchStart + BATCH_SIZE - 1, currentEnd)
|
||||||
|
|
||||||
-- 构造 URL 列表(相对路径)
|
|
||||||
local urls = {}
|
local urls = {}
|
||||||
for j = batchStart, batchEnd do
|
for j = batchStart, batchEnd do
|
||||||
if not allFrameData[j] then
|
if not allFrameData[j] and not downloadedFrames[j] then
|
||||||
table.insert(urls, videoInfo.frame_urls[j])
|
table.insert(urls, videoInfo.frame_urls[j])
|
||||||
|
downloadedFrames[j] = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if #urls > 0 then
|
if #urls > 0 then
|
||||||
table.insert(batches, {
|
table.insert(batches, {
|
||||||
start = batchStart,
|
start = batchStart,
|
||||||
urls = urls
|
urls = urls,
|
||||||
|
retry = 0
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
i = batchEnd + 1
|
i = batchEnd + 1
|
||||||
else
|
else
|
||||||
i = i + 1
|
i = i + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 如果没有需要下载的批次,休眠后重试
|
-- 发起所有新请求(不等待!)
|
||||||
if #batches == 0 then
|
for _, batch in ipairs(batches) do
|
||||||
|
local url = server_url .. "/api/framepack?" .. batch.urls[1]
|
||||||
|
local body = textutils.serializeJSON({ urls = batch.urls })
|
||||||
|
http.request({
|
||||||
|
url = url,
|
||||||
|
headers = { ["Content-Type"] = "application/json" },
|
||||||
|
body = body,
|
||||||
|
timeout = 2,
|
||||||
|
binary = true
|
||||||
|
})
|
||||||
|
pendingRequests[url] = batch
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 如果没有要下载的,小睡
|
||||||
|
if next(batches) == nil then
|
||||||
sleep(0.5)
|
sleep(0.5)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- 并发下载所有缺失的批次
|
-- HTTP 处理协程
|
||||||
downloadTasks = {}
|
local function httpResponseHandler()
|
||||||
for i, batch in ipairs(batches) do
|
while running do
|
||||||
table.insert(downloadTasks, function()
|
local event, url, handleOrErr = os.pullEvent()
|
||||||
print("Downloading batch: " .. batch.start .. " - " .. (batch.start + #batch.urls - 1))
|
|
||||||
while true do
|
|
||||||
local resp = http.post({
|
|
||||||
url = server_url .. "/api/framepack?"..batch.urls[1],
|
|
||||||
headers = { ["Content-Type"] = "application/json" },
|
|
||||||
body = textutils.serializeJSON({ urls = batch.urls } ),
|
|
||||||
timeout = 2,
|
|
||||||
binary = true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if resp then
|
if event == "http_success" then
|
||||||
local binData = resp.readAll()
|
local batch = pendingRequests[url]
|
||||||
|
if batch then
|
||||||
|
pendingRequests[url] = nil
|
||||||
|
local binData = handleOrErr.readAll()
|
||||||
|
handleOrErr.close()
|
||||||
|
|
||||||
resp.close()
|
local success, batchFrames = pcall(unpackFramePack, binData)
|
||||||
|
if success then
|
||||||
|
for idx = 1, #batchFrames do
|
||||||
local batchFrames = unpackFramePack(binData)
|
local globalIdx = batch.start + idx - 1
|
||||||
|
if not allFrameData[globalIdx] then
|
||||||
for idx = 1, #batchFrames do
|
allFrameData[globalIdx] = batchFrames[idx]
|
||||||
local globalIdx = batch.start + idx - 1
|
|
||||||
if not allFrameData[globalIdx] then
|
|
||||||
allFrameData[globalIdx] = batchFrames[idx]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
print("Cached batch: " .. batch.start .. " - " .. (batch.start + #batchFrames - 1))
|
end
|
||||||
break
|
print("[V] Cached batch: " .. batch.start .. " - " .. (batch.start + #batchFrames - 1))
|
||||||
else
|
else
|
||||||
print("Failed to cache batch starting at " .. batch.start)
|
print("[R] Unpack failed for batch " .. batch.start .. ": " .. tostring(batchFrames))
|
||||||
|
-- 标记这些帧可重试
|
||||||
|
for j = batch.start, batch.start + #batch.urls - 1 do
|
||||||
|
downloadedFrames[j] = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
end
|
||||||
|
|
||||||
|
elseif event == "http_failure" then
|
||||||
|
local batch = pendingRequests[url]
|
||||||
|
if batch then
|
||||||
|
pendingRequests[url] = nil
|
||||||
|
batch.retry = (batch.retry or 0) + 1
|
||||||
|
if batch.retry < 3 then
|
||||||
|
print("[R] Retrying batch " .. batch.start .. " (attempt " .. (batch.retry + 1) .. ")")
|
||||||
|
-- 允许重试
|
||||||
|
for j = batch.start, batch.start + #batch.urls - 1 do
|
||||||
|
downloadedFrames[j] = nil
|
||||||
|
end
|
||||||
|
-- 重新触发 cacheAhead 下一轮会重发
|
||||||
|
else
|
||||||
|
print("[X] Giving up on batch " .. batch.start)
|
||||||
|
-- 永久失败,不再重试
|
||||||
|
for j = batch.start, batch.start + #batch.urls - 1 do
|
||||||
|
downloadedFrames[j] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 并发执行所有下载任务
|
|
||||||
parallel.waitForAll(table.unpack(downloadTasks))
|
|
||||||
|
|
||||||
-- 检查是否已缓存到最后一帧
|
|
||||||
if currentEnd >= #videoInfo.frame_urls then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
-- 小睡
|
|
||||||
sleep(0.2)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -416,11 +440,8 @@ local function renderVideo()
|
|||||||
|
|
||||||
local play_time = tonumber(string.format("%.3f",os.clock() - starttime))
|
local play_time = tonumber(string.format("%.3f",os.clock() - starttime))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
frameIndex2 = math.ceil(play_time / frameDelay)
|
frameIndex2 = math.ceil(play_time / frameDelay)
|
||||||
|
|
||||||
|
|
||||||
if frameIndex2 + (frameIndex - frameIndex2) * 0.5 >= 4 then
|
if frameIndex2 + (frameIndex - frameIndex2) * 0.5 >= 4 then
|
||||||
frameIndex = frameIndex2
|
frameIndex = frameIndex2
|
||||||
else
|
else
|
||||||
@@ -435,7 +456,8 @@ end
|
|||||||
|
|
||||||
|
|
||||||
-- 启动协程
|
-- 启动协程
|
||||||
parallel.waitForAll(renderVideo, cacheAhead)
|
-- 启动三个协程:渲染 + 缓存调度 + HTTP 响应处理
|
||||||
|
parallel.waitForAny(renderVideo, cacheAhead, httpResponseHandler)
|
||||||
local endtime = os.clock()
|
local endtime = os.clock()
|
||||||
local time = endtime-starttime1
|
local time = endtime-starttime1
|
||||||
|
|
||||||
|
|||||||
@@ -201,6 +201,7 @@ elseif cmd == "play" then
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- 主播放循环
|
-- 主播放循环
|
||||||
|
_G.audio_ready = true
|
||||||
os.pullEvent("audio_start")
|
os.pullEvent("audio_start")
|
||||||
while bytes_read < total_size and _G.Playopen do
|
while bytes_read < total_size and _G.Playopen do
|
||||||
-- 检查是否需要设置播放位置
|
-- 检查是否需要设置播放位置
|
||||||
|
|||||||
Reference in New Issue
Block a user