feat: add BGM playback control for big screen display

- Add audio player to display store with play/stop functions
- Listen for music state changes from AdminState sync
- Support bgm, lottery, and fanfare audio tracks
- Create audio directory structure

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
empty
2026-01-25 22:05:30 +08:00
parent 7dc77da939
commit 4eb2814391
2 changed files with 76 additions and 0 deletions

View File

@@ -14,6 +14,16 @@ import { SOCKET_EVENTS } from '@gala/shared/constants';
type GalaSocket = Socket<ServerToClientEvents, ClientToServerEvents>;
// Audio player singleton
let audioPlayer: HTMLAudioElement | null = null;
// Audio file paths - place audio files in public/audio/
const AUDIO_TRACKS: Record<string, string> = {
bgm: '/audio/bgm.mp3',
lottery: '/audio/lottery.mp3',
fanfare: '/audio/fanfare.mp3',
};
export const useDisplayStore = defineStore('display', () => {
// State - use shallowRef for socket to avoid deep reactivity issues
const socket = shallowRef<GalaSocket | null>(null);
@@ -35,6 +45,10 @@ export const useDisplayStore = defineStore('display', () => {
// QR Code display state (controlled by admin)
const showEntryQR = ref(false);
// Music state
const musicPlaying = ref(false);
const musicTrack = ref<string>('none');
// Computed
const connectionStatus = computed(() => {
if (isConnected.value) return 'connected';
@@ -132,6 +146,20 @@ export const useDisplayStore = defineStore('display', () => {
detail: { mode: newMode, phase: state.systemPhase }
}));
}
// Handle music state changes
if (state.music) {
const { isPlaying, track } = state.music;
if (isPlaying && track && track !== 'none') {
// Only play if not already playing the same track
if (!musicPlaying.value || musicTrack.value !== track) {
const shouldLoop = track === 'bgm' || track === 'lottery';
playAudio(track, shouldLoop);
}
} else if (!isPlaying && musicPlaying.value) {
stopAudio();
}
}
});
// QR Code display control events
@@ -174,6 +202,43 @@ export const useDisplayStore = defineStore('display', () => {
return socket.value;
}
/**
* Play audio track
*/
function playAudio(track: string, loop: boolean = false) {
stopAudio();
const src = AUDIO_TRACKS[track];
if (!src) {
console.warn(`[Screen] Unknown audio track: ${track}`);
return;
}
audioPlayer = new Audio(src);
audioPlayer.loop = loop;
audioPlayer.volume = 0.7;
audioPlayer.play().catch((err) => {
console.error('[Screen] Failed to play audio:', err);
});
musicPlaying.value = true;
musicTrack.value = track;
console.log(`[Screen] Playing audio: ${track}`);
}
/**
* Stop audio playback
*/
function stopAudio() {
if (audioPlayer) {
audioPlayer.pause();
audioPlayer.currentTime = 0;
audioPlayer = null;
}
musicPlaying.value = false;
musicTrack.value = 'none';
console.log('[Screen] Audio stopped');
}
return {
// State (excluding socket to avoid type inference issues)
isConnected,
@@ -184,6 +249,8 @@ export const useDisplayStore = defineStore('display', () => {
currentPrize,
currentWinner,
showEntryQR,
musicPlaying,
musicTrack,
// Computed
connectionStatus,
@@ -193,5 +260,7 @@ export const useDisplayStore = defineStore('display', () => {
disconnect,
setMode,
getSocket,
playAudio,
stopAudio,
};
});