feat: 明确音频优先级,优化发车音频触发时机

- 到达与发车最高优先级(3),其次是进站预告(2),然后是延误警告(1),最低为背景音乐(0)

- 将发车音乐的触发时机从固定时间改为动态计算(音频时长+1.5秒补偿),确保音频结束时列车刚好发车

- 拆分 handleTrainApproaching 与 handleTrainAlert 逻辑,解耦进站与延误的优先级

- 版本号提升至 v1.0.59
This commit is contained in:
2026-08-04 13:00:04 +08:00
parent 01ac488aef
commit d8eee041c8
4 changed files with 150 additions and 43 deletions
+88 -38
View File
@@ -297,7 +297,7 @@ function publishStationUpdate(req) {
}
function getInterruptModeForPriority(priority) {
if (priority >= 2) return 'all';
if (priority >= 3) return 'all';
if (priority >= 1) return 'ifLower';
return 'none';
}
@@ -859,7 +859,7 @@ function enqueueStationAudioTask(stationName, fallbackWs, task) {
const newP = Number(task.priority || 0);
const cur = q.current;
if (task.interruptMode === 'all' || newP >= 2) {
if (task.interruptMode === 'all') {
cancelStationAudioTask(cur);
q.queue.length = 0;
sendAudioControl(cleanStation, { type: 'audio_stop', mode: 'all', reason: 'emergency' }, fallbackWs);
@@ -1802,6 +1802,9 @@ function handleMessage(ws, data) {
case 'train_approaching':
handleTrainApproaching(ws, data);
break;
case 'train_alert':
handleTrainAlert(ws, data);
break;
case 'train_departure':
handleTrainDeparture(ws, data);
break;
@@ -1982,11 +1985,8 @@ function handleTrainApproaching(ws, data) {
const lineCode = data.lineCode || (stationCode ? stationCode.split('-')[0] : '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({
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('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);
const audioId = `approaching-${stationCode}-${Date.now()}`;
@@ -2035,7 +2011,7 @@ function handleTrainApproaching(ws, data) {
sourcePath: built.sourcePath,
streamUrl: built.streamUrl,
durationSec: built.durationSec,
priority: 1,
priority: 2, // Priority 2 for Approaching
interruptMode: 'ifLower',
source: 'train_approaching',
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) {
const trainId = data.trainId;
const stationName = getCorrectStationName(data.stationName);
@@ -2077,11 +2121,17 @@ function handleTrainArrival(ws, data) {
saveData();
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({
type: 'train_arrival_ack',
success: true,
message: 'Train arrival processed successfully',
trainId
trainId,
departureDuration
}));
// Broadcast Audio Logic
@@ -2101,8 +2151,8 @@ function handleTrainArrival(ws, data) {
sourcePath: built.sourcePath,
streamUrl: built.streamUrl,
durationSec: built.durationSec,
priority: 1,
interruptMode: 'ifLower',
priority: 3,
interruptMode: 'all',
source: 'train_arrival',
audioId,
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,
streamUrl: built.streamUrl,
durationSec: built.durationSec,
priority: 1,
interruptMode: 'ifLower',
priority: 3,
interruptMode: 'all',
source: 'train_departure',
audioId,
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',
sourcePath: playbackAudio.sourcePath,
durationSec: playbackAudio.durationSec,
priority: -1,
priority: 0,
interruptMode: 'none',
source: 'music',
loop,
@@ -3147,7 +3197,7 @@ app.post('/api/music/start', express.json(), (req, res) => {
buffer: playbackAudio.buffer,
streamUrl: playbackAudio.streamUrl,
durationSec: playbackAudio.durationSec,
priority: -1,
priority: 0,
interruptMode: 'none',
source: 'music',
loop,