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({