From 95521b5e03af97bc518ca520648e10c5e7caaedd Mon Sep 17 00:00:00 2001 From: Selim Nahimi Date: Fri, 29 Sep 2023 12:15:41 +0000 Subject: [PATCH] Add howler.js for seamless audio looping --- package-lock.json | 12 ++++++++++ package.json | 2 ++ src/components/SoundscapePlayer.vue | 9 ++++---- src/lib/SoundPlayer.ts | 34 +++++++++++++++++++++-------- 4 files changed, 44 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 95710f0..15ea2d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,8 @@ "name": "soundscape-player", "version": "0.0.0", "dependencies": { + "@types/howler": "^2.2.9", + "howler": "^2.2.4", "vue": "^3.3.4", "vue-facing-decorator": "^3.0.2", "vue-router": "^4.2.4", @@ -1067,6 +1069,11 @@ "integrity": "sha512-d6McJeGsuoRlwWZmVIeE8CUA27lu6jLjvv1JzqmpsytOYYbVi1tHZEnwCNVOXnj4pyLvneZlFlpXUK+X9wBWyw==", "dev": true }, + "node_modules/@types/howler": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/@types/howler/-/howler-2.2.9.tgz", + "integrity": "sha512-kNi8mBM+IAmMzo1xhFlR8pKUWqCz9kEtiT1Xp5hZW0ynQl2wUg0EAm+brsHvhahMzxlADt0Xhu0jV/NHmk/Ibg==" + }, "node_modules/@types/json-schema": { "version": "7.0.13", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", @@ -2788,6 +2795,11 @@ "node": "14 || >=16.14" } }, + "node_modules/howler": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/howler/-/howler-2.2.4.tgz", + "integrity": "sha512-iARIBPgcQrwtEr+tALF+rapJ8qSc+Set2GJQl7xT1MQzWaVkFebdJhR3alVlSiUf5U7nAANKuj3aWpwerocD5w==" + }, "node_modules/html-tags": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", diff --git a/package.json b/package.json index b2ac2d5..1e84d45 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "format": "prettier --write src/" }, "dependencies": { + "howler": "^2.2.4", "vue": "^3.3.4", "vue-facing-decorator": "^3.0.2", "vue-router": "^4.2.4", @@ -22,6 +23,7 @@ "@rushstack/eslint-patch": "^1.3.3", "@tsconfig/node18": "^18.2.2", "@types/node": "^18.17.19", + "@types/howler": "^2.2.9", "@vitejs/plugin-vue": "^4.3.4", "@vitejs/plugin-vue-jsx": "^3.0.2", "@vue/eslint-config-prettier": "^8.0.0", diff --git a/src/components/SoundscapePlayer.vue b/src/components/SoundscapePlayer.vue index 7b250a6..7210403 100644 --- a/src/components/SoundscapePlayer.vue +++ b/src/components/SoundscapePlayer.vue @@ -14,7 +14,7 @@ class SoundscapePlayer extends Vue { timers: { [key: number]: ReturnType } = {}; - activeLoopingSounds: HTMLAudioElement[] = []; + activeLoopingSounds: Howl[] = []; mounted() { console.log("playing " + this.soundscape.name); @@ -56,7 +56,8 @@ class SoundscapePlayer extends Vue { private stopLoops() { this.activeLoopingSounds.forEach(sound => { - sound.pause(); + sound.stop(); + sound.unload(); }); this.activeLoopingSounds = []; @@ -141,9 +142,9 @@ class SoundscapePlayer extends Vue { return; let randomVolume = this.getRandomVolume(action); - let sound = SoundPlayer.playSoundFileLoop(soundFile, randomVolume); - this.activeLoopingSounds.push(sound); + SoundPlayer.playSoundFileLoop(soundFile, randomVolume) + .then(audio => this.activeLoopingSounds.push(audio)); } private playActionRandom(action: SoundscapeAction) { diff --git a/src/lib/SoundPlayer.ts b/src/lib/SoundPlayer.ts index 617d8da..0478220 100644 --- a/src/lib/SoundPlayer.ts +++ b/src/lib/SoundPlayer.ts @@ -1,24 +1,40 @@ +import { Howl } from "howler"; + export default class SoundPlayer { static defaultGame = 'hl2'; static playSoundFile(file: File, volume: number = 1.0, game: string = this.defaultGame): HTMLAudioElement { - return this.play(file, volume, false, game); - } - - static playSoundFileLoop(file: File, volume: number = 1.0, game: string = this.defaultGame): HTMLAudioElement { - return this.play(file, volume, true, game); - } - - private static play(file: File, volume: number, loop: boolean = false, game: string): HTMLAudioElement { const url = URL.createObjectURL(file); const audio = new Audio(url); audio.volume = this.clampVolume(volume); - audio.loop = loop; audio.play(); return audio; } + static playSoundFileLoop(file: File, volume: number = 1.0, game: string = this.defaultGame): Promise { + return new Promise((resolve, _) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onloadend = () => { + const base64data = reader.result; + + if (typeof base64data !== 'string') + return; + + var audio = new Howl({ + src: [base64data], + volume: volume, + autoplay: false, + loop: true + }); + audio.play(); + + resolve(audio); + } + }); + } + private static clampVolume(volume: number): number { if (volume > 1.0) { return 1.0;