From 6610b168d787e36dca515579b260848babb5d66a Mon Sep 17 00:00:00 2001 From: HenryDu8133 <813367384@qq.com> Date: Sat, 1 Aug 2026 00:25:26 +0800 Subject: [PATCH] fix(music): synchronize multi-speaker wav streaming Bump the station build to v1.0.5, reduce live WAV chunks to 0.25 seconds, and queue decoded samples in a synchronized multi-speaker mode so multiple speakers stay aligned during streaming playback. --- CloudServer/server.js | 2 +- station_broadcast.lua | 47 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/CloudServer/server.js b/CloudServer/server.js index 4c87be1..e972280 100644 --- a/CloudServer/server.js +++ b/CloudServer/server.js @@ -324,7 +324,7 @@ async function sendLiveWavChunksToStation(stationName, fallbackWs, task) { const sampleRate = 48000; const channels = 1; const bitsPerSample = 16; - const chunkDurationSec = 0.5; + const chunkDurationSec = 0.25; const chunkBytes = Math.max(4096, Math.floor(sampleRate * channels * (bitsPerSample / 8) * chunkDurationSec)); targetWs.send(JSON.stringify({ diff --git a/station_broadcast.lua b/station_broadcast.lua index 4e74fea..110ce8d 100644 --- a/station_broadcast.lua +++ b/station_broadcast.lua @@ -2,7 +2,7 @@ -- ============================================================================ -- 1. Configuration & Constants -- ============================================================================ -local APP_VERSION_TEXT = "v1.0.4" +local APP_VERSION_TEXT = "v1.0.5" local CONFIG = { CLOUD_URL = "ws://stabadmin.fse-media.group", @@ -612,6 +612,43 @@ local function waitForNativeSpeakersEmpty(speakers, playSeq) return true end +local function queueSamplesSynchronized(samples, volume, playSeq) + if not Peripherals.speakers or #Peripherals.speakers == 0 then return false end + if not waitForNativeSpeakersEmpty(Peripherals.speakers, playSeq) then + return false + end + + local pending = {} + for _, sp in ipairs(Peripherals.speakers) do + local name = peripheral.getName(sp) + if name and 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(samples, volume) + end) + if ok then + 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 + + Utils.log("INFO", string.format("Audio samples queued in synchronized multi-speaker mode: samples=%d speakers=%d", #samples, #Peripherals.speakers)) + return true +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 @@ -1111,7 +1148,13 @@ function Audio.playWavStreamItem(item) tonumber(meta and meta.channels) or 0, tonumber(meta and meta.bitsPerSample) or 0 )) - if not playChunkOnSpeakers(samples, CONFIG.AUDIO_VOLUME, playSeq, false) then + local playOk + if #Peripherals.speakers > 1 then + playOk = queueSamplesSynchronized(samples, CONFIG.AUDIO_VOLUME, playSeq) + else + playOk = playChunkOnSpeakers(samples, CONFIG.AUDIO_VOLUME, playSeq, false) + end + if not playOk then Audio.stop("current") return end