diff --git a/frontend/pages/communication/phone.vue b/frontend/pages/communication/phone.vue index 1f0e878..53bd080 100644 --- a/frontend/pages/communication/phone.vue +++ b/frontend/pages/communication/phone.vue @@ -24,6 +24,15 @@ const incomingCall = ref(null) const callState = ref("idle") const registererState = ref("Initial") const sipEvents = ref([]) +const ringtoneTimer = shallowRef(null) +const ringtoneAudioContext = shallowRef(null) +const callSignalStatus = ref("Bereit") +const originalDocumentTitle = ref("") + +const incomingCaller = computed(() => { + const identity = incomingCall.value?.remoteIdentity + return identity?.displayName || identity?.uri?.user || "Unbekannter Anrufer" +}) const addSipEvent = (message) => { sipEvents.value = [ @@ -35,6 +44,72 @@ const addSipEvent = (message) => { ].slice(0, 8) } +const playRingtoneTick = async () => { + if (!window.AudioContext && !window.webkitAudioContext) return + + const AudioContextConstructor = window.AudioContext || window.webkitAudioContext + const context = ringtoneAudioContext.value || new AudioContextConstructor() + ringtoneAudioContext.value = context + + if (context.state === "suspended") { + await context.resume() + } + + const oscillator = context.createOscillator() + const gain = context.createGain() + oscillator.type = "sine" + oscillator.frequency.value = 880 + gain.gain.setValueAtTime(0.0001, context.currentTime) + gain.gain.exponentialRampToValueAtTime(0.12, context.currentTime + 0.03) + gain.gain.exponentialRampToValueAtTime(0.0001, context.currentTime + 0.35) + oscillator.connect(gain) + gain.connect(context.destination) + oscillator.start() + oscillator.stop(context.currentTime + 0.38) +} + +const startCallSignaling = async () => { + stopCallSignaling() + callSignalStatus.value = "Signalisiert" + originalDocumentTitle.value = document.title + document.title = "Eingehender Anruf - FEDEO" + + if (navigator.vibrate) { + navigator.vibrate([180, 90, 180]) + } + + try { + await playRingtoneTick() + ringtoneTimer.value = window.setInterval(() => { + playRingtoneTick().catch((error) => { + callSignalStatus.value = "Klingelton blockiert" + addSipEvent(`Klingelton blockiert: ${error?.message || "Browser-Autoplay"}`) + }) + }, 1400) + } catch (error) { + callSignalStatus.value = "Klingelton blockiert" + addSipEvent(`Klingelton blockiert: ${error?.message || "Browser-Autoplay"}`) + } +} + +const stopCallSignaling = () => { + if (ringtoneTimer.value) { + window.clearInterval(ringtoneTimer.value) + ringtoneTimer.value = null + } + + if (originalDocumentTitle.value) { + document.title = originalDocumentTitle.value + originalDocumentTitle.value = "" + } + + if (navigator.vibrate) { + navigator.vibrate(0) + } + + callSignalStatus.value = "Bereit" +} + const selectedAccount = computed(() => (config.value?.testAccounts || []).find((account) => account.extension === selectedExtension.value) || config.value?.testAccounts?.[0] @@ -203,6 +278,7 @@ const setupSession = (session, direction = "outgoing") => { } if (state === SessionState.Established) { + stopCallSignaling() callState.value = "active" sipStatus.value = "Im Gespräch" incomingCall.value = null @@ -210,6 +286,7 @@ const setupSession = (session, direction = "outgoing") => { } if (state === SessionState.Terminated) { + stopCallSignaling() callState.value = "terminated" sipStatus.value = sipRegistered.value ? "Registriert" : "Nicht verbunden" activeSession.value = null @@ -268,6 +345,7 @@ const registerSip = async () => { addSipEvent(`INVITE von ${invitation.remoteIdentity?.uri?.user || "unbekannt"} empfangen`) incomingCall.value = invitation setupSession(invitation, "incoming") + startCallSignaling() toast.add({ title: "Eingehender Anruf", @@ -411,6 +489,7 @@ const acceptIncomingCall = async () => { sipLoading.value = true sipError.value = null + stopCallSignaling() try { await incomingCall.value.accept({ @@ -432,6 +511,7 @@ const rejectIncomingCall = async () => { if (!incomingCall.value) return try { + stopCallSignaling() await incomingCall.value.reject() } finally { incomingCall.value = null @@ -457,6 +537,7 @@ const hangupCall = async () => { session.dispose() } } finally { + stopCallSignaling() activeSession.value = null incomingCall.value = null callState.value = "idle" @@ -474,6 +555,7 @@ watch(config, (nextConfig) => { onMounted(loadTelephony) onBeforeUnmount(() => { + stopCallSignaling() stopSip() }) @@ -481,6 +563,60 @@ onBeforeUnmount(() => {