mirror of
https://github.com/OpenSquawk/OpenSquawk
synced 2026-08-04 16:22:48 +08:00
feat(atis): clock-locked ATIS loop tied to tuned frequency
Real ATIS broadcasts run continuously; tuning in mid-broadcast should drop the pilot into the current spoken position, then loop. The frontend now generates the full announcement once via TTS, plays it as a looping HTMLAudioElement, and seeks to ((Date.now() - lastUpdated) / duration) on metadata-load so all clients tuned to the same ATIS hear it phase- synced. The loop starts/stops automatically with frequency tuning and restarts on info-letter change. say.post.ts now caches tag=atis like tag=flightlab to avoid re-synthesizing identical announcements. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,14 @@ function isFlightLabTag(tag?: string) {
|
||||
return (tag || "").trim().toLowerCase() === "flightlab";
|
||||
}
|
||||
|
||||
function isAtisTag(tag?: string) {
|
||||
return (tag || "").trim().toLowerCase() === "atis";
|
||||
}
|
||||
|
||||
function isCacheableTag(tag?: string) {
|
||||
return isFlightLabTag(tag) || isAtisTag(tag);
|
||||
}
|
||||
|
||||
type TTSProvider = "openai" | "speaches" | "piper";
|
||||
|
||||
function resolveTtsProvider(useSpeaches: boolean, usePiper: boolean): TTSProvider {
|
||||
@@ -196,7 +204,8 @@ export default defineEventHandler(async (event) => {
|
||||
const ext = fmtToExt(fmt);
|
||||
const outputExt = (provider === "openai" || provider === "piper") ? "wav" : ext;
|
||||
const flightlabRequest = isFlightLabTag(body?.tag);
|
||||
const flightlabCacheKey = flightlabRequest
|
||||
const cacheableRequest = isCacheableTag(body?.tag);
|
||||
const flightlabCacheKey = cacheableRequest
|
||||
? buildFlightLabCacheKey({
|
||||
normalized,
|
||||
level,
|
||||
@@ -207,7 +216,7 @@ export default defineEventHandler(async (event) => {
|
||||
model: providerModel
|
||||
})
|
||||
: null;
|
||||
const flightlabCacheBaseDir = flightlabRequest ? flightLabCacheDir() : null;
|
||||
const flightlabCacheBaseDir = cacheableRequest ? flightLabCacheDir() : null;
|
||||
const flightlabCachedAudioPath = (flightlabCacheBaseDir && flightlabCacheKey)
|
||||
? join(flightlabCacheBaseDir, `${flightlabCacheKey}.${outputExt}`)
|
||||
: null;
|
||||
@@ -286,7 +295,7 @@ export default defineEventHandler(async (event) => {
|
||||
{
|
||||
key: flightlabCacheKey,
|
||||
createdAt: timestamp,
|
||||
tag: "flightlab",
|
||||
tag: (body?.tag || "").trim().toLowerCase() || "flightlab",
|
||||
voice,
|
||||
speed,
|
||||
level,
|
||||
|
||||
Reference in New Issue
Block a user