mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 03:08:54 +01:00
audio: Bug fix: Write can block and needs to be called asynchronously (#373)
This commit is contained in:
parent
ba86d37ce8
commit
1fe2d90d8a
@ -171,7 +171,9 @@ func (p *players) hasSource(src ReadSeekCloser) bool {
|
|||||||
// In this case, audio goes on even when the game stops e.g. by diactivating the screen.
|
// In this case, audio goes on even when the game stops e.g. by diactivating the screen.
|
||||||
type Context struct {
|
type Context struct {
|
||||||
players *players
|
players *players
|
||||||
driver *oto.Player
|
playerWriteCh chan []uint8
|
||||||
|
playerErrCh chan error
|
||||||
|
playerCloseCh chan struct{}
|
||||||
sampleRate int
|
sampleRate int
|
||||||
frames int64
|
frames int64
|
||||||
writtenBytes int64
|
writtenBytes int64
|
||||||
@ -214,40 +216,62 @@ func NewContext(sampleRate int) (*Context, error) {
|
|||||||
//
|
//
|
||||||
// Update returns error when IO error occurs in the underlying IO object.
|
// Update returns error when IO error occurs in the underlying IO object.
|
||||||
func (c *Context) Update() error {
|
func (c *Context) Update() error {
|
||||||
// Initialize c.driver lazily to enable calling NewContext in an 'init' function.
|
// Initialize oto.Player lazily to enable calling NewContext in an 'init' function.
|
||||||
// Accessing driver functions requires the environment to be already initialized,
|
// Accessing oto.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 c.driver == nil {
|
if c.playerWriteCh == nil {
|
||||||
|
init := make(chan error)
|
||||||
|
c.playerWriteCh = make(chan []uint8)
|
||||||
|
c.playerErrCh = make(chan error, 1)
|
||||||
|
c.playerCloseCh = make(chan struct{})
|
||||||
|
go func() {
|
||||||
// The buffer size is 1/15 sec.
|
// The buffer size is 1/15 sec.
|
||||||
// It looks like 1/20 sec is too short for Android.
|
// It looks like 1/20 sec is too short for Android.
|
||||||
s := c.sampleRate * channelNum * bytesPerSample / 15
|
s := c.sampleRate * channelNum * bytesPerSample / 15
|
||||||
p, err := oto.NewPlayer(c.sampleRate, channelNum, bytesPerSample, s)
|
p, err := oto.NewPlayer(c.sampleRate, channelNum, bytesPerSample, s)
|
||||||
c.driver = p
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
init <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer p.Close()
|
||||||
|
close(init)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case buf := <-c.playerWriteCh:
|
||||||
|
if _, err = p.Write(buf); err != nil {
|
||||||
|
c.playerErrCh <- err
|
||||||
|
}
|
||||||
|
case <-c.playerCloseCh:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err := <-init; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
select {
|
||||||
|
case err := <-c.playerErrCh:
|
||||||
|
close(c.playerCloseCh)
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
}
|
||||||
c.frames++
|
c.frames++
|
||||||
bytesPerFrame := c.sampleRate * bytesPerSample * channelNum / ebiten.FPS
|
bytesPerFrame := c.sampleRate * bytesPerSample * channelNum / ebiten.FPS
|
||||||
l := (c.frames * int64(bytesPerFrame)) - c.writtenBytes
|
l := (c.frames * int64(bytesPerFrame)) - c.writtenBytes
|
||||||
l &= mask
|
l &= mask
|
||||||
c.writtenBytes += l
|
c.writtenBytes += l
|
||||||
buf := make([]uint8, l)
|
buf := make([]uint8, l)
|
||||||
n, err := io.ReadFull(c.players, buf)
|
if _, err := io.ReadFull(c.players, buf); err != nil {
|
||||||
if err != nil {
|
close(c.playerCloseCh)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if n != len(buf) {
|
select {
|
||||||
return c.driver.Close()
|
case c.playerWriteCh <- buf:
|
||||||
}
|
// Writing can block. Don't wait for the result here.
|
||||||
_, err = c.driver.Write(buf)
|
default:
|
||||||
if err == io.EOF {
|
|
||||||
return c.driver.Close()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user