fix: 修复音乐和临时广播无法播放(AUDIO FREE)的问题

- 重写 waitForInterruptibleDelay/waitForNativeSpeakersEmpty 使用 os.clock()
  轮询,替换会抢占 websocket_message 事件的 os.pullEvent()
- 重写 queueSamplesSynchronized/playChunkOnSpeakers 重试循环使用
  os.sleep() 轮询,不再调用 pullEvent 吞掉音频分块事件
- 重写 Audio.createStream().read() 与 Audio.playerLoop 使用 sleep 轮询,
  不再使用 os.pullEvent('audio_chunk')/'audio_queue' 抢事件
- 修复 Audio.playItem 将 wav-stream/wav-stream-live 错误路由到
  playPcmS16LEItem,改为路由到 playWavStreamItem(服务端分块每块
  带完整 44 字节 WAV 头,需 decodeWavChunkToSamples)
- Network.messageLoop audio_start 分支将 wav-stream-live 加入流式
  接收判定,避免走 chunks 缓冲逻辑
- 版本号升级至 v1.0.97
This commit is contained in:
2026-08-17 14:30:54 +08:00
parent e9819d4894
commit 849b43af39
+45 -67
View File
@@ -1,6 +1,6 @@
-- 1. Configuration & Constants
local APP_VERSION_TEXT = "v1.0.76"
local APP_VERSION_TEXT = "v1.0.97"
local CONFIG = {
CLOUD_URL = "ws://stabadmin.fse-media.group",
@@ -1053,18 +1053,16 @@ function Audio.createStream()
stream.push = function(chunk)
if type(chunk) ~= "string" then return end
table.insert(chunks, chunk)
os.queueEvent("audio_chunk")
end
stream.finish = function()
finished = true
os.queueEvent("audio_chunk")
end
stream.read = function()
while #chunks == 0 do
if finished then return nil end
os.pullEvent("audio_chunk")
os.sleep(0.02)
end
return table.remove(chunks, 1)
end
@@ -1156,7 +1154,6 @@ function Audio.enqueueItem(item)
idx = idx + 1
end
table.insert(State.audioQueue, idx, item)
os.queueEvent("audio_queue")
end
function Audio.stop(mode, flushBelow)
@@ -1194,41 +1191,46 @@ local function shouldStop(playSeq)
end
local function waitForInterruptibleDelay(playSeq, seconds)
local timer = os.startTimer(seconds)
while true do
local ev = { os.pullEvent() }
if ev[1] == "audio_interrupt" and tonumber(ev[2]) and tonumber(ev[2]) >= playSeq then
local endTime = os.clock() + seconds
while os.clock() < endTime do
if shouldStop(playSeq) then
return false
end
if ev[1] == "timer" and ev[2] == timer then
return true
end
local remaining = endTime - os.clock()
if remaining <= 0 then break end
local sleepTime = math.min(remaining, 0.02)
os.sleep(sleepTime)
end
return not shouldStop(playSeq)
end
local function waitForNativeSpeakersEmpty(speakers, playSeq)
local waiting = {}
for _, sp in ipairs(speakers or {}) do
local name = peripheral.getName(sp)
if name then waiting[name] = true end
end
local timerId = os.startTimer(2.0) -- Maximum 2 seconds wait for empty
while next(waiting) do
local maxWait = os.clock() + 2.0
while os.clock() < maxWait do
if shouldStop(playSeq) then return false end
local ev = { os.pullEvent() }
if ev[1] == "audio_interrupt" and tonumber(ev[2]) and tonumber(ev[2]) >= playSeq then
return false
local allEmpty = true
for _, sp in ipairs(speakers or {}) do
local playing = false
local queued = 0
pcall(function()
if type(sp.speakIsPlaying) == "function" then
playing = sp.speakIsPlaying() and true or false
end
if ev[1] == "timer" and ev[2] == timerId then
Utils.log("WARN", "Timeout waiting for native speakers to empty")
if type(sp.speakQueueSize) == "function" then
queued = tonumber(sp.speakQueueSize()) or 0
end
end)
if playing or queued > 0 then
allEmpty = false
break
end
if ev[1] == "speaker_audio_empty" and ev[2] and waiting[ev[2]] then
waiting[ev[2]] = nil
end
if allEmpty then
return true
end
os.sleep(0.02)
end
Utils.log("WARN", "Timeout waiting for native speakers to empty (polling)")
return true
end
@@ -1246,6 +1248,7 @@ local function queueSamplesSynchronized(samples, volume, playSeq)
end
end
local maxAttempts = 400
while next(pending) do
if shouldStop(playSeq) then return false end
for name, sp in pairs(pending) do
@@ -1258,17 +1261,12 @@ local function queueSamplesSynchronized(samples, volume, playSeq)
end
end
if next(pending) then
local timerId = os.startTimer(0.05)
while true do
local ev = { os.pullEvent() }
if ev[1] == "audio_interrupt" and tonumber(ev[2]) and tonumber(ev[2]) >= playSeq then
return false
elseif ev[1] == "timer" and ev[2] == timerId then
break
elseif ev[1] == "speaker_audio_empty" then
maxAttempts = maxAttempts - 1
if maxAttempts <= 0 then
Utils.log("WARN", "Speaker buffer full or failed, skipping chunk (sync)")
break
end
end
os.sleep(0.05)
end
end
@@ -1286,7 +1284,7 @@ local function playChunkOnSpeakers(samples, volume, playSeq, waitForDrain)
end
local usedNativePath = false
local maxAttempts = 20
local maxAttempts = 400
while next(pending) do
if shouldStop(playSeq) then return false end
for name, sp in pairs(pending) do
@@ -1307,17 +1305,7 @@ local function playChunkOnSpeakers(samples, volume, playSeq, waitForDrain)
Utils.log("WARN", "Speaker buffer full or failed, skipping chunk")
break
end
local timerId = os.startTimer(0.05)
while true do
local ev = { os.pullEvent() }
if ev[1] == "audio_interrupt" and tonumber(ev[2]) and tonumber(ev[2]) >= playSeq then
return false
elseif ev[1] == "timer" and ev[2] == timerId then
break
elseif ev[1] == "speaker_audio_empty" then
break
end
end
os.sleep(0.05)
end
end
@@ -1327,7 +1315,7 @@ local function playChunkOnSpeakers(samples, volume, playSeq, waitForDrain)
local controller = Peripherals.speakers[1]
if controller and type(controller.playAudioAll) == "function" then
Utils.log("WARN", "Falling back to controller.playAudioAll for decoded samples")
local maxAttempts = 20
local maxAttempts2 = 400
while true do
if shouldStop(playSeq) then return false end
local ok = false
@@ -1335,22 +1323,12 @@ local function playChunkOnSpeakers(samples, volume, playSeq, waitForDrain)
ok = controller.playAudioAll(samples, volume)
end)
if ok then break end
maxAttempts = maxAttempts - 1
if maxAttempts <= 0 then
maxAttempts2 = maxAttempts2 - 1
if maxAttempts2 <= 0 then
Utils.log("WARN", "Controller buffer full or failed, skipping chunk")
break
end
local timerId = os.startTimer(0.05)
while true do
local ev = { os.pullEvent() }
if ev[1] == "audio_interrupt" and tonumber(ev[2]) and tonumber(ev[2]) >= playSeq then
return false
elseif ev[1] == "timer" and ev[2] == timerId then
break
elseif ev[1] == "speaker_audio_empty" then
break
end
end
os.sleep(0.05)
end
else
return false
@@ -2027,7 +2005,7 @@ function Audio.playItem(item)
elseif format == "pcm" then
return Audio.playPcmItem(item)
elseif format == "wav-stream" or format == "wav-stream-live" then
return Audio.playPcmS16LEItem(item)
return Audio.playWavStreamItem(item)
end
return Audio.playDfpwmItem(item)
end
@@ -2035,7 +2013,7 @@ end
function Audio.playerLoop()
while true do
if #State.audioQueue == 0 then
os.pullEvent("audio_queue")
os.sleep(0.05)
else
local item = table.remove(State.audioQueue, 1)
State.currentPlaying = item
@@ -2403,12 +2381,12 @@ function Network.messageLoop()
audioId = data.audioId
})
State.audioRx = nil
elseif audioFormat == "dfpwm" or audioFormat == "pcm" or audioFormat == "wav-stream" then
elseif audioFormat == "dfpwm" or audioFormat == "pcm" or audioFormat == "wav-stream" or audioFormat == "wav-stream-live" then
local stream = Audio.createStream()
State.audioRx.stream = stream
if audioFormat == "pcm" then
Utils.log("INFO", "Receiving live PCM audio stream")
elseif audioFormat == "wav-stream" then
elseif audioFormat == "wav-stream" or audioFormat == "wav-stream-live" then
Utils.log("INFO", "Receiving live WAV chunk stream")
end
Audio.enqueueItem({