diff --git a/install_station_broadcast.lua b/install_station_broadcast.lua index ddbb45d..c3edd51 100644 --- a/install_station_broadcast.lua +++ b/install_station_broadcast.lua @@ -1,3 +1,11 @@ +--[[ +Author: HenryDu8133 813367384@qq.com +Date: 2026-05-31 00:12:30 +LastEditors: HenryDu8133 813367384@qq.com +LastEditTime: 2026-08-16 11:28:02 +FilePath: \StationBroadcastDisplayCN\install_station_broadcast.lua +Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE +--]] local STATION_BROADCAST_URL = "https://gitea.fse-media.group/Henry_Du/CC-StationBroadcast/raw/branch/main/station_broadcast.lua" local UPDATE_SCRIPT_URL = "https://gitea.fse-media.group/Henry_Du/CC-StationBroadcast/raw/branch/main/update_station_broadcast.lua" diff --git a/station_broadcast-warning.lua b/station_broadcast-warning.lua index e405590..de4f881 100644 --- a/station_broadcast-warning.lua +++ b/station_broadcast-warning.lua @@ -1,3 +1,4 @@ + -- Random Audio Player -- Simplified version that only plays random audio files @@ -45,7 +46,15 @@ local function playDfpwmFile(path, volume) if controller and type(controller.playAudioAll) == "function" then while not controller.playAudioAll(samples, volume) do - os.pullEvent("speaker_audio_empty") + local timerId = os.startTimer(0.05) + while true do + local ev = { os.pullEvent() } + if ev[1] == "timer" and ev[2] == timerId then + break + elseif ev[1] == "speaker_audio_empty" then + break + end + end end else local pending = {} @@ -59,7 +68,15 @@ local function playDfpwmFile(path, volume) end end if next(pending) then - os.pullEvent("speaker_audio_empty") + local timerId = os.startTimer(0.05) + while true do + local ev = { os.pullEvent() } + if ev[1] == "timer" and ev[2] == timerId then + break + elseif ev[1] == "speaker_audio_empty" then + break + end + end end end end diff --git a/station_broadcast.lua b/station_broadcast.lua index 170ae00..a7f2234 100644 --- a/station_broadcast.lua +++ b/station_broadcast.lua @@ -1,6 +1,6 @@ -- 1. Configuration & Constants -local APP_VERSION_TEXT = "v1.0.61" +local APP_VERSION_TEXT = "v1.0.62" local CONFIG = { CLOUD_URL = "ws://stabadmin.fse-media.group", @@ -1213,9 +1213,16 @@ local function queueSamplesSynchronized(samples, volume, playSeq) 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 + 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 end end @@ -1249,9 +1256,16 @@ local function playChunkOnSpeakers(samples, volume, playSeq, waitForDrain) 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 + 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 end end @@ -1269,9 +1283,16 @@ local function playChunkOnSpeakers(samples, volume, playSeq, waitForDrain) ok = controller.playAudioAll(samples, volume) end) if ok then break end - local ev = { os.pullEvent() } - if ev[1] == "audio_interrupt" and tonumber(ev[2]) and tonumber(ev[2]) >= playSeq then - return false + 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 end else @@ -1285,23 +1306,67 @@ end local function decodePcmS8Chunk(chunk) if type(chunk) ~= "string" or #chunk == 0 then return nil end + local startIdx = 1 + if #chunk >= 44 and string.sub(chunk, 1, 4) == "RIFF" and string.sub(chunk, 9, 12) == "WAVE" then + local pos = 13 + while pos + 7 <= #chunk do + local chunkId = string.sub(chunk, pos, pos + 3) + local a, b, c, d = string.byte(chunk, pos + 4, pos + 7) + local chunkSize = 0 + if a and b and c and d then + chunkSize = a + b * 256 + c * 65536 + d * 16777216 + end + if chunkId == "data" then + startIdx = pos + 8 + break + end + pos = pos + 8 + chunkSize + end + if startIdx == 1 then + startIdx = 45 -- fallback + end + end + local samples = {} - for i = 1, #chunk do + local idx = 1 + for i = startIdx, #chunk do local b = string.byte(chunk, i) if b >= 128 then b = b - 256 end - samples[i] = b + samples[idx] = b + idx = idx + 1 end return samples end local function decodePcmS16LEChunk(chunk) if type(chunk) ~= "string" or #chunk < 2 then return nil end + local startIdx = 1 + if #chunk >= 44 and string.sub(chunk, 1, 4) == "RIFF" and string.sub(chunk, 9, 12) == "WAVE" then + local pos = 13 + while pos + 7 <= #chunk do + local chunkId = string.sub(chunk, pos, pos + 3) + local a, b, c, d = string.byte(chunk, pos + 4, pos + 7) + local chunkSize = 0 + if a and b and c and d then + chunkSize = a + b * 256 + c * 65536 + d * 16777216 + end + if chunkId == "data" then + startIdx = pos + 8 + break + end + pos = pos + 8 + chunkSize + end + if startIdx == 1 then + startIdx = 45 -- fallback to 44 bytes header + 1 + end + end + local samples = {} local idx = 1 - for i = 2, #chunk, 2 do - local b = string.byte(chunk, i) + for i = startIdx, #chunk - 1, 2 do + local b = string.byte(chunk, i + 1) if b >= 128 then b = b - 256 end @@ -1671,9 +1736,16 @@ function Audio.playPcmItem(item) end local playSeq = State.interruptSeq + + if not waitForNativeSpeakersEmpty(Peripherals.speakers, playSeq) then + return + end + local source = item.audioData + local streamObj = nil if type(source) == "table" and source.read then + streamObj = source source = source.read end @@ -1686,13 +1758,33 @@ function Audio.playPcmItem(item) end end + local prebufferChunks = math.max(0, tonumber(CONFIG.AUDIO_STREAM_PREROLL_CHUNKS) or 0) + if streamObj and prebufferChunks > 0 then + Utils.log("INFO", "Prebuffering live PCM8 stream: target chunks=" .. tostring(prebufferChunks)) + while streamObj.count() < prebufferChunks and not streamObj.isFinished() do + if shouldStop(playSeq) then + Audio.stop("current") + return + end + if not waitForInterruptibleDelay(playSeq, 0.02) then + Audio.stop("current") + return + end + end + Utils.log("INFO", "Live PCM8 prebuffer ready: chunks=" .. tostring(streamObj.count())) + end + while true do - if shouldStop(playSeq) then break end + if shouldStop(playSeq) then + Audio.stop("current") + break + end local chunk = source() if chunk == nil then break end if type(chunk) == "string" and #chunk > 0 then local samples = decodePcmS8Chunk(chunk) if samples and not playChunkOnSpeakers(samples, CONFIG.AUDIO_VOLUME, playSeq) then + Audio.stop("current") break end end @@ -1706,9 +1798,16 @@ function Audio.playPcmS16LEItem(item) end local playSeq = State.interruptSeq + + if not waitForNativeSpeakersEmpty(Peripherals.speakers, playSeq) then + return + end + local source = item.audioData + local streamObj = nil if type(source) == "table" and source.read then + streamObj = source source = source.read end @@ -1721,13 +1820,33 @@ function Audio.playPcmS16LEItem(item) end end + local prebufferChunks = math.max(0, tonumber(CONFIG.AUDIO_STREAM_PREROLL_CHUNKS) or 0) + if streamObj and prebufferChunks > 0 then + Utils.log("INFO", "Prebuffering live PCM16 stream: target chunks=" .. tostring(prebufferChunks)) + while streamObj.count() < prebufferChunks and not streamObj.isFinished() do + if shouldStop(playSeq) then + Audio.stop("current") + return + end + if not waitForInterruptibleDelay(playSeq, 0.02) then + Audio.stop("current") + return + end + end + Utils.log("INFO", "Live PCM16 prebuffer ready: chunks=" .. tostring(streamObj.count())) + end + while true do - if shouldStop(playSeq) then break end + if shouldStop(playSeq) then + Audio.stop("current") + break + end local chunk = source() if chunk == nil then break end if type(chunk) == "string" and #chunk > 0 then local samples = decodePcmS16LEChunk(chunk) if samples and not playChunkOnSpeakers(samples, CONFIG.AUDIO_VOLUME, playSeq) then + Audio.stop("current") break end end