Skip to content

Commit 6e87867

Browse files
committed
fixed sound clipping and overlapping
1 parent 66d5668 commit 6e87867

File tree

4 files changed

+60
-35
lines changed

4 files changed

+60
-35
lines changed

web/speakerbob/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@
6969
"rules": {
7070
"@typescript-eslint/no-explicit-any": 0,
7171
"no-case-declarations": 0,
72-
"@typescript-eslint/no-unused-vars": 0
72+
"@typescript-eslint/no-unused-vars": 0,
73+
"@typescript-eslint/no-non-null-assertion": 0
7374
}
7475
},
7576
"browserslist": [

web/speakerbob/src/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ const router = new VueRouter({
2121
})
2222

2323
const wsConnection = new WSConnection()
24-
const player = new Player()
2524
const api = new API(router)
2625
const auth = new Auth(router)
26+
const player = new Player(api.api)
2727
const workbox = new Workbox()
2828

2929
router.beforeEach(wsConnection.NavigationGuard)

web/speakerbob/src/plugins/api.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
import { Vue as _Vue } from 'vue/types/vue'
2-
import axios from 'axios'
2+
import axios, { AxiosInstance } from 'axios'
33
import VueRouter from 'vue-router'
44
export class APIOptions {}
55

66
export default class API {
77
private router!: VueRouter
8+
public readonly api!: AxiosInstance
89

910
constructor (router: VueRouter) {
1011
this.router = router
1112

1213
this.validateStatus = this.validateStatus.bind(this)
13-
}
1414

15-
public install (Vue: typeof _Vue, _options?: APIOptions) {
16-
Vue.prototype.$api = axios.create({
15+
this.api = axios.create({
1716
baseURL: '/api/',
1817
validateStatus: this.validateStatus,
1918
withCredentials: true,
@@ -23,6 +22,10 @@ export default class API {
2322
})
2423
}
2524

25+
public install (Vue: typeof _Vue, _options?: APIOptions) {
26+
Vue.prototype.$api = this.api
27+
}
28+
2629
private validateStatus (status: number): boolean {
2730
// any 2xx response is valid
2831
if (status >= 200 && status <= 299) {
Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,86 @@
11
import { Sound } from '@/definitions/sound'
22
import { Vue as _Vue } from 'vue/types/vue'
33
import { WebsocketOptions } from '@/plugins/websocket'
4+
import { AxiosInstance, AxiosResponse } from 'axios'
5+
6+
class AsyncBlockingQueue {
7+
private promises: Promise<Sound>[] = [];
8+
private resolvers: ((t: Sound) => void)[] = [];
9+
10+
private add () {
11+
this.promises.push(new Promise(resolve => {
12+
this.resolvers.push(resolve)
13+
}))
14+
}
15+
16+
public enqueue (sound: Sound) {
17+
if (!this.resolvers.length) this.add()
18+
const resolve = this.resolvers.shift()!
19+
resolve(sound)
20+
}
21+
22+
public dequeue (): Promise<Sound> {
23+
if (!this.promises.length) this.add()
24+
return this.promises.shift()!
25+
}
26+
27+
public isEmpty (): boolean {
28+
return !this.promises.length
29+
}
30+
}
431

532
export default class Player {
633
private enabled = false
7-
private audio!: HTMLAudioElement;
8-
private queue: Sound[] = [];
9-
private isPlaying = false;
34+
private isPlaying = false
35+
private queue: AsyncBlockingQueue = new AsyncBlockingQueue()
1036

11-
public constructor () {
37+
private ctx!: AudioContext
38+
private api!: AxiosInstance
39+
40+
public constructor (api: AxiosInstance) {
1241
this.install = this.install.bind(this)
1342
this.OnPlayMessage = this.OnPlayMessage.bind(this)
1443
this.EnableSound = this.EnableSound.bind(this)
1544
this.playNextSound = this.playNextSound.bind(this)
1645

17-
this.audio = new Audio()
46+
this.api = api
1847
}
1948

2049
public install (Vue: typeof _Vue, _options?: WebsocketOptions) {
2150
Vue.prototype.$player = this
2251
}
2352

24-
public async OnPlayMessage (sound: any) {
53+
public async OnPlayMessage (message: any) {
2554
if (!this.enabled) {
2655
return
2756
}
2857

29-
this.queue.push(sound.sound)
58+
// add this sound to the queue
59+
this.queue.enqueue(message.sound)
3060

31-
await this.playNextSound()
61+
if (!this.isPlaying) {
62+
await this.playNextSound()
63+
}
3264
}
3365

3466
public async EnableSound () {
35-
this.audio.src = ''
36-
37-
try {
38-
await this.audio.play()
39-
} catch {}
67+
this.ctx = this.ctx = new window.AudioContext()
4068

4169
this.enabled = true
4270
}
4371

4472
private async playNextSound () {
45-
if (this.isPlaying) {
46-
return
47-
}
73+
this.isPlaying = true
4874

49-
while (true) {
50-
const sound = this.queue.pop()
75+
const sound = await this.queue.dequeue()
5176

52-
if (sound === undefined) {
53-
return
54-
}
77+
const resp: AxiosResponse = await this.api.get(`/sound/sounds/${sound.id}/download/`, { responseType: 'arraybuffer' })
5578

56-
this.audio.src = `/api/sound/sounds/${sound.id}/download/`
57-
58-
this.isPlaying = true
59-
60-
await this.audio.play()
61-
62-
this.isPlaying = false
63-
}
79+
const buf = await this.ctx.decodeAudioData(resp.data)
80+
const source = this.ctx.createBufferSource()
81+
source.buffer = buf
82+
source.connect(this.ctx.destination)
83+
source.start()
84+
source.onended = this.playNextSound
6485
}
6586
}

0 commit comments

Comments
 (0)