feat(music): switch station music to live PCM streaming
Move music playback off the HLS/download-first path and stream 48k mono PCM chunks over WebSocket so stations can start playing immediately. Add station-side PCM chunk playback support and stream lifecycle logging to make no-audio issues easier to trace.
This commit is contained in:
+56
-2
@@ -654,6 +654,19 @@ local function playChunkOnSpeakers(samples, volume, playSeq)
|
||||
return true
|
||||
end
|
||||
|
||||
local function decodePcmS8Chunk(chunk)
|
||||
if type(chunk) ~= "string" or #chunk == 0 then return nil end
|
||||
local samples = {}
|
||||
for i = 1, #chunk do
|
||||
local b = string.byte(chunk, i)
|
||||
if b >= 128 then
|
||||
b = b - 256
|
||||
end
|
||||
samples[i] = b
|
||||
end
|
||||
return samples
|
||||
end
|
||||
|
||||
local function areHqSpeakersBusy(speakers)
|
||||
for _, sp in ipairs(speakers) do
|
||||
local playing = false
|
||||
@@ -824,10 +837,47 @@ function Audio.playDfpwmItem(item)
|
||||
end
|
||||
end
|
||||
|
||||
function Audio.playPcmItem(item)
|
||||
if not Peripherals.speakers or #Peripherals.speakers == 0 then
|
||||
Utils.log("WARN", "No speakers found; cannot play audio")
|
||||
return
|
||||
end
|
||||
|
||||
local playSeq = State.interruptSeq
|
||||
local source = item.audioData
|
||||
|
||||
if type(source) == "table" and source.read then
|
||||
source = source.read
|
||||
end
|
||||
|
||||
if type(source) ~= "function" then
|
||||
local data = source
|
||||
source = function()
|
||||
local chunk = data
|
||||
data = nil
|
||||
return chunk
|
||||
end
|
||||
end
|
||||
|
||||
while true do
|
||||
if shouldStop(playSeq) then 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
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Audio.playItem(item)
|
||||
local format = tostring(item.audioFormat or "dfpwm"):lower()
|
||||
if format == "wav" or format == "file" or format == "stream" or format == "hls" then
|
||||
return playHqAudioItem(item)
|
||||
elseif format == "pcm" then
|
||||
return Audio.playPcmItem(item)
|
||||
end
|
||||
return Audio.playDfpwmItem(item)
|
||||
end
|
||||
@@ -1160,9 +1210,12 @@ function Network.messageLoop()
|
||||
audioId = data.audioId
|
||||
})
|
||||
State.audioRx = nil
|
||||
elseif audioFormat == "dfpwm" then
|
||||
elseif audioFormat == "dfpwm" or audioFormat == "pcm" then
|
||||
local stream = Audio.createStream()
|
||||
State.audioRx.stream = stream
|
||||
if audioFormat == "pcm" then
|
||||
Utils.log("INFO", "Receiving live PCM audio stream")
|
||||
end
|
||||
Audio.enqueueItem({
|
||||
audioData = stream,
|
||||
audioFormat = audioFormat,
|
||||
@@ -1204,7 +1257,8 @@ function Network.messageLoop()
|
||||
else
|
||||
if State.audioRx then
|
||||
-- Memory protection: limit to 10MB per audio item
|
||||
if State.audioRx.receivedBytes + #msg > 10 * 1024 * 1024 then
|
||||
local isLiveStream = State.audioRx.stream and (tonumber(State.audioRx.expectedBytes) or 0) <= 0
|
||||
if (not isLiveStream) and State.audioRx.receivedBytes + #msg > 10 * 1024 * 1024 then
|
||||
Utils.log("ERROR", "Audio item too large, discarding")
|
||||
if State.audioRx.stream then
|
||||
State.audioRx.stream.finish()
|
||||
|
||||
Reference in New Issue
Block a user