audio/internal/go2cpp: Replace isWritable with unplayedBufferSize instead

This is the more accurate way not to overflow the underlying buffer.
This commit is contained in:
Hajime Hoshi 2021-03-23 23:34:04 +09:00
parent 6f185063d6
commit 1083233d5f
2 changed files with 46 additions and 13 deletions

View File

@ -21,6 +21,10 @@ import (
"syscall/js" "syscall/js"
) )
const (
readChunkSize = 4096
)
type Context struct { type Context struct {
v js.Value v js.Value
sampleRate int sampleRate int
@ -96,6 +100,16 @@ func (p *Player) Pause() {
p.cond.Signal() p.cond.Signal()
} }
func (p *Player) oneBufferSize() int {
// TODO: This must be audio.oneBufferSize(p.context.sampleRate). Avoid the duplication.
return p.context.sampleRate * p.context.channelNum * p.context.bitDepthInBytes / 4
}
func (p *Player) maxBufferSize() int {
// TODO: This must be audio.maxBufferSize(p.context.sampleRate). Avoid the duplication.
return p.oneBufferSize() * 2
}
func (p *Player) Play() { func (p *Player) Play() {
p.cond.L.Lock() p.cond.L.Lock()
defer p.cond.L.Unlock() defer p.cond.L.Unlock()
@ -116,7 +130,11 @@ func (p *Player) Play() {
// Prepare the first data as soon as possible, or the audio can get stuck. // Prepare the first data as soon as possible, or the audio can get stuck.
// TODO: Get the appropriate buffer size from the C++ side. // TODO: Get the appropriate buffer size from the C++ side.
if p.buf == nil { if p.buf == nil {
p.buf = make([]byte, p.context.sampleRate*p.context.channelNum*p.context.bitDepthInBytes/4) n := p.oneBufferSize()
if max := p.maxBufferSize() - int(p.UnplayedBufferSize()); n > max {
n = max
}
p.buf = make([]byte, n)
} }
n, err := p.src.Read(p.buf) n, err := p.src.Read(p.buf)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
@ -176,6 +194,9 @@ func (p *Player) SetVolume(volume float64) {
} }
func (p *Player) UnplayedBufferSize() int64 { func (p *Player) UnplayedBufferSize() int64 {
if !p.v.Truthy() {
return 0
}
return int64(p.v.Get("unplayedBufferSize").Int()) return int64(p.v.Get("unplayedBufferSize").Int())
} }
@ -219,11 +240,24 @@ func (p *Player) setError(err error) {
p.cond.Signal() p.cond.Signal()
} }
func (p *Player) shouldWait() bool {
if !p.v.Truthy() {
return false
}
switch p.state {
case playerStatePaused:
return true
case playerStatePlaying:
return p.v.Get("unplayedBufferSize").Int() >= p.maxBufferSize()
}
return false
}
func (p *Player) waitUntilUnpaused() bool { func (p *Player) waitUntilUnpaused() bool {
p.cond.L.Lock() p.cond.L.Lock()
defer p.cond.L.Unlock() defer p.cond.L.Unlock()
for p.v.Truthy() && (p.state == playerStatePaused || (p.state == playerStatePlaying && !p.v.Call("isWritable").Bool())) { for p.shouldWait() {
p.cond.Wait() p.cond.Wait()
} }
return p.v.Truthy() && p.state == playerStatePlaying return p.v.Truthy() && p.state == playerStatePlaying
@ -248,23 +282,25 @@ func (p *Player) writeImpl(dst js.Value, src []byte) {
} }
func (p *Player) loop() { func (p *Player) loop() {
const size = 4096 buf := make([]byte, readChunkSize)
dst := js.Global().Get("Uint8Array").New(readChunkSize)
buf := make([]byte, size)
dst := js.Global().Get("Uint8Array").New(size)
for { for {
if !p.waitUntilUnpaused() { if !p.waitUntilUnpaused() {
return return
} }
n, err := p.src.Read(buf) n := readChunkSize
if max := p.maxBufferSize() - int(p.UnplayedBufferSize()); n > max {
n = max
}
n2, err := p.src.Read(buf[:n])
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
p.setError(err) p.setError(err)
return return
} }
if n > 0 { if n > 0 {
p.write(dst, buf[:n]) p.write(dst, buf[:n2])
} }
if err == io.EOF { if err == io.EOF {

View File

@ -32,11 +32,8 @@ func oneBufferSize(sampleRate int) int {
// maxBufferSize returns the maximum size of the buffer for the audio source. // maxBufferSize returns the maximum size of the buffer for the audio source.
// This buffer is used when unreading on pausing the player. // This buffer is used when unreading on pausing the player.
func maxBufferSize(sampleRate int) int { func maxBufferSize(sampleRate int) int {
// Actually *2 should be enough in most cases, // The number of underlying buffers should be 2.
// but in some implementation (e.g, go2cpp), a player might have more UnplayedBufferSize values. return oneBufferSize(sampleRate) * 2
// As a safe margin, use *4 value.
// TODO: Ensure the maximum value of UnplayedBufferSize on all the platforms.
return oneBufferSize(sampleRate) * 4
} }
// readerDriver represents a driver using io.ReadClosers. // readerDriver represents a driver using io.ReadClosers.