audio: Close audio after its playing finishes

Fixes #746

This is a temporal fix and we will need further re-designing of
audio package.
This commit is contained in:
Hajime Hoshi 2018-12-17 03:37:00 +09:00
parent 732b036343
commit b0cb216f5f
2 changed files with 58 additions and 1 deletions

View File

@ -124,6 +124,9 @@ func (p *players) Read(b []byte) (int, error) {
} }
} }
for _, player := range closed { for _, player := range closed {
if player.isFinalized() {
player.closeImpl()
}
delete(p.players, player) delete(p.players, player)
} }
@ -353,6 +356,8 @@ type playerImpl struct {
proceedCh chan []int16 proceedCh chan []int16
proceededCh chan proceededValues proceededCh chan proceededValues
syncCh chan func() syncCh chan func()
finalized bool
} }
type seekArgs struct { type seekArgs struct {
@ -407,7 +412,7 @@ func NewPlayer(context *Context, src io.ReadCloser) (*Player, error) {
} }
p.p.pos = pos p.p.pos = pos
} }
runtime.SetFinalizer(p, (*Player).Close) runtime.SetFinalizer(p, (*Player).finalize)
go func() { go func() {
p.p.readLoop() p.p.readLoop()
@ -433,6 +438,30 @@ func NewPlayerFromBytes(context *Context, src []byte) (*Player, error) {
return p, nil return p, nil
} }
func (p *Player) finalize() {
runtime.SetFinalizer(p, nil)
p.p.setFinalized(true)
// TODO: It is really hard to say concurrent safety.
// Refactor this package to reduce goroutines.
if !p.IsPlaying() {
p.Close()
}
}
func (p *playerImpl) setFinalized(finalized bool) {
p.sync(func() {
p.finalized = finalized
})
}
func (p *playerImpl) isFinalized() bool {
b := false
p.sync(func() {
b = p.finalized
})
return b
}
// Close closes the stream. // Close closes the stream.
// //
// When closing, the stream owned by the player will also be closed by calling its Close. // When closing, the stream owned by the player will also be closed by calling its Close.
@ -446,7 +475,10 @@ func (p *Player) Close() error {
func (p *playerImpl) Close() error { func (p *playerImpl) Close() error {
p.players.removePlayer(p) p.players.removePlayer(p)
return p.closeImpl()
}
func (p *playerImpl) closeImpl() error {
select { select {
case p.closeCh <- struct{}{}: case p.closeCh <- struct{}{}:
<-p.closedCh <-p.closedCh

View File

@ -15,12 +15,34 @@
package audio_test package audio_test
import ( import (
"errors"
"os"
"runtime" "runtime"
"testing" "testing"
"time"
"github.com/hajimehoshi/ebiten"
. "github.com/hajimehoshi/ebiten/audio" . "github.com/hajimehoshi/ebiten/audio"
"github.com/hajimehoshi/ebiten/internal/testflock"
) )
func TestMain(m *testing.M) {
testflock.Lock()
defer testflock.Unlock()
code := 0
// Run an Ebiten process so that audio is available.
regularTermination := errors.New("regular termination")
f := func(screen *ebiten.Image) error {
code = m.Run()
return regularTermination
}
if err := ebiten.Run(f, 320, 240, 1, "Test"); err != nil && err != regularTermination {
panic(err)
}
os.Exit(code)
}
var context *Context var context *Context
func init() { func init() {
@ -48,6 +70,9 @@ func TestGC(t *testing.T) {
runtime.KeepAlive(p) runtime.KeepAlive(p)
p = nil p = nil
runtime.GC() runtime.GC()
// 100[ms] should be enough all the bytes are consumed.
// TODO: This is a darty hack. Would it be possible to use virtual time?
time.Sleep(100 * time.Millisecond)
got = PlayersNumForTesting() got = PlayersNumForTesting()
if want := 0; got != want { if want := 0; got != want {
t.Errorf("PlayersNum() after GC: got: %d, want: %d", got, want) t.Errorf("PlayersNum() after GC: got: %d, want: %d", got, want)