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"
)
const (
readChunkSize = 4096
)
type Context struct {
v js.Value
sampleRate int
@ -96,6 +100,16 @@ func (p *Player) Pause() {
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() {
p.cond.L.Lock()
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.
// TODO: Get the appropriate buffer size from the C++ side.
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)
if err != nil && err != io.EOF {
@ -176,6 +194,9 @@ func (p *Player) SetVolume(volume float64) {
}
func (p *Player) UnplayedBufferSize() int64 {
if !p.v.Truthy() {
return 0
}
return int64(p.v.Get("unplayedBufferSize").Int())
}
@ -219,11 +240,24 @@ func (p *Player) setError(err error) {
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 {
p.cond.L.Lock()
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()
}
return p.v.Truthy() && p.state == playerStatePlaying
@ -248,23 +282,25 @@ func (p *Player) writeImpl(dst js.Value, src []byte) {
}
func (p *Player) loop() {
const size = 4096
buf := make([]byte, size)
dst := js.Global().Get("Uint8Array").New(size)
buf := make([]byte, readChunkSize)
dst := js.Global().Get("Uint8Array").New(readChunkSize)
for {
if !p.waitUntilUnpaused() {
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 {
p.setError(err)
return
}
if n > 0 {
p.write(dst, buf[:n])
p.write(dst, buf[:n2])
}
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.
// This buffer is used when unreading on pausing the player.
func maxBufferSize(sampleRate int) int {
// Actually *2 should be enough in most cases,
// but in some implementation (e.g, go2cpp), a player might have more UnplayedBufferSize values.
// As a safe margin, use *4 value.
// TODO: Ensure the maximum value of UnplayedBufferSize on all the platforms.
return oneBufferSize(sampleRate) * 4
// The number of underlying buffers should be 2.
return oneBufferSize(sampleRate) * 2
}
// readerDriver represents a driver using io.ReadClosers.