diff --git a/src/lib/components/calling/CallScreen.svelte b/src/lib/components/calling/CallScreen.svelte
index b58bab845..bc8e433b3 100644
--- a/src/lib/components/calling/CallScreen.svelte
+++ b/src/lib/components/calling/CallScreen.svelte
@@ -12,9 +12,10 @@
import { _ } from "svelte-i18n"
import type { Chat } from "$lib/types"
import VolumeMixer from "./VolumeMixer.svelte"
- import { onDestroy, onMount } from "svelte"
- import { callTimeout, TIME_TO_SHOW_CONNECTING, VoiceRTCInstance } from "$lib/media/Voice"
+ import { createEventDispatcher, onDestroy, onMount } from "svelte"
+ import { callTimeout, connectionOpened, TIME_TO_SHOW_CONNECTING, usersAcceptedTheCall, VoiceRTCInstance } from "$lib/media/Voice"
import { log } from "$lib/utils/Logger"
+ import { playSound, SoundHandler, Sounds } from "../utils/SoundHandler"
export let expanded: boolean = false
function toggleExanded() {
@@ -30,6 +31,8 @@
export let deafened: boolean = get(Store.state.devices.deafened)
export let chat: Chat
+ let dispatch = createEventDispatcher()
+
function toggleFullscreen() {
const elem = document.getElementById("call-screen")
@@ -117,15 +120,26 @@
let showAnimation = true
let message = $_("settings.calling.connecting")
let timeout: NodeJS.Timeout | undefined
+ let callSound: SoundHandler | undefined = undefined
+
+ $: if ($usersAcceptedTheCall.length > 0) {
+ callSound?.stop()
+ callSound = undefined
+ }
onMount(async () => {
+ callTimeout.set(false)
+ usersAcceptedTheCall.set([])
document.addEventListener("mousedown", handleClickOutside)
await VoiceRTCInstance.setVideoElements(localVideoCurrentSrc)
/// HACK: To make sure the video elements are loaded before we start the call
if (VoiceRTCInstance.localVideoCurrentSrc && VoiceRTCInstance.remoteVideoCreator) {
if (VoiceRTCInstance.toCall && VoiceRTCInstance.toCall.find(did => did !== "") !== undefined) {
+ callSound = await playSound(Sounds.OutgoingCall)
await VoiceRTCInstance.makeCall()
timeout = setTimeout(() => {
+ callSound?.stop()
+ callSound = undefined
showAnimation = false
message = $_("settings.calling.noResponse")
}, TIME_TO_SHOW_CONNECTING)
@@ -138,6 +152,7 @@
})
onDestroy(() => {
+ callTimeout.set(false)
document.removeEventListener("mousedown", handleClickOutside)
subscribeOne()
subscribeTwo()
@@ -146,6 +161,8 @@
if (timeout) {
clearTimeout(timeout)
}
+ callSound?.stop()
+ callSound = undefined
})
@@ -185,13 +202,18 @@
{#if user === get(Store.state.user).key && !userCallOptions.video.enabled}
{:else if $userCache[user] && $userCache[user].key !== get(Store.state.user).key && VoiceRTCInstance.toCall && !$remoteStreams[user]}
- {#if showAnimation}
+ {#if showAnimation && !$usersAcceptedTheCall.includes(user)}
+ {:else if $usersAcceptedTheCall.includes(user)}
+
+
+
{$_("settings.calling.acceptedCall")}
+
{:else}
@@ -322,6 +344,7 @@
on:click={_ => {
Store.endCall()
VoiceRTCInstance.leaveCall()
+ dispatch("endCall")
}}>
diff --git a/src/lib/components/calling/IncomingCall.svelte b/src/lib/components/calling/IncomingCall.svelte
index 6cc789ec8..80a5b7561 100644
--- a/src/lib/components/calling/IncomingCall.svelte
+++ b/src/lib/components/calling/IncomingCall.svelte
@@ -35,8 +35,7 @@
Store.state.pendingCall.subscribe(async _ => {
if (VoiceRTCInstance.incomingCallFrom && !VoiceRTCInstance.toCall && $connectionOpened) {
if (callSound === null || callSound === undefined) {
- callSound = playSound(Sounds.IncomingCall)
- callSound.play()
+ callSound = await playSound(Sounds.IncomingCall)
}
pending = true
let chat = UIStore.getChat(VoiceRTCInstance.incomingCallFrom[1].metadata.channel)
@@ -48,11 +47,11 @@
user = Store.getUser(VoiceRTCInstance.incomingCallFrom[1].metadata.did)
} else if (!$connectionOpened) {
cancelledCall = true
+ callSound?.stop()
+ callSound = undefined
timeOutToCancel = setTimeout(() => {
cancelledCall = false
pending = false
- callSound?.stop()
- callSound = undefined
}, 4000)
} else {
pending = false
diff --git a/src/lib/components/utils/SoundHandler.ts b/src/lib/components/utils/SoundHandler.ts
index 2ef6ffc06..16808f8ba 100644
--- a/src/lib/components/utils/SoundHandler.ts
+++ b/src/lib/components/utils/SoundHandler.ts
@@ -1,3 +1,4 @@
+import { log } from "$lib/utils/Logger"
import { Howl } from "howler"
export enum Sounds {
@@ -23,15 +24,17 @@ export type SoundOption = {
export class SoundHandler {
private sound: Howl
+ public soundID: number = -1
private muted: boolean = false
- constructor(sound: Howl) {
+ constructor(sound: Howl, soundID: number) {
this.sound = sound
+ this.soundID = soundID
}
mute() {
this.muted = !this.muted
- this.sound.mute(!this.muted)
+ this.sound.mute(!this.muted, this.soundID)
}
isMuted(): boolean {
@@ -39,30 +42,46 @@ export class SoundHandler {
}
pause() {
- this.sound.pause()
+ this.sound.pause(this.soundID)
}
play() {
- this.sound.play()
+ this.sound.play(undefined, true)
}
paused(): boolean {
- return !this.sound.playing()
+ return !this.sound.playing(this.soundID)
}
stop() {
- this.sound.stop()
+ this.sound.stop(0, true)
this.sound.unload()
}
}
-export function playSound(src: string | Sounds, opt?: SoundOption): SoundHandler {
- var sound = new Howl({
- src: [src.toString()],
- html5: opt?.large,
- volume: opt?.volume ? opt?.volume : 1,
- loop: opt?.loop,
- })
- sound.play()
- return new SoundHandler(sound)
+export async function playSound(src: string | Sounds, opt?: SoundOption): Promise
{
+ try {
+ // Ask for permission to play sound
+ if (Notification.permission !== "granted") {
+ const permission = await Notification.requestPermission()
+ if (permission !== "granted") {
+ log.warn("Permission to play sound not granted")
+ return undefined
+ }
+ }
+
+ const sound = new Howl({
+ src: [src.toString()],
+ html5: opt?.large || false,
+ volume: opt?.volume || 1,
+ loop: opt?.loop || false,
+ })
+
+ let soundID = sound.play(undefined, false)
+
+ return new SoundHandler(sound, soundID)
+ } catch (error) {
+ log.error("Error to play callsound:", error)
+ return undefined
+ }
}
diff --git a/src/lib/lang/en.json b/src/lib/lang/en.json
index c5ea93788..d4d53c904 100644
--- a/src/lib/lang/en.json
+++ b/src/lib/lang/en.json
@@ -347,6 +347,7 @@
"disconnecting": "Disconnecting...",
"userInviteToAGroupCall": "is inviting you to join a group call",
"noAnswer": "No answer, leaving the call...",
+ "acceptedCall": "Joined, loading...",
"connecting": "Connecting...",
"noResponse": "No response"
},
diff --git a/src/lib/media/Voice.ts b/src/lib/media/Voice.ts
index b8f1bfa99..9eda0a83f 100644
--- a/src/lib/media/Voice.ts
+++ b/src/lib/media/Voice.ts
@@ -1,12 +1,11 @@
import { CallDirection } from "$lib/enums"
-import { SettingsStore } from "$lib/state"
import { Store } from "$lib/state/Store"
import { create_cancellable_handler, type Cancellable } from "$lib/utils/CancellablePromise"
import { log } from "$lib/utils/Logger"
import { RaygunStoreInstance } from "$lib/wasm/RaygunStore"
import Peer, { DataConnection } from "peerjs"
import { _ } from "svelte-i18n"
-import { get, writable } from "svelte/store"
+import { get, writable, type Writable } from "svelte/store"
import type { Room } from "trystero"
import { joinRoom } from "trystero/ipfs"
@@ -17,6 +16,7 @@ const TIME_TO_SHOW_END_CALL_FEEDBACK = 3500
export const TIME_TO_SHOW_CONNECTING = 30000
let timeOuts: NodeJS.Timeout[] = []
+export const usersAcceptedTheCall: Writable = writable([])
export enum VoiceRTCMessageType {
UpdateUser = "UPDATE_USER",
@@ -507,6 +507,8 @@ export class VoiceRTC {
})
conn.once("data", d => {
if (d === CALL_ACK) {
+ callTimeout.set(false)
+ usersAcceptedTheCall.set([...get(usersAcceptedTheCall), did])
accepted = true
}
})
@@ -591,7 +593,9 @@ export class VoiceRTC {
}
async leaveCall(sendEndCallMessage = false) {
+ callTimeout.set(false)
connectionOpened.set(false)
+ usersAcceptedTheCall.set([])
timeOuts.forEach(t => clearTimeout(t))
sendEndCallMessage = sendEndCallMessage && this.channel !== undefined && this.call != null
if (sendEndCallMessage && this.call?.start) {
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index e1b2b149f..c3a5b5d9c 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -231,6 +231,33 @@
console.log("Arriving here on +layout")
+ window.addEventListener(
+ "click",
+ () => {
+ initializeAudioContext()
+ log.debug("Audio context unlocked after click.")
+ },
+ { once: true }
+ )
+
+ window.addEventListener(
+ "touchstart",
+ () => {
+ initializeAudioContext()
+ log.debug("Audio context unlocked after touch.")
+ },
+ { once: true }
+ )
+
+ function initializeAudioContext() {
+ const audioContext = new window.AudioContext()
+ if (audioContext.state === "suspended") {
+ audioContext.resume().then(() => {
+ log.info("Audio context unlocked.")
+ })
+ }
+ }
+
let isLocaleSet = false
$: if ($locale) {
diff --git a/src/routes/chat/+page.svelte b/src/routes/chat/+page.svelte
index 5611cc130..e467c7e92 100644
--- a/src/routes/chat/+page.svelte
+++ b/src/routes/chat/+page.svelte
@@ -655,7 +655,12 @@
{/if}
{#if activeCallInProgress && activeCallDid === $activeChat.id}
-
+ {
+ activeCallInProgress = false
+ activeCallDid = ""
+ }} />
{/if}