From 288c51aa0de82d1b7eed965b5ef942ae23595258 Mon Sep 17 00:00:00 2001
From: Alexey Zinoviev <alexey.zinoviev@xored.com>
Date: Tue, 15 Oct 2024 21:31:49 +0400
Subject: [PATCH] uberf-8485: fix sounds (#6944)

Signed-off-by: Alexey Zinoviev <alexey.zinoviev@xored.com>
---
 packages/presentation/src/sound.ts            | 70 ++++++++++---------
 .../src/components/RequestPopup.svelte        | 11 +--
 2 files changed, 45 insertions(+), 36 deletions(-)

diff --git a/packages/presentation/src/sound.ts b/packages/presentation/src/sound.ts
index 1be62940a7..eff939be10 100644
--- a/packages/presentation/src/sound.ts
+++ b/packages/presentation/src/sound.ts
@@ -3,10 +3,10 @@ import { type Asset, getMetadata, getResource } from '@hcengineering/platform'
 import { getClient } from '.'
 import notification from '@hcengineering/notification'
 
-const sounds = new Map<Asset, AudioBufferSourceNode>()
+const sounds = new Map<Asset, AudioBuffer>()
 const context = new AudioContext()
 
-export async function prepareSound (key: string, _class?: Ref<Class<Doc>>, loop = false, play = false): Promise<void> {
+export async function prepareSound (key: string, _class?: Ref<Class<Doc>>): Promise<void> {
   if (_class === undefined) return
 
   const client = getClient()
@@ -23,39 +23,45 @@ export async function prepareSound (key: string, _class?: Ref<Class<Doc>>, loop
 
   try {
     const soundUrl = getMetadata(key as Asset) as string
-    const audioBuffer = await fetch(soundUrl)
-      .then(async (res) => await res.arrayBuffer())
-      .then(async (ArrayBuffer) => await context.decodeAudioData(ArrayBuffer))
+    const rawAudio = await fetch(soundUrl)
+    const rawBuffer = await rawAudio.arrayBuffer()
+    const decodedBuffer = await context.decodeAudioData(rawBuffer)
+
+    sounds.set(key as Asset, decodedBuffer)
+  } catch (err) {
+    console.error('Sound not found', key)
+  }
+}
+
+export async function playSound (
+  soundKey: string,
+  _class?: Ref<Class<Doc>>,
+  loop = false
+): Promise<(() => void) | null> {
+  const soundAssetKey = soundKey as Asset
+  if (!sounds.has(soundAssetKey)) {
+    await prepareSound(soundKey, _class)
+  }
+
+  const sound = sounds.get(soundKey as Asset)
+  if (sound === undefined) {
+    console.error('Cannot prepare audio buffer', soundKey)
+    return null
+  }
+
+  try {
     const audio = context.createBufferSource()
-    audio.buffer = audioBuffer
+    audio.buffer = sound
     audio.loop = loop
-    sounds.set(key as Asset, audio)
-    if (play) {
-      playSound(key)
+    audio.connect(context.destination)
+    audio.start()
+
+    return (): void => {
+      audio.stop()
+      audio.disconnect(context.destination)
     }
   } catch (err) {
-    console.error('sound not found', key)
-  }
-}
-
-export function playSound (soundKey: string, _class?: Ref<Class<Doc>>, loop = false): void {
-  const sound = sounds.get(soundKey as Asset)
-  if (sound !== undefined) {
-    try {
-      sound.connect(context.destination)
-      sound.start()
-    } catch (err) {
-      console.error('error happened during sound play', soundKey, err)
-    }
-  } else {
-    void prepareSound(soundKey, _class, loop, true)
-  }
-}
-
-export function stopSound (soundKey: string): void {
-  const sound = sounds.get(soundKey as Asset)
-  if (sound !== undefined && sound?.context.state === 'running') {
-    sound.stop()
-    sound.disconnect(context.destination)
+    console.error('Error when playing sound back', soundKey, err)
+    return null
   }
 }
diff --git a/plugins/love-resources/src/components/RequestPopup.svelte b/plugins/love-resources/src/components/RequestPopup.svelte
index cd285c49cd..650761715d 100644
--- a/plugins/love-resources/src/components/RequestPopup.svelte
+++ b/plugins/love-resources/src/components/RequestPopup.svelte
@@ -16,7 +16,7 @@
   import { PersonAccount, formatName } from '@hcengineering/contact'
   import { Avatar, personByIdStore } from '@hcengineering/contact-resources'
   import { getCurrentAccount } from '@hcengineering/core'
-  import { getClient, playSound, stopSound } from '@hcengineering/presentation'
+  import { getClient, playSound } from '@hcengineering/presentation'
   import { Button, Label } from '@hcengineering/ui'
   import { JoinRequest, RequestStatus } from '@hcengineering/love'
   import love from '../plugin'
@@ -29,6 +29,7 @@
   $: person = $personByIdStore.get(request.person)
 
   const client = getClient()
+  let stopSound: (() => void) | null = null
 
   async function accept (): Promise<void> {
     await client.update(request, { status: RequestStatus.Approved })
@@ -43,11 +44,13 @@
   async function decline (): Promise<void> {
     await client.update(request, { status: RequestStatus.Rejected })
   }
-  onMount(() => {
-    playSound(love.sound.Knock, love.class.JoinRequest, true)
+
+  onMount(async () => {
+    stopSound = await playSound(love.sound.Knock, love.class.JoinRequest, true)
   })
+
   onDestroy(() => {
-    stopSound(love.sound.Knock)
+    stopSound?.()
   })
 </script>