fix(music): smooth wav chunk streaming playback
Bump the station build to v1.0.4, reduce live WAV chunk size to 0.5 seconds, and keep native speaker queues fed during streaming instead of draining after every chunk so playback is less jittery and stays closer to real time.
This commit is contained in:
@@ -324,7 +324,7 @@ async function sendLiveWavChunksToStation(stationName, fallbackWs, task) {
|
||||
const sampleRate = 48000;
|
||||
const channels = 1;
|
||||
const bitsPerSample = 16;
|
||||
const chunkDurationSec = 1.0;
|
||||
const chunkDurationSec = 0.5;
|
||||
const chunkBytes = Math.max(4096, Math.floor(sampleRate * channels * (bitsPerSample / 8) * chunkDurationSec));
|
||||
|
||||
targetWs.send(JSON.stringify({
|
||||
|
||||
+32
-21
@@ -2,7 +2,7 @@
|
||||
-- ============================================================================
|
||||
-- 1. Configuration & Constants
|
||||
-- ============================================================================
|
||||
local APP_VERSION_TEXT = "v1.0.3"
|
||||
local APP_VERSION_TEXT = "v1.0.4"
|
||||
|
||||
local CONFIG = {
|
||||
CLOUD_URL = "ws://stabadmin.fse-media.group",
|
||||
@@ -591,8 +591,30 @@ local function waitForInterruptibleDelay(playSeq, seconds)
|
||||
end
|
||||
end
|
||||
|
||||
local function playChunkOnSpeakers(samples, volume, playSeq)
|
||||
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
|
||||
|
||||
while next(waiting) 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
|
||||
end
|
||||
if ev[1] == "speaker_audio_empty" and ev[2] and waiting[ev[2]] then
|
||||
waiting[ev[2]] = nil
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
local pending = {}
|
||||
for _, sp in ipairs(Peripherals.speakers) do
|
||||
local name = peripheral.getName(sp)
|
||||
@@ -645,24 +667,8 @@ local function playChunkOnSpeakers(samples, volume, playSeq)
|
||||
end
|
||||
end
|
||||
|
||||
local waiting = {}
|
||||
for _, sp in ipairs(Peripherals.speakers) do
|
||||
local name = peripheral.getName(sp)
|
||||
if name then waiting[name] = true end
|
||||
end
|
||||
|
||||
while next(waiting) 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
|
||||
end
|
||||
if ev[1] == "speaker_audio_empty" and ev[2] and waiting[ev[2]] then
|
||||
waiting[ev[2]] = nil
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
if not waitForDrain then return true end
|
||||
return waitForNativeSpeakersEmpty(Peripherals.speakers, playSeq)
|
||||
end
|
||||
|
||||
local function decodePcmS8Chunk(chunk)
|
||||
@@ -1105,7 +1111,7 @@ 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) then
|
||||
if not playChunkOnSpeakers(samples, CONFIG.AUDIO_VOLUME, playSeq, false) then
|
||||
Audio.stop("current")
|
||||
return
|
||||
end
|
||||
@@ -1119,6 +1125,11 @@ function Audio.playWavStreamItem(item)
|
||||
end
|
||||
end
|
||||
|
||||
if not waitForNativeSpeakersEmpty(Peripherals.speakers, playSeq) then
|
||||
Audio.stop("current")
|
||||
return
|
||||
end
|
||||
|
||||
while areHqSpeakersBusy(Peripherals.speakers) do
|
||||
if shouldStop(playSeq) then
|
||||
Audio.stop("current")
|
||||
|
||||
Reference in New Issue
Block a user