audio: Bug fix: readerdriver.NewContext might be called multiple times

Updates #1709
This commit is contained in:
Hajime Hoshi 2021-07-10 19:48:00 +09:00
parent 4b7064ac58
commit 1e3df9f391

View File

@ -26,6 +26,8 @@ import (
type readerPlayerFactory struct { type readerPlayerFactory struct {
context readerdriver.Context context readerdriver.Context
sampleRate int sampleRate int
m sync.Mutex
} }
var readerDriverForTesting readerdriver.Context var readerDriverForTesting readerdriver.Context
@ -51,6 +53,9 @@ type readerPlayer struct {
} }
func (f *readerPlayerFactory) newPlayerImpl(context *Context, src io.Reader) (playerImpl, error) { func (f *readerPlayerFactory) newPlayerImpl(context *Context, src io.Reader) (playerImpl, error) {
f.m.Lock()
defer f.m.Unlock()
p := &readerPlayer{ p := &readerPlayer{
src: src, src: src,
context: context, context: context,
@ -61,6 +66,9 @@ func (f *readerPlayerFactory) newPlayerImpl(context *Context, src io.Reader) (pl
} }
func (f *readerPlayerFactory) suspend() error { func (f *readerPlayerFactory) suspend() error {
f.m.Lock()
defer f.m.Unlock()
if f.context == nil { if f.context == nil {
return nil return nil
} }
@ -68,29 +76,51 @@ func (f *readerPlayerFactory) suspend() error {
} }
func (f *readerPlayerFactory) resume() error { func (f *readerPlayerFactory) resume() error {
f.m.Lock()
defer f.m.Unlock()
if f.context == nil { if f.context == nil {
return nil return nil
} }
return f.context.Resume() return f.context.Resume()
} }
func (f *readerPlayerFactory) initContextIfNeeded() (<-chan struct{}, error) {
f.m.Lock()
defer f.m.Unlock()
if f.context != nil {
return nil, nil
}
c, ready, err := readerdriver.NewContext(f.sampleRate, channelNum, bitDepthInBytes)
if err != nil {
return nil, err
}
f.context = c
return ready, nil
}
func (p *readerPlayer) ensurePlayer() error { func (p *readerPlayer) ensurePlayer() error {
// Initialize the underlying player lazily to enable calling NewContext in an 'init' function. // Initialize the underlying player lazily to enable calling NewContext in an 'init' function.
// Accessing the underlying player functions requires the environment to be already initialized, // Accessing the underlying player functions requires the environment to be already initialized,
// but if Ebiten is used for a shared library, the timing when init functions are called // but if Ebiten is used for a shared library, the timing when init functions are called
// is unexpectable. // is unexpectable.
// e.g. a variable for JVM on Android might not be set. // e.g. a variable for JVM on Android might not be set.
if p.factory.context == nil { ready, err := p.factory.initContextIfNeeded()
c, ready, err := readerdriver.NewContext(p.factory.sampleRate, channelNum, bitDepthInBytes)
if err != nil { if err != nil {
return err return err
} }
if ready != nil {
go func() { go func() {
<-ready <-ready
p.context.setReady() p.context.setReady()
}() }()
p.factory.context = c
} }
p.m.Lock()
defer p.m.Unlock()
if p.stream == nil { if p.stream == nil {
s, err := newTimeStream(p.src, p.factory.sampleRate) s, err := newTimeStream(p.src, p.factory.sampleRate)
if err != nil { if err != nil {