Skip to content
Open
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
64 changes: 50 additions & 14 deletions driver_js.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type context struct {
audioContext js.Value
scriptProcessor js.Value
scriptProcessorCallback js.Func
ready bool
err atomicError

mux *mux.Mux
}
Expand Down Expand Up @@ -57,6 +57,8 @@ func newContext(sampleRate int, channelCount int, format mux.Format, bufferSizeI
}

buf32 := make([]float32, bufferSizeInBytes/4)
var moduleReady bool
var finishInitialization func()

if w := d.audioContext.Get("audioWorklet"); w.Truthy() {
script := fmt.Sprintf(`
Expand Down Expand Up @@ -112,9 +114,14 @@ class OtoWorkletProcessor extends AudioWorkletProcessor {
registerProcessor('oto-worklet-processor', OtoWorkletProcessor);
`, bufferSizeInBytes/4/channelCount, channelCount)
scriptURL := newScriptURL(script)
var onAddModuleSuccess js.Func
onAddModuleSuccess = js.FuncOf(func(this js.Value, arguments []js.Value) any {
var onAddModuleSuccess, onAddModuleFailure js.Func
release := func() {
js.Global().Get("URL").Call("revokeObjectURL", scriptURL)
onAddModuleSuccess.Release()
onAddModuleFailure.Release()
}
onAddModuleSuccess = js.FuncOf(func(this js.Value, arguments []js.Value) any {
defer release()

node := js.Global().Get("AudioWorkletNode").New(d.audioContext, "oto-worklet-processor", map[string]any{
"outputChannelCount": []any{channelCount},
Expand All @@ -131,10 +138,17 @@ registerProcessor('oto-worklet-processor', OtoWorkletProcessor);
}))
node.Call("connect", d.audioContext.Get("destination"))

onAddModuleSuccess.Release()
moduleReady = true
finishInitialization()
return nil
})
w.Call("addModule", scriptURL).Call("then", onAddModuleSuccess)
onAddModuleFailure = js.FuncOf(func(this js.Value, arguments []js.Value) any {
defer release()
d.err.Join(fmt.Errorf("oto: AudioWorklet.addModule failed: %s", js.Global().Get("String").Invoke(arguments[0]).String()))
finishInitialization()
return nil
})
w.Call("addModule", scriptURL).Call("then", onAddModuleSuccess, onAddModuleFailure)
} else {
// Use ScriptProcessorNode if AudioWorklet is not available.

Expand Down Expand Up @@ -169,29 +183,51 @@ registerProcessor('oto-worklet-processor', OtoWorkletProcessor);
d.scriptProcessor = sp
d.scriptProcessorCallback = f
sp.Call("connect", d.audioContext.Get("destination"))
moduleReady = true
}

// Browsers require user interaction to start the audio.
// https://developers.google.com/web/updates/2017/09/autoplay-policy-changes#webaudio

events := []string{"touchend", "keyup", "mouseup"}

var resumed, resuming, finished bool
var onEventFired js.Func
var onResumeSuccess js.Func
onResumeSuccess = js.FuncOf(func(this js.Value, arguments []js.Value) any {
d.ready = true
finishInitialization = func() {
if finished || (d.err.Load() == nil && (!moduleReady || !resumed)) {
return
}
finished = true
close(ready)
for _, event := range events {
js.Global().Get("document").Call("removeEventListener", event, onEventFired)
}
onEventFired.Release()
onResumeSuccess.Release()
return nil
})
}
onEventFired = js.FuncOf(func(this js.Value, arguments []js.Value) any {
if !d.ready {
d.audioContext.Call("resume").Call("then", onResumeSuccess)
if resumed || resuming || finished {
return nil
}
resuming = true
// A module load failure can finish initialization while resume is pending.
// Keep these callbacks alive until their promise settles.
var onSuccess, onFailure js.Func
release := func() {
resuming = false
onSuccess.Release()
onFailure.Release()
}
onSuccess = js.FuncOf(func(this js.Value, arguments []js.Value) any {
defer release()
resumed = true
finishInitialization()
return nil
})
onFailure = js.FuncOf(func(this js.Value, arguments []js.Value) any {
defer release()
return nil
})
d.audioContext.Call("resume").Call("then", onSuccess, onFailure)
return nil
})
for _, event := range events {
Expand All @@ -212,7 +248,7 @@ func (c *context) Resume() error {
}

func (c *context) Err() error {
return nil
return c.err.Load()
}

func float32SliceToTypedArray(s []float32) js.Value {
Expand Down
Loading