From 1083233d5f156da155853fb2de2c38be83742e96 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Tue, 23 Mar 2021 23:34:04 +0900 Subject: [PATCH] audio/internal/go2cpp: Replace isWritable with unplayedBufferSize instead This is the more accurate way not to overflow the underlying buffer. --- audio/internal/go2cpp/player_js.go | 52 +++++++++++++++++++++++++----- audio/readerplayer.go | 7 ++-- 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/audio/internal/go2cpp/player_js.go b/audio/internal/go2cpp/player_js.go index 04f9c415a..6f38c0abf 100644 --- a/audio/internal/go2cpp/player_js.go +++ b/audio/internal/go2cpp/player_js.go @@ -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 { diff --git a/audio/readerplayer.go b/audio/readerplayer.go index e1e4c67b8..fa6b4f369 100644 --- a/audio/readerplayer.go +++ b/audio/readerplayer.go @@ -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.