diff --git a/station_broadcast.lua b/station_broadcast.lua index 90753b4..5d0495b 100644 --- a/station_broadcast.lua +++ b/station_broadcast.lua @@ -1,6 +1,6 @@ -- 1. Configuration & Constants -local APP_VERSION_TEXT = "v1.0.49" +local APP_VERSION_TEXT = "v1.0.50" local CONFIG = { CLOUD_URL = "ws://stabadmin.fse-media.group", @@ -32,6 +32,8 @@ local COLORS = { SUCCESS = colors.green } +local NATIVE_AUDIO_FRAME_SAMPLES = 128 + -- 2. State Management @@ -1170,46 +1172,70 @@ end local function playChunkOnSpeakers(samples, volume, playSeq, waitForDrain) if not Peripherals.speakers or #Peripherals.speakers == 0 then return false end if waitForDrain == nil then waitForDrain = true end - local pending = {} + local totalSamples = #samples + if totalSamples == 0 then return true end + + local speakerMap = {} + local nativeSpeakerCount = 0 for _, sp in ipairs(Peripherals.speakers) do local name = peripheral.getName(sp) - if name then pending[name] = sp end - end - - local usedNativePath = false - while next(pending) do - if shouldStop(playSeq) then return false end - for name, sp in pairs(pending) do - local ok = false - pcall(function() - if type(sp.playAudio) == "function" then - ok = sp.playAudio(samples, volume) - end - end) - if ok then - usedNativePath = true - pending[name] = nil + if name then + speakerMap[name] = sp + if type(sp.playAudio) == "function" then + nativeSpeakerCount = nativeSpeakerCount + 1 end end - if next(pending) then - local ev = { os.pullEvent() } - if ev[1] == "audio_interrupt" and tonumber(ev[2]) and tonumber(ev[2]) >= playSeq then + end + + local controller = Peripherals.speakers[1] + local usedNativePath = false + + for frameStart = 1, totalSamples, NATIVE_AUDIO_FRAME_SAMPLES do + local frameEnd = math.min(frameStart + NATIVE_AUDIO_FRAME_SAMPLES - 1, totalSamples) + local frame = {} + for i = frameStart, frameEnd do + frame[#frame + 1] = samples[i] + end + + if nativeSpeakerCount > 0 then + local pending = {} + for name, sp in pairs(speakerMap) do + if type(sp.playAudio) == "function" then + pending[name] = sp + end + end + + while next(pending) do + if shouldStop(playSeq) then return false end + for name, sp in pairs(pending) do + local ok = false + pcall(function() + ok = sp.playAudio(frame, volume) + end) + if ok then + usedNativePath = true + pending[name] = nil + end + end + if next(pending) then + local ev = { os.pullEvent() } + if ev[1] == "audio_interrupt" and tonumber(ev[2]) and tonumber(ev[2]) >= playSeq then + return false + end + end + end + else + if not controller or type(controller.playAudioAll) ~= "function" then return false end - end - end - - if usedNativePath then - Utils.log("INFO", string.format("Audio samples queued via native speaker.playAudio: samples=%d", #samples)) - else - local controller = Peripherals.speakers[1] - if controller and type(controller.playAudioAll) == "function" then - Utils.log("WARN", "Falling back to controller.playAudioAll for decoded samples") + if frameStart == 1 then + Utils.log("WARN", "Falling back to controller.playAudioAll for decoded samples") + end while true do if shouldStop(playSeq) then return false end local ok = false pcall(function() - ok = controller.playAudioAll(samples, volume) + ok = controller.playAudioAll(frame, volume) end) if ok then break end local ev = { os.pullEvent() } @@ -1217,11 +1243,13 @@ local function playChunkOnSpeakers(samples, volume, playSeq, waitForDrain) return false end end - else - return false end end + if usedNativePath then + Utils.log("INFO", string.format("Audio samples queued via native speaker.playAudio: samples=%d frame=%d", totalSamples, NATIVE_AUDIO_FRAME_SAMPLES)) + end + if not waitForDrain then return true end return waitForNativeSpeakersEmpty(Peripherals.speakers, playSeq) end