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:
+45
-67
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user