From 849b43af39cb910364bf507f5e6e84fea5482fd6 Mon Sep 17 00:00:00 2001 From: HenryDu8133 <813367384@qq.com> Date: Mon, 17 Aug 2026 14:30:54 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=9F=B3=E4=B9=90?= =?UTF-8?q?=E5=92=8C=E4=B8=B4=E6=97=B6=E5=B9=BF=E6=92=AD=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E6=92=AD=E6=94=BE(AUDIO=20FREE)=E7=9A=84=E9=97=AE=E9=A2=98=20-?= =?UTF-8?q?=20=E9=87=8D=E5=86=99=20waitForInterruptibleDelay/waitForNative?= =?UTF-8?q?SpeakersEmpty=20=E4=BD=BF=E7=94=A8=20os.clock()=20=20=20?= =?UTF-8?q?=E8=BD=AE=E8=AF=A2=EF=BC=8C=E6=9B=BF=E6=8D=A2=E4=BC=9A=E6=8A=A2?= =?UTF-8?q?=E5=8D=A0=20websocket=5Fmessage=20=E4=BA=8B=E4=BB=B6=E7=9A=84?= =?UTF-8?q?=20os.pullEvent()=20-=20=E9=87=8D=E5=86=99=20queueSamplesSynchr?= =?UTF-8?q?onized/playChunkOnSpeakers=20=E9=87=8D=E8=AF=95=E5=BE=AA?= =?UTF-8?q?=E7=8E=AF=E4=BD=BF=E7=94=A8=20=20=20os.sleep()=20=E8=BD=AE?= =?UTF-8?q?=E8=AF=A2=EF=BC=8C=E4=B8=8D=E5=86=8D=E8=B0=83=E7=94=A8=20pullEv?= =?UTF-8?q?ent=20=E5=90=9E=E6=8E=89=E9=9F=B3=E9=A2=91=E5=88=86=E5=9D=97?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=20-=20=E9=87=8D=E5=86=99=20Audio.createStrea?= =?UTF-8?q?m().read()=20=E4=B8=8E=20Audio.playerLoop=20=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=20sleep=20=E8=BD=AE=E8=AF=A2=EF=BC=8C=20=20=20=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E4=BD=BF=E7=94=A8=20os.pullEvent('audio=5Fchunk')/'au?= =?UTF-8?q?dio=5Fqueue'=20=E6=8A=A2=E4=BA=8B=E4=BB=B6=20-=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=20Audio.playItem=20=E5=B0=86=20wav-stream/wav-stream-?= =?UTF-8?q?live=20=E9=94=99=E8=AF=AF=E8=B7=AF=E7=94=B1=E5=88=B0=20=20=20pl?= =?UTF-8?q?ayPcmS16LEItem=EF=BC=8C=E6=94=B9=E4=B8=BA=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=88=B0=20playWavStreamItem=EF=BC=88=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E7=AB=AF=E5=88=86=E5=9D=97=E6=AF=8F=E5=9D=97=20=20=20=E5=B8=A6?= =?UTF-8?q?=E5=AE=8C=E6=95=B4=2044=20=E5=AD=97=E8=8A=82=20WAV=20=E5=A4=B4?= =?UTF-8?q?=EF=BC=8C=E9=9C=80=20decodeWavChunkToSamples=EF=BC=89=20-=20Net?= =?UTF-8?q?work.messageLoop=20audio=5Fstart=20=E5=88=86=E6=94=AF=E5=B0=86?= =?UTF-8?q?=20wav-stream-live=20=E5=8A=A0=E5=85=A5=E6=B5=81=E5=BC=8F=20=20?= =?UTF-8?q?=20=E6=8E=A5=E6=94=B6=E5=88=A4=E5=AE=9A=EF=BC=8C=E9=81=BF?= =?UTF-8?q?=E5=85=8D=E8=B5=B0=20chunks=20=E7=BC=93=E5=86=B2=E9=80=BB?= =?UTF-8?q?=E8=BE=91=20-=20=E7=89=88=E6=9C=AC=E5=8F=B7=E5=8D=87=E7=BA=A7?= =?UTF-8?q?=E8=87=B3=20v1.0.97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- station_broadcast.lua | 118 +++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 70 deletions(-) diff --git a/station_broadcast.lua b/station_broadcast.lua index 9791e47..6beb7b8 100644 --- a/station_broadcast.lua +++ b/station_broadcast.lua @@ -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 type(sp.speakQueueSize) == "function" then + queued = tonumber(sp.speakQueueSize()) or 0 + end + end) + if playing or queued > 0 then + allEmpty = false + break + end end - if ev[1] == "timer" and ev[2] == timerId then - Utils.log("WARN", "Timeout waiting for native speakers to empty") - break - end - if ev[1] == "speaker_audio_empty" and ev[2] and waiting[ev[2]] then - waiting[ev[2]] = nil + 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 - break - end + maxAttempts = maxAttempts - 1 + if maxAttempts <= 0 then + Utils.log("WARN", "Speaker buffer full or failed, skipping chunk (sync)") + break 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({