This commit is contained in:
2026-10-12 14:58:24 +07:00
parent 8f54434757
commit 4a89dd1d74
2 changed files with 20 additions and 19 deletions
Binary file not shown.
+20 -19
View File
@@ -44,6 +44,7 @@ import type {
TeamResult, TeamResult,
Ticket, Ticket,
} from '@/api/generated/crabs/evening_detective_server' } from '@/api/generated/crabs/evening_detective_server'
import notificationSoundURL from '@/assets/icq-uh-oh.wav'
import GameMapPanel from '@/components/GameMapPanel.vue' import GameMapPanel from '@/components/GameMapPanel.vue'
import GameScreensPanel from '@/components/GameScreensPanel.vue' import GameScreensPanel from '@/components/GameScreensPanel.vue'
import GameStatePanel from '@/components/GameStatePanel.vue' import GameStatePanel from '@/components/GameStatePanel.vue'
@@ -134,6 +135,7 @@ let appliedGameRequestVersion = 0
let hasLoadedGame = false let hasLoadedGame = false
let isDisposed = false let isDisposed = false
let notificationAudio: AudioContext | undefined let notificationAudio: AudioContext | undefined
let notificationSound: Promise<AudioBuffer> | undefined
let hasReportedSoundError = false let hasReportedSoundError = false
const canViewState = computed( const canViewState = computed(
() => () =>
@@ -239,30 +241,29 @@ async function enableNotificationSound() {
try { try {
notificationAudio ??= new AudioContext() notificationAudio ??= new AudioContext()
if (notificationAudio.state === 'suspended') await notificationAudio.resume() if (notificationAudio.state === 'suspended') await notificationAudio.resume()
const audio = notificationAudio
notificationSound ??= fetch(notificationSoundURL).then(async (response) => {
if (!response.ok) throw new Error('Не удалось загрузить звук уведомления')
return audio.decodeAudioData(await response.arrayBuffer())
})
await notificationSound
} catch (error) { } catch (error) {
notificationSound = undefined
reportSoundError(error) reportSoundError(error)
} }
} }
function playApplicationNotification() { async function playApplicationNotification() {
// До первого взаимодействия браузер блокирует звук; пропущенные сигналы не накапливаем. // До первого взаимодействия браузер блокирует звук; пропущенные сигналы не накапливаем.
if (!notificationAudio || notificationAudio.state !== 'running') return if (!notificationAudio || notificationAudio.state !== 'running' || !notificationSound) return
try { try {
const oscillator = notificationAudio.createOscillator() const buffer = await notificationSound
const gain = notificationAudio.createGain() if (isDisposed || notificationAudio.state !== 'running') return
const now = notificationAudio.currentTime const source = notificationAudio.createBufferSource()
oscillator.frequency.value = 880 source.buffer = buffer
gain.gain.setValueAtTime(0, now) source.connect(notificationAudio.destination)
gain.gain.linearRampToValueAtTime(0.1, now + 0.02) source.onended = () => source.disconnect()
gain.gain.linearRampToValueAtTime(0, now + 0.3) source.start()
oscillator.connect(gain)
gain.connect(notificationAudio.destination)
oscillator.onended = () => {
oscillator.disconnect()
gain.disconnect()
}
oscillator.start(now)
oscillator.stop(now + 0.3)
} catch (error) { } catch (error) {
reportSoundError(error) reportSoundError(error)
} }
@@ -270,7 +271,7 @@ function playApplicationNotification() {
async function testNotificationSound() { async function testNotificationSound() {
await enableNotificationSound() await enableNotificationSound()
playApplicationNotification() await playApplicationNotification()
} }
onMounted(() => { onMounted(() => {
@@ -303,7 +304,7 @@ async function getGame(id: number) {
hasLoadedGame && hasLoadedGame &&
hasNewTeamApplications(game.value.teams || [], res.game?.teams || []) hasNewTeamApplications(game.value.teams || [], res.game?.teams || [])
) { ) {
playApplicationNotification() void playApplicationNotification()
} }
hasLoadedGame = true hasLoadedGame = true
game.value = res.game! game.value = res.game!