feat(station-broadcast, cloud-server): 添加HLS播放与循环播放支持,更新脚本拉取地址
调整两个工具脚本的远程拉取地址,将固定commit哈希链接改为main分支链接;为云服务端添加loop参数与HLS音频播放支持,重构音频播放逻辑简化冗余代码;为station_broadcast.lua新增hls格式支持与循环播放控制,完善网络消息参数处理。
This commit is contained in:
+27
-15
@@ -244,6 +244,7 @@ async function sendAudioBufferToStation(stationName, fallbackWs, task) {
|
|||||||
channels: Number(task.channels || 1),
|
channels: Number(task.channels || 1),
|
||||||
audioFormat,
|
audioFormat,
|
||||||
streamUrl,
|
streamUrl,
|
||||||
|
loop: !!task.loop,
|
||||||
priority: Number(task.priority || 0),
|
priority: Number(task.priority || 0),
|
||||||
interruptMode: task.interruptMode || getInterruptModeForPriority(Number(task.priority || 0)),
|
interruptMode: task.interruptMode || getInterruptModeForPriority(Number(task.priority || 0)),
|
||||||
source: task.source || 'unknown'
|
source: task.source || 'unknown'
|
||||||
@@ -2376,6 +2377,11 @@ app.post('/api/music/start', express.json(), (req, res) => {
|
|||||||
stopStationMusic(name, ws);
|
stopStationMusic(name, ws);
|
||||||
stationMusicStates.set(name, { enabled: true, track, loop });
|
stationMusicStates.set(name, { enabled: true, track, loop });
|
||||||
|
|
||||||
|
const streamName = getStationStreamName(name);
|
||||||
|
const hlsUrl = HLS_OUTPUT_MODE === 'local'
|
||||||
|
? getLocalHlsUrlForRequest(streamName, req)
|
||||||
|
: getStationHlsUrlForRequest(streamName, req);
|
||||||
|
|
||||||
const enqueueOnce = () => {
|
const enqueueOnce = () => {
|
||||||
const currentWs = getStationWs(name);
|
const currentWs = getStationWs(name);
|
||||||
if (!currentWs || currentWs.readyState !== WebSocket.OPEN) {
|
if (!currentWs || currentWs.readyState !== WebSocket.OPEN) {
|
||||||
@@ -2384,6 +2390,24 @@ app.post('/api/music/start', express.json(), (req, res) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (HLS_ENABLE) {
|
||||||
|
enqueueHlsPush(name, [track]);
|
||||||
|
enqueueStationAudioTask(name, currentWs, {
|
||||||
|
audioFormat: 'hls',
|
||||||
|
streamUrl: hlsUrl,
|
||||||
|
durationSec: playbackAudio.durationSec,
|
||||||
|
priority: -1,
|
||||||
|
interruptMode: 'none',
|
||||||
|
source: 'music',
|
||||||
|
loop,
|
||||||
|
audioId: `music-${track}-${name}-${Date.now()}`,
|
||||||
|
requeueOnPreempt: true,
|
||||||
|
consoleStreamStart: { type: 'stream_start', station: name, seq: [`MUSIC:${track}`], streamName, hlsUrl },
|
||||||
|
consoleStreamEnd: { type: 'stream_end', station: name }
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
enqueueStationAudioTask(name, currentWs, {
|
enqueueStationAudioTask(name, currentWs, {
|
||||||
audioFormat: playbackAudio.audioFormat,
|
audioFormat: playbackAudio.audioFormat,
|
||||||
buffer: playbackAudio.buffer,
|
buffer: playbackAudio.buffer,
|
||||||
@@ -2392,23 +2416,11 @@ app.post('/api/music/start', express.json(), (req, res) => {
|
|||||||
priority: -1,
|
priority: -1,
|
||||||
interruptMode: 'none',
|
interruptMode: 'none',
|
||||||
source: 'music',
|
source: 'music',
|
||||||
|
loop,
|
||||||
audioId: `music-${track}-${name}-${Date.now()}`,
|
audioId: `music-${track}-${name}-${Date.now()}`,
|
||||||
requeueOnPreempt: true,
|
requeueOnPreempt: true,
|
||||||
consoleStreamStart: { type: 'stream_start', station: name, seq: [`MUSIC:${track}`], streamName: getStationStreamName(name), hlsUrl: getStationHlsUrl(getStationStreamName(name)) },
|
consoleStreamStart: { type: 'stream_start', station: name, seq: [`MUSIC:${track}`], streamName, hlsUrl },
|
||||||
consoleStreamEnd: { type: 'stream_end', station: name },
|
consoleStreamEnd: { type: 'stream_end', station: name }
|
||||||
onDone: (info) => {
|
|
||||||
const st = stationMusicStates.get(name);
|
|
||||||
if (!st || !st.enabled) return;
|
|
||||||
if (st.track !== track) return;
|
|
||||||
if (!st.loop) return;
|
|
||||||
// Only stop if cancelled by something else (like stopMusic or higher priority)
|
|
||||||
if (info && info.cancelled) {
|
|
||||||
// If it was just preempted, enqueueStationAudioTask handles the re-insertion
|
|
||||||
// If it was cancelled by stopStationMusic, st.enabled will be false
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
enqueueOnce();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
local STATION_BROADCAST_URL = "https://gitea.fse-media.group/Henry_Du/CC-StationBroadcast/raw/commit/e4b4fa5cdb2bc42279fd87098fef0d27a90e23d5/station_broadcast.lua"
|
local STATION_BROADCAST_URL = "https://gitea.fse-media.group/Henry_Du/CC-StationBroadcast/raw/branch/main/station_broadcast.lua"
|
||||||
|
|
||||||
local function fetch(url)
|
local function fetch(url)
|
||||||
if not http then
|
if not http then
|
||||||
|
|||||||
+23
-5
@@ -718,10 +718,18 @@ local function playHqAudioItem(item)
|
|||||||
|
|
||||||
local controller = Peripherals.speakers[1]
|
local controller = Peripherals.speakers[1]
|
||||||
local called = false
|
local called = false
|
||||||
|
local loopEnabled = item.loop and true or false
|
||||||
|
|
||||||
if controller then
|
if controller then
|
||||||
|
pcall(function()
|
||||||
|
if type(controller.setLoopingAll) == "function" then
|
||||||
|
controller.setLoopingAll(loopEnabled)
|
||||||
|
end
|
||||||
|
end)
|
||||||
local ok, result = pcall(function()
|
local ok, result = pcall(function()
|
||||||
if format == "wav" and type(controller.speakWavAll) == "function" then
|
if format == "hls" and type(controller.speakHLSAll) == "function" then
|
||||||
|
return controller.speakHLSAll(payload, CONFIG.AUDIO_VOLUME)
|
||||||
|
elseif format == "wav" and type(controller.speakWavAll) == "function" then
|
||||||
return controller.speakWavAll(payload, CONFIG.AUDIO_VOLUME)
|
return controller.speakWavAll(payload, CONFIG.AUDIO_VOLUME)
|
||||||
elseif format == "mp3" and type(controller.speakMp3All) == "function" then
|
elseif format == "mp3" and type(controller.speakMp3All) == "function" then
|
||||||
return controller.speakMp3All(payload, CONFIG.AUDIO_VOLUME)
|
return controller.speakMp3All(payload, CONFIG.AUDIO_VOLUME)
|
||||||
@@ -738,8 +746,15 @@ local function playHqAudioItem(item)
|
|||||||
|
|
||||||
if not called then
|
if not called then
|
||||||
for _, sp in ipairs(Peripherals.speakers) do
|
for _, sp in ipairs(Peripherals.speakers) do
|
||||||
|
pcall(function()
|
||||||
|
if type(sp.setLooping) == "function" then
|
||||||
|
sp.setLooping(loopEnabled)
|
||||||
|
end
|
||||||
|
end)
|
||||||
local ok, result = pcall(function()
|
local ok, result = pcall(function()
|
||||||
if format == "wav" and type(sp.speakWav) == "function" then
|
if format == "hls" and type(sp.speakHLS) == "function" then
|
||||||
|
return sp.speakHLS(payload, CONFIG.AUDIO_VOLUME)
|
||||||
|
elseif format == "wav" and type(sp.speakWav) == "function" then
|
||||||
return sp.speakWav(payload, CONFIG.AUDIO_VOLUME)
|
return sp.speakWav(payload, CONFIG.AUDIO_VOLUME)
|
||||||
elseif format == "mp3" and type(sp.speakMp3) == "function" then
|
elseif format == "mp3" and type(sp.speakMp3) == "function" then
|
||||||
return sp.speakMp3(payload, CONFIG.AUDIO_VOLUME)
|
return sp.speakMp3(payload, CONFIG.AUDIO_VOLUME)
|
||||||
@@ -811,7 +826,7 @@ end
|
|||||||
|
|
||||||
function Audio.playItem(item)
|
function Audio.playItem(item)
|
||||||
local format = tostring(item.audioFormat or "dfpwm"):lower()
|
local format = tostring(item.audioFormat or "dfpwm"):lower()
|
||||||
if format == "wav" or format == "file" or format == "stream" then
|
if format == "wav" or format == "file" or format == "stream" or format == "hls" then
|
||||||
return playHqAudioItem(item)
|
return playHqAudioItem(item)
|
||||||
end
|
end
|
||||||
return Audio.playDfpwmItem(item)
|
return Audio.playDfpwmItem(item)
|
||||||
@@ -1092,6 +1107,7 @@ function Network.messageLoop()
|
|||||||
audioData = table.concat(rx.chunks or {}),
|
audioData = table.concat(rx.chunks or {}),
|
||||||
audioFormat = rx.audioFormat or "wav",
|
audioFormat = rx.audioFormat or "wav",
|
||||||
priority = rx.priority,
|
priority = rx.priority,
|
||||||
|
loop = rx.loop and true or false,
|
||||||
source = rx.source,
|
source = rx.source,
|
||||||
audioId = rx.audioId
|
audioId = rx.audioId
|
||||||
})
|
})
|
||||||
@@ -1129,15 +1145,17 @@ function Network.messageLoop()
|
|||||||
expectedBytes = tonumber(data.byteLength) or 0,
|
expectedBytes = tonumber(data.byteLength) or 0,
|
||||||
receivedBytes = 0,
|
receivedBytes = 0,
|
||||||
priority = priority,
|
priority = priority,
|
||||||
|
loop = data.loop and true or false,
|
||||||
source = data.source or data.category,
|
source = data.source or data.category,
|
||||||
audioFormat = audioFormat
|
audioFormat = audioFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
if audioFormat == "stream" and streamUrl and streamUrl ~= "" then
|
if (audioFormat == "stream" or audioFormat == "hls") and streamUrl and streamUrl ~= "" then
|
||||||
Audio.enqueueItem({
|
Audio.enqueueItem({
|
||||||
audioData = streamUrl,
|
audioData = streamUrl,
|
||||||
audioFormat = "stream",
|
audioFormat = audioFormat,
|
||||||
priority = priority,
|
priority = priority,
|
||||||
|
loop = State.audioRx.loop,
|
||||||
source = State.audioRx.source,
|
source = State.audioRx.source,
|
||||||
audioId = data.audioId
|
audioId = data.audioId
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
local STATION_BROADCAST_URL = "https://gitea.fse-media.group/Henry_Du/CC-StationBroadcast/raw/commit/e4b4fa5cdb2bc42279fd87098fef0d27a90e23d5/station_broadcast.lua"
|
local STATION_BROADCAST_URL = "https://gitea.fse-media.group/Henry_Du/CC-StationBroadcast/raw/branch/main/station_broadcast.lua"
|
||||||
|
|
||||||
local function fetch(url)
|
local function fetch(url)
|
||||||
if not http then
|
if not http then
|
||||||
|
|||||||
Reference in New Issue
Block a user