feat: 明确音频优先级,优化发车音频触发时机
- 到达与发车最高优先级(3),其次是进站预告(2),然后是延误警告(1),最低为背景音乐(0) - 将发车音乐的触发时机从固定时间改为动态计算(音频时长+1.5秒补偿),确保音频结束时列车刚好发车 - 拆分 handleTrainApproaching 与 handleTrainAlert 逻辑,解耦进站与延误的优先级 - 版本号提升至 v1.0.59
This commit is contained in:
Binary file not shown.
+88
-38
@@ -297,7 +297,7 @@ function publishStationUpdate(req) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getInterruptModeForPriority(priority) {
|
function getInterruptModeForPriority(priority) {
|
||||||
if (priority >= 2) return 'all';
|
if (priority >= 3) return 'all';
|
||||||
if (priority >= 1) return 'ifLower';
|
if (priority >= 1) return 'ifLower';
|
||||||
return 'none';
|
return 'none';
|
||||||
}
|
}
|
||||||
@@ -859,7 +859,7 @@ function enqueueStationAudioTask(stationName, fallbackWs, task) {
|
|||||||
const newP = Number(task.priority || 0);
|
const newP = Number(task.priority || 0);
|
||||||
const cur = q.current;
|
const cur = q.current;
|
||||||
|
|
||||||
if (task.interruptMode === 'all' || newP >= 2) {
|
if (task.interruptMode === 'all') {
|
||||||
cancelStationAudioTask(cur);
|
cancelStationAudioTask(cur);
|
||||||
q.queue.length = 0;
|
q.queue.length = 0;
|
||||||
sendAudioControl(cleanStation, { type: 'audio_stop', mode: 'all', reason: 'emergency' }, fallbackWs);
|
sendAudioControl(cleanStation, { type: 'audio_stop', mode: 'all', reason: 'emergency' }, fallbackWs);
|
||||||
@@ -1802,6 +1802,9 @@ function handleMessage(ws, data) {
|
|||||||
case 'train_approaching':
|
case 'train_approaching':
|
||||||
handleTrainApproaching(ws, data);
|
handleTrainApproaching(ws, data);
|
||||||
break;
|
break;
|
||||||
|
case 'train_alert':
|
||||||
|
handleTrainAlert(ws, data);
|
||||||
|
break;
|
||||||
case 'train_departure':
|
case 'train_departure':
|
||||||
handleTrainDeparture(ws, data);
|
handleTrainDeparture(ws, data);
|
||||||
break;
|
break;
|
||||||
@@ -1982,11 +1985,8 @@ function handleTrainApproaching(ws, data) {
|
|||||||
|
|
||||||
const lineCode = data.lineCode || (stationCode ? stationCode.split('-')[0] : 'UNK');
|
const lineCode = data.lineCode || (stationCode ? stationCode.split('-')[0] : 'UNK');
|
||||||
const destCode = data.destCode || getStationCodeFromName(data.destination) || 'UNK';
|
const destCode = data.destCode || getStationCodeFromName(data.destination) || 'UNK';
|
||||||
const delay = Math.max(0, Number(data.delay) || 0); // 获取延误信息(分钟)
|
|
||||||
const cancelled = Boolean(data.cancelled);
|
|
||||||
const rawReason = String(data.reason || '').trim();
|
|
||||||
|
|
||||||
log('INFO', `Train ${trainId} approaching ${stationName} (Delay: ${delay} min). Broadcasting pre-arrival...`);
|
log('INFO', `Train ${trainId} approaching ${stationName}. Broadcasting pre-arrival...`);
|
||||||
|
|
||||||
ws.send(JSON.stringify({
|
ws.send(JSON.stringify({
|
||||||
type: 'train_approaching_ack',
|
type: 'train_approaching_ack',
|
||||||
@@ -2000,30 +2000,6 @@ function handleTrainApproaching(ws, data) {
|
|||||||
seq.push('ja-fc1', `ja-${finalPlatform}`, 'ja-fc2', `ja-${destCode}`, 'ja-fc3');
|
seq.push('ja-fc1', `ja-${finalPlatform}`, 'ja-fc2', `ja-${destCode}`, 'ja-fc3');
|
||||||
seq.push('ch-fc1', `ch-${finalPlatform}`, 'ch-fc2', 'ch-fc3', `ch-${destCode}`, 'ch-fc4');
|
seq.push('ch-fc1', `ch-${finalPlatform}`, 'ch-fc2', 'ch-fc3', `ch-${destCode}`, 'ch-fc4');
|
||||||
|
|
||||||
const normalizeReason = (reason, lang) => {
|
|
||||||
let r = String(reason || '').trim();
|
|
||||||
if (!r || r === 'delay') r = 'default_delay';
|
|
||||||
if (lang === 'ch' && r === 'train') r = 'delaytrain';
|
|
||||||
return r;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (cancelled) {
|
|
||||||
seq.push(
|
|
||||||
'ksts',
|
|
||||||
'ja-cancel1', `ja-${lineCode}`, 'ja-cancel2',
|
|
||||||
'ch-cancel1', `ch-${lineCode}`, 'ch-cancel2'
|
|
||||||
);
|
|
||||||
} else if (delay > 0) {
|
|
||||||
const d = Math.max(1, Math.round(delay));
|
|
||||||
const jaReason = normalizeReason(rawReason, 'ja');
|
|
||||||
const chReason = normalizeReason(rawReason, 'ch');
|
|
||||||
seq.push(
|
|
||||||
'ksts',
|
|
||||||
'ja-delay1', `ja-${lineCode}`, `ja-${jaReason}`, 'ja-delay2', `ja-${d}min`, 'ja-delay3',
|
|
||||||
'ch-delay1', `ch-${lineCode}`, 'ch-delay2', `ch-${chReason}`, 'ch-delay3', `ch-${d}`, 'ch-delay4'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
enqueueHlsPush(stationName, seq);
|
enqueueHlsPush(stationName, seq);
|
||||||
|
|
||||||
const audioId = `approaching-${stationCode}-${Date.now()}`;
|
const audioId = `approaching-${stationCode}-${Date.now()}`;
|
||||||
@@ -2035,7 +2011,7 @@ function handleTrainApproaching(ws, data) {
|
|||||||
sourcePath: built.sourcePath,
|
sourcePath: built.sourcePath,
|
||||||
streamUrl: built.streamUrl,
|
streamUrl: built.streamUrl,
|
||||||
durationSec: built.durationSec,
|
durationSec: built.durationSec,
|
||||||
priority: 1,
|
priority: 2, // Priority 2 for Approaching
|
||||||
interruptMode: 'ifLower',
|
interruptMode: 'ifLower',
|
||||||
source: 'train_approaching',
|
source: 'train_approaching',
|
||||||
audioId,
|
audioId,
|
||||||
@@ -2047,6 +2023,74 @@ function handleTrainApproaching(ws, data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleTrainAlert(ws, data) {
|
||||||
|
const trainId = data.trainId;
|
||||||
|
const stationName = getCorrectStationName(data.stationName);
|
||||||
|
const stationCode = data.stationCode;
|
||||||
|
const platform = data.stationPlatform || data.platform || '1';
|
||||||
|
|
||||||
|
const lineCode = data.lineCode || (stationCode ? stationCode.split('-')[0] : 'UNK');
|
||||||
|
const delay = Math.max(0, Number(data.delay) || 0); // 获取延误信息(分钟)
|
||||||
|
const cancelled = Boolean(data.cancelled);
|
||||||
|
const rawReason = String(data.reason || '').trim();
|
||||||
|
|
||||||
|
log('INFO', `Train ${trainId} alert at ${stationName} (Delay: ${delay} min, Cancelled: ${cancelled}). Broadcasting alert...`);
|
||||||
|
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
type: 'train_alert_ack',
|
||||||
|
success: true,
|
||||||
|
trainId
|
||||||
|
}));
|
||||||
|
|
||||||
|
const seq = ['ksts'];
|
||||||
|
|
||||||
|
const normalizeReason = (reason, lang) => {
|
||||||
|
let r = String(reason || '').trim();
|
||||||
|
if (!r || r === 'delay') r = 'default_delay';
|
||||||
|
if (lang === 'ch' && r === 'train') r = 'delaytrain';
|
||||||
|
return r;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (cancelled) {
|
||||||
|
seq.push(
|
||||||
|
'ja-cancel1', `ja-${lineCode}`, 'ja-cancel2',
|
||||||
|
'ch-cancel1', `ch-${lineCode}`, 'ch-cancel2'
|
||||||
|
);
|
||||||
|
} else if (delay > 0) {
|
||||||
|
const d = Math.max(1, Math.round(delay));
|
||||||
|
const jaReason = normalizeReason(rawReason, 'ja');
|
||||||
|
const chReason = normalizeReason(rawReason, 'ch');
|
||||||
|
seq.push(
|
||||||
|
'ja-delay1', `ja-${lineCode}`, `ja-${jaReason}`, 'ja-delay2', `ja-${d}min`, 'ja-delay3',
|
||||||
|
'ch-delay1', `ch-${lineCode}`, 'ch-delay2', `ch-${chReason}`, 'ch-delay3', `ch-${d}`, 'ch-delay4'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
enqueueHlsPush(stationName, seq);
|
||||||
|
|
||||||
|
const audioId = `alert-${stationCode}-${Date.now()}`;
|
||||||
|
const built = buildSequenceWavFile(seq);
|
||||||
|
if (built) {
|
||||||
|
log('INFO', `Alert audio queued for ${stationName}: ${audioId} (${built.durationSec.toFixed(2)}s)`);
|
||||||
|
enqueueStationAudioTask(stationName, ws, {
|
||||||
|
audioFormat: built.audioFormat,
|
||||||
|
sourcePath: built.sourcePath,
|
||||||
|
streamUrl: built.streamUrl,
|
||||||
|
durationSec: built.durationSec,
|
||||||
|
priority: 1, // Priority 1 for Alert
|
||||||
|
interruptMode: 'ifLower',
|
||||||
|
source: 'train_alert',
|
||||||
|
audioId,
|
||||||
|
consoleStreamStart: { type: 'stream_start', station: stationName, seq, streamName: getStationStreamName(stationName), hlsUrl: getStationHlsUrl(getStationStreamName(stationName)) },
|
||||||
|
consoleStreamEnd: { type: 'stream_end', station: stationName }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
log('WARN', `Alert audio build returned empty for ${stationName}: ${audioId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleTrainArrival(ws, data) {
|
function handleTrainArrival(ws, data) {
|
||||||
const trainId = data.trainId;
|
const trainId = data.trainId;
|
||||||
const stationName = getCorrectStationName(data.stationName);
|
const stationName = getCorrectStationName(data.stationName);
|
||||||
@@ -2077,11 +2121,17 @@ function handleTrainArrival(ws, data) {
|
|||||||
saveData();
|
saveData();
|
||||||
broadcastUpdate();
|
broadcastUpdate();
|
||||||
|
|
||||||
|
const departureBeep = data.departureBeep || 'fc1';
|
||||||
|
const departureSeq = [departureBeep, 'ja-doorwarn', 'ch-doorwarn'];
|
||||||
|
const departureBuilt = buildSequenceWavFile(departureSeq);
|
||||||
|
const departureDuration = departureBuilt ? departureBuilt.durationSec : 10;
|
||||||
|
|
||||||
ws.send(JSON.stringify({
|
ws.send(JSON.stringify({
|
||||||
type: 'train_arrival_ack',
|
type: 'train_arrival_ack',
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Train arrival processed successfully',
|
message: 'Train arrival processed successfully',
|
||||||
trainId
|
trainId,
|
||||||
|
departureDuration
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Broadcast Audio Logic
|
// Broadcast Audio Logic
|
||||||
@@ -2101,8 +2151,8 @@ function handleTrainArrival(ws, data) {
|
|||||||
sourcePath: built.sourcePath,
|
sourcePath: built.sourcePath,
|
||||||
streamUrl: built.streamUrl,
|
streamUrl: built.streamUrl,
|
||||||
durationSec: built.durationSec,
|
durationSec: built.durationSec,
|
||||||
priority: 1,
|
priority: 3,
|
||||||
interruptMode: 'ifLower',
|
interruptMode: 'all',
|
||||||
source: 'train_arrival',
|
source: 'train_arrival',
|
||||||
audioId,
|
audioId,
|
||||||
consoleStreamStart: { type: 'stream_start', station: stationName, seq, streamName: getStationStreamName(stationName), hlsUrl: getStationHlsUrl(getStationStreamName(stationName)) },
|
consoleStreamStart: { type: 'stream_start', station: stationName, seq, streamName: getStationStreamName(stationName), hlsUrl: getStationHlsUrl(getStationStreamName(stationName)) },
|
||||||
@@ -2150,8 +2200,8 @@ function handleTrainDeparture(ws, data) {
|
|||||||
sourcePath: built.sourcePath,
|
sourcePath: built.sourcePath,
|
||||||
streamUrl: built.streamUrl,
|
streamUrl: built.streamUrl,
|
||||||
durationSec: built.durationSec,
|
durationSec: built.durationSec,
|
||||||
priority: 1,
|
priority: 3,
|
||||||
interruptMode: 'ifLower',
|
interruptMode: 'all',
|
||||||
source: 'train_departure',
|
source: 'train_departure',
|
||||||
audioId,
|
audioId,
|
||||||
consoleStreamStart: { type: 'stream_start', station: stationName, seq, streamName: getStationStreamName(stationName), hlsUrl: getStationHlsUrl(getStationStreamName(stationName)) },
|
consoleStreamStart: { type: 'stream_start', station: stationName, seq, streamName: getStationStreamName(stationName), hlsUrl: getStationHlsUrl(getStationStreamName(stationName)) },
|
||||||
@@ -3130,7 +3180,7 @@ app.post('/api/music/start', express.json(), (req, res) => {
|
|||||||
audioFormat: 'wav-stream-live',
|
audioFormat: 'wav-stream-live',
|
||||||
sourcePath: playbackAudio.sourcePath,
|
sourcePath: playbackAudio.sourcePath,
|
||||||
durationSec: playbackAudio.durationSec,
|
durationSec: playbackAudio.durationSec,
|
||||||
priority: -1,
|
priority: 0,
|
||||||
interruptMode: 'none',
|
interruptMode: 'none',
|
||||||
source: 'music',
|
source: 'music',
|
||||||
loop,
|
loop,
|
||||||
@@ -3147,7 +3197,7 @@ app.post('/api/music/start', express.json(), (req, res) => {
|
|||||||
buffer: playbackAudio.buffer,
|
buffer: playbackAudio.buffer,
|
||||||
streamUrl: playbackAudio.streamUrl,
|
streamUrl: playbackAudio.streamUrl,
|
||||||
durationSec: playbackAudio.durationSec,
|
durationSec: playbackAudio.durationSec,
|
||||||
priority: -1,
|
priority: 0,
|
||||||
interruptMode: 'none',
|
interruptMode: 'none',
|
||||||
source: 'music',
|
source: 'music',
|
||||||
loop,
|
loop,
|
||||||
|
|||||||
+13
-5
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
-- 1. Configuration & Constants
|
-- 1. Configuration & Constants
|
||||||
local APP_VERSION_TEXT = "v1.0.58"
|
local APP_VERSION_TEXT = "v1.0.59"
|
||||||
|
|
||||||
local CONFIG = {
|
local CONFIG = {
|
||||||
CLOUD_URL = "ws://stabadmin.fse-media.group",
|
CLOUD_URL = "ws://stabadmin.fse-media.group",
|
||||||
@@ -2161,6 +2161,11 @@ function Network.messageLoop()
|
|||||||
local flushBelow = data.flushBelow
|
local flushBelow = data.flushBelow
|
||||||
Audio.stop(mode, flushBelow)
|
Audio.stop(mode, flushBelow)
|
||||||
end
|
end
|
||||||
|
elseif data.type == "train_arrival_ack" then
|
||||||
|
if State.currentTrain and State.currentTrain.id == data.trainId then
|
||||||
|
State.currentTrain.departureDuration = tonumber(data.departureDuration) or 10
|
||||||
|
Utils.log("INFO", "Got departure audio duration: " .. State.currentTrain.departureDuration .. "s")
|
||||||
|
end
|
||||||
elseif data.type == "station_update" then
|
elseif data.type == "station_update" then
|
||||||
local targetVersion = AutoUpdate.getTargetVersion(data)
|
local targetVersion = AutoUpdate.getTargetVersion(data)
|
||||||
if targetVersion == "" then
|
if targetVersion == "" then
|
||||||
@@ -2752,7 +2757,7 @@ function Main.loop()
|
|||||||
State.announcedAlerts[alertKey] = now
|
State.announcedAlerts[alertKey] = now
|
||||||
Utils.log("INFO", string.format("Train Alert: %s (ETA: %ds, Delay: %dm, Cancelled: %s, Reason: %s)", trainName, etaSeconds, displayDelayMinutes, tostring(cancelled), tostring(reason or "")))
|
Utils.log("INFO", string.format("Train Alert: %s (ETA: %ds, Delay: %dm, Cancelled: %s, Reason: %s)", trainName, etaSeconds, displayDelayMinutes, tostring(cancelled), tostring(reason or "")))
|
||||||
Network.send({
|
Network.send({
|
||||||
type = "train_approaching",
|
type = "train_alert",
|
||||||
stationName = State.stationName,
|
stationName = State.stationName,
|
||||||
stationCode = State.stationCode,
|
stationCode = State.stationCode,
|
||||||
stationPlatform = platform,
|
stationPlatform = platform,
|
||||||
@@ -2908,7 +2913,7 @@ function Main.loop()
|
|||||||
State.announcedAlerts[alertKey] = now
|
State.announcedAlerts[alertKey] = now
|
||||||
Utils.log("INFO", string.format("Train Delay Alert: %s (ETA: %ds, Delay: %dm)", tName, etaSeconds, delay))
|
Utils.log("INFO", string.format("Train Delay Alert: %s (ETA: %ds, Delay: %dm)", tName, etaSeconds, delay))
|
||||||
Network.send({
|
Network.send({
|
||||||
type = "train_approaching",
|
type = "train_alert",
|
||||||
stationName = State.stationName,
|
stationName = State.stationName,
|
||||||
stationCode = State.stationCode,
|
stationCode = State.stationCode,
|
||||||
stationPlatform = platform or State.stationPlatform or "1",
|
stationPlatform = platform or State.stationPlatform or "1",
|
||||||
@@ -3029,11 +3034,14 @@ function Main.loop()
|
|||||||
trainId = id,
|
trainId = id,
|
||||||
destination = dest,
|
destination = dest,
|
||||||
nextStation = nextStation,
|
nextStation = nextStation,
|
||||||
departureTime = State.currentTrain.departureTime
|
departureTime = State.currentTrain.departureTime,
|
||||||
|
departureBeep = State.departureBeep
|
||||||
})
|
})
|
||||||
else
|
else
|
||||||
local now = os.epoch("utc") / 1000
|
local now = os.epoch("utc") / 1000
|
||||||
if not State.currentTrain.departureSent and (State.currentTrain.departureTime - now) <= 10 then
|
-- 增加 1.5 秒网络与预缓冲补偿,确保音频结束时列车刚好发车
|
||||||
|
local triggerOffset = (State.currentTrain.departureDuration or 10) + 1.5
|
||||||
|
if not State.currentTrain.departureSent and (State.currentTrain.departureTime - now) <= triggerOffset then
|
||||||
State.currentTrain.departureSent = true
|
State.currentTrain.departureSent = true
|
||||||
Network.send({
|
Network.send({
|
||||||
type = "train_departure",
|
type = "train_departure",
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
function getKeywords(text)
|
||||||
|
if not text then return {} end
|
||||||
|
local s = tostring(text):lower()
|
||||||
|
|
||||||
|
local keywords = {}
|
||||||
|
|
||||||
|
for code in s:gmatch("[%a%d]+%-[%a%d]+") do
|
||||||
|
keywords[code] = true
|
||||||
|
end
|
||||||
|
for code in s:gmatch("%a+%d+") do
|
||||||
|
keywords[code] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
for zh in s:gmatch("[\128-\255]+") do
|
||||||
|
local cleanZh = zh:gsub("站台", ""):gsub("轨道", ""):gsub("号", ""):gsub("线", "")
|
||||||
|
if cleanZh and cleanZh ~= "" then
|
||||||
|
keywords[cleanZh] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local list = {}
|
||||||
|
for k, _ in pairs(keywords) do
|
||||||
|
table.insert(list, k)
|
||||||
|
end
|
||||||
|
return list
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test(a, b)
|
||||||
|
local k1 = getKeywords(a)
|
||||||
|
local k2 = getKeywords(b)
|
||||||
|
local match = false
|
||||||
|
for _, w1 in ipairs(k1) do
|
||||||
|
for _, w2 in ipairs(k2) do
|
||||||
|
if w1 == w2 or w1:find(w2, 1, true) or w2:find(w1, 1, true) then
|
||||||
|
match = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
print(a .. " vs " .. b .. " -> " .. tostring(match))
|
||||||
|
print(" K1: " .. table.concat(k1, ", "))
|
||||||
|
print(" K2: " .. table.concat(k2, ", "))
|
||||||
|
end
|
||||||
|
|
||||||
|
test("CE-03 (桃园)", "桃园")
|
||||||
|
test("CE-04", "CE-04 [西门]")
|
||||||
|
test("CRH-01", "CRH-01 南山线")
|
||||||
|
test("南山线", "南山")
|
||||||
|
test("???", "Unknown")
|
||||||
Reference in New Issue
Block a user