Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(Call): Fix call sounds in general #719

Merged
merged 6 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions src/lib/components/calling/CallScreen.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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")

Expand Down Expand Up @@ -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)
Expand All @@ -138,6 +152,7 @@
})

onDestroy(() => {
callTimeout.set(false)
document.removeEventListener("mousedown", handleClickOutside)
subscribeOne()
subscribeTwo()
Expand All @@ -146,6 +161,8 @@
if (timeout) {
clearTimeout(timeout)
}
callSound?.stop()
callSound = undefined
})
</script>

Expand Down Expand Up @@ -185,13 +202,18 @@
{#if user === get(Store.state.user).key && !userCallOptions.video.enabled}
<Participant participant={$userCache[user]} hasVideo={$userCache[user].media.is_streaming_video} isMuted={muted} isDeafened={userCallOptions.audio.deafened} isTalking={$userCache[user].media.is_playing_audio} />
{:else if $userCache[user] && $userCache[user].key !== get(Store.state.user).key && VoiceRTCInstance.toCall && !$remoteStreams[user]}
{#if showAnimation}
{#if showAnimation && !$usersAcceptedTheCall.includes(user)}
<div class="calling-animation">
<div class="shaking-participant">
<Participant participant={$userCache[user]} hasVideo={false} isMuted={true} isDeafened={true} isTalking={false} />
<p>{message}</p>
</div>
</div>
{:else if $usersAcceptedTheCall.includes(user)}
<div class="no-response">
<Participant participant={$userCache[user]} hasVideo={false} isMuted={true} isDeafened={true} isTalking={false} />
<p>{$_("settings.calling.acceptedCall")}</p>
</div>
{:else}
<div class="no-response">
<Participant participant={$userCache[user]} hasVideo={false} isMuted={true} isDeafened={true} isTalking={false} />
Expand Down Expand Up @@ -322,6 +344,7 @@
on:click={_ => {
Store.endCall()
VoiceRTCInstance.leaveCall()
dispatch("endCall")
}}>
<Icon icon={Shape.PhoneXMark} />
</Button>
Expand Down
7 changes: 3 additions & 4 deletions src/lib/components/calling/IncomingCall.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
49 changes: 34 additions & 15 deletions src/lib/components/utils/SoundHandler.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { log } from "$lib/utils/Logger"
import { Howl } from "howler"

export enum Sounds {
Expand All @@ -23,46 +24,64 @@ 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 {
return this.muted
}

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<SoundHandler | undefined> {
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
}
}
1 change: 1 addition & 0 deletions src/lib/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
8 changes: 6 additions & 2 deletions src/lib/media/Voice.ts
Original file line number Diff line number Diff line change
@@ -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"

Expand All @@ -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<string[]> = writable([])

export enum VoiceRTCMessageType {
UpdateUser = "UPDATE_USER",
Expand Down Expand Up @@ -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
}
})
Expand Down Expand Up @@ -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) {
Expand Down
27 changes: 27 additions & 0 deletions src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
7 changes: 6 additions & 1 deletion src/routes/chat/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,12 @@
</Topbar>
{/if}
{#if activeCallInProgress && activeCallDid === $activeChat.id}
<CallScreen chat={$activeChat} />
<CallScreen
chat={$activeChat}
on:endCall={_ => {
activeCallInProgress = false
activeCallDid = ""
}} />
{/if}
<Conversation
loading={loading}
Expand Down