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:
7
packages/client-screen/public/audio/.gitkeep
Normal file
7
packages/client-screen/public/audio/.gitkeep
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Audio Files Directory
|
||||||
|
|
||||||
|
Place the following audio files here:
|
||||||
|
|
||||||
|
- bgm.mp3 - Background music (will loop)
|
||||||
|
- lottery.mp3 - Lottery spinning music (will loop)
|
||||||
|
- fanfare.mp3 - Winner celebration sound effect
|
||||||
@@ -14,6 +14,16 @@ import { SOCKET_EVENTS } from '@gala/shared/constants';
|
|||||||
|
|
||||||
type GalaSocket = Socket<ServerToClientEvents, ClientToServerEvents>;
|
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', () => {
|
export const useDisplayStore = defineStore('display', () => {
|
||||||
// State - use shallowRef for socket to avoid deep reactivity issues
|
// State - use shallowRef for socket to avoid deep reactivity issues
|
||||||
const socket = shallowRef<GalaSocket | null>(null);
|
const socket = shallowRef<GalaSocket | null>(null);
|
||||||
@@ -35,6 +45,10 @@ export const useDisplayStore = defineStore('display', () => {
|
|||||||
// QR Code display state (controlled by admin)
|
// QR Code display state (controlled by admin)
|
||||||
const showEntryQR = ref(false);
|
const showEntryQR = ref(false);
|
||||||
|
|
||||||
|
// Music state
|
||||||
|
const musicPlaying = ref(false);
|
||||||
|
const musicTrack = ref<string>('none');
|
||||||
|
|
||||||
// Computed
|
// Computed
|
||||||
const connectionStatus = computed(() => {
|
const connectionStatus = computed(() => {
|
||||||
if (isConnected.value) return 'connected';
|
if (isConnected.value) return 'connected';
|
||||||
@@ -132,6 +146,20 @@ export const useDisplayStore = defineStore('display', () => {
|
|||||||
detail: { mode: newMode, phase: state.systemPhase }
|
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
|
// QR Code display control events
|
||||||
@@ -174,6 +202,43 @@ export const useDisplayStore = defineStore('display', () => {
|
|||||||
return socket.value;
|
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 {
|
return {
|
||||||
// State (excluding socket to avoid type inference issues)
|
// State (excluding socket to avoid type inference issues)
|
||||||
isConnected,
|
isConnected,
|
||||||
@@ -184,6 +249,8 @@ export const useDisplayStore = defineStore('display', () => {
|
|||||||
currentPrize,
|
currentPrize,
|
||||||
currentWinner,
|
currentWinner,
|
||||||
showEntryQR,
|
showEntryQR,
|
||||||
|
musicPlaying,
|
||||||
|
musicTrack,
|
||||||
|
|
||||||
// Computed
|
// Computed
|
||||||
connectionStatus,
|
connectionStatus,
|
||||||
@@ -193,5 +260,7 @@ export const useDisplayStore = defineStore('display', () => {
|
|||||||
disconnect,
|
disconnect,
|
||||||
setMode,
|
setMode,
|
||||||
getSocket,
|
getSocket,
|
||||||
|
playAudio,
|
||||||
|
stopAudio,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user