From 8edb021b70f2f030faccefceb410e4100cf0e289 Mon Sep 17 00:00:00 2001 From: nnwang <10577303+nnwang@user.noreply.gitee.com> Date: Sun, 8 Feb 2026 15:29:09 +0800 Subject: [PATCH] =?UTF-8?q?HTTP=E7=BC=93=E5=AD=98=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E5=BC=82=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- play.lua | 146 ++++++++++++++++++++++++++++--------------------- speakerlib.lua | 1 + 2 files changed, 85 insertions(+), 62 deletions(-) diff --git a/play.lua b/play.lua index f5d5081..cb26d59 100644 --- a/play.lua +++ b/play.lua @@ -3,6 +3,7 @@ local gpu = peripheral.wrap("tm_gpu_0") gpu.refreshSize() gpu.setSize(64) 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 server_url = "https://newgmapi.liulikeji.cn" @@ -15,7 +16,7 @@ if not videoUrl then return end - +_G.audio_ready = false local task_id local status_url @@ -240,10 +241,12 @@ print("Initial caching completed.") local frameDelay = tonumber(string.format("%.3f",1 / videoInfo.fps)) print("Starting playback (FPS: " .. videoInfo.fps .. ")") print(frameDelay * #videoInfo.frame_urls) + +repeat + sleep(0.05) +until _G.audio_ready + local starttime1 = os.clock() -sleep(4) - - -- 播放前已缓存 10 秒 @@ -257,95 +260,116 @@ local running = true -- 全局帧索引(由播放线程更新) local frameIndex = 1 --- 缓存协程:边播边下(并发下载多个 framepack) + +-- 共享状态(用于 cacheAhead 和 httpResponseHandler 通信) +local pendingRequests = {} -- url -> { batch = {...}, retry = N } +local downloadedFrames = {} -- frameIndex -> true (防止重复调度) + +-- 缓存协程:边播边下(使用异步 http.request) local function cacheAhead() while running do - -- 计算当前应缓存的范围 [frameIndex, frameIndex + maxCachedFrames) local currentStart = frameIndex local currentEnd = math.min(frameIndex + videoInfo.fps * 20 - 1, totalFramesToPlay) - -- 找出尚未缓存的帧段,按 BATCH_SIZE 切分 local batches = {} local i = currentStart while i <= currentEnd do - if not allFrameData[i] then + if not allFrameData[i] and not downloadedFrames[i] then local batchStart = i local batchEnd = math.min(batchStart + BATCH_SIZE - 1, currentEnd) - - -- 构造 URL 列表(相对路径) local urls = {} 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]) + downloadedFrames[j] = true end end - if #urls > 0 then table.insert(batches, { start = batchStart, - urls = urls + urls = urls, + retry = 0 }) end - i = batchEnd + 1 else i = i + 1 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) end + end +end - -- 并发下载所有缺失的批次 - downloadTasks = {} - for i, batch in ipairs(batches) do - table.insert(downloadTasks, function() - 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 - } - ) +-- HTTP 处理协程 +local function httpResponseHandler() + while running do + local event, url, handleOrErr = os.pullEvent() - if resp then - local binData = resp.readAll() + if event == "http_success" then + local batch = pendingRequests[url] + if batch then + pendingRequests[url] = nil + local binData = handleOrErr.readAll() + handleOrErr.close() - resp.close() - - - local batchFrames = unpackFramePack(binData) - - for idx = 1, #batchFrames do - local globalIdx = batch.start + idx - 1 - if not allFrameData[globalIdx] then - allFrameData[globalIdx] = batchFrames[idx] - end + local success, batchFrames = pcall(unpackFramePack, binData) + if success then + for idx = 1, #batchFrames do + local globalIdx = batch.start + idx - 1 + if not allFrameData[globalIdx] then + allFrameData[globalIdx] = batchFrames[idx] end - print("Cached batch: " .. batch.start .. " - " .. (batch.start + #batchFrames - 1)) - break - else - print("Failed to cache batch starting at " .. batch.start) + end + print("[V] Cached batch: " .. batch.start .. " - " .. (batch.start + #batchFrames - 1)) + else + 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 + + 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 - - -- 并发执行所有下载任务 - parallel.waitForAll(table.unpack(downloadTasks)) - - -- 检查是否已缓存到最后一帧 - if currentEnd >= #videoInfo.frame_urls then - break - end - - -- 小睡 - sleep(0.2) end end @@ -416,11 +440,8 @@ local function renderVideo() local play_time = tonumber(string.format("%.3f",os.clock() - starttime)) - - frameIndex2 = math.ceil(play_time / frameDelay) - if frameIndex2 + (frameIndex - frameIndex2) * 0.5 >= 4 then frameIndex = frameIndex2 else @@ -435,7 +456,8 @@ end -- 启动协程 -parallel.waitForAll(renderVideo, cacheAhead) +-- 启动三个协程:渲染 + 缓存调度 + HTTP 响应处理 +parallel.waitForAny(renderVideo, cacheAhead, httpResponseHandler) local endtime = os.clock() local time = endtime-starttime1 diff --git a/speakerlib.lua b/speakerlib.lua index ae620c2..63176ad 100644 --- a/speakerlib.lua +++ b/speakerlib.lua @@ -201,6 +201,7 @@ elseif cmd == "play" then end -- 主播放循环 + _G.audio_ready = true os.pullEvent("audio_start") while bytes_read < total_size and _G.Playopen do -- 检查是否需要设置播放位置