oto: Player reads infinitely even after being paused/reset
Hello (again 😉)
I found some weird behavior with the player (intentional?) that can cause issues and wanted to discuss them here (tested on Windows 11).
Note that the following behaviors happen when the player is paused, but do not happen if the player has never played. They will start to happen even if the player was started then immediately paused, or if the sound played completely.
- Issuing a
player.Reset()always causes oneRead()call Resetfollowed by a seek (or vice versa) toio.SeekStart(or similar) will cause the player to callRead()infinitely! Once the infinite reading starts even seeking toio.SeekEnddoesn’t stop it. The only thing that seems to stop that is to callPlay()and let the sound finish.- While point 2 is active (Read is being called by Oto), a user calling seek can cause the
ReadandSeekfunctions to be called concurrently which is many times not safe. - Infinite calling of
Read()will start with reset+seek, or by playing and pausing before a sound finishes playing.
I had issues with sounds not re-playing properly because of this, although a mutex in read/seek seems to help in some cases.
Here is simple code to reproduce:
//This is a io.ReadSeeker wrapper that will log when Read/Seek is called, and will panic on concurrent use
type ReadSeekerFileWrapper struct {
F *os.File
M sync.Mutex
}
//If mutex is already locked this will panic
func (fw *ReadSeekerFileWrapper) Check(name string) {
locked := fw.M.TryLock()
if locked {
fw.M.Unlock()
} else {
panic("Concurrent use by: " + name)
}
}
var shouldPrint = false
func (fw *ReadSeekerFileWrapper) Read(outBuf []byte) (bytesRead int, err error) {
//mp3 decoding calls read, so we don't want to log that
if shouldPrint {
println("Read called")
}
fw.Check("Read")
fw.M.Lock()
defer fw.M.Unlock()
n, err := fw.F.Read(outBuf)
if shouldPrint {
fmt.Printf("Read %d bytes\n", n)
}
return n, err
}
func (fw *ReadSeekerFileWrapper) Seek(offset int64, whence int) (int64, error) {
if shouldPrint {
fmt.Printf("Seek called: offset=%d, whence=%d\n", offset, whence)
}
fw.Check("Seek")
fw.M.Lock()
defer fw.M.Unlock()
return fw.F.Seek(offset, whence)
}
func main() {
//Load some mp3
file, _ := os.Open("./test_audio_files/camera.mp3")
fw := &ReadSeekerFileWrapper{F: file, M: sync.Mutex{}}
decodedMp3, _ := mp3.NewDecoder(fw)
shouldPrint = true
//Init Oto
otoCtx, readyChan, _ := oto.NewContext(44100, 2, 2)
<-readyChan
player := otoCtx.NewPlayer(decodedMp3)
//Play once
player.Play()
for player.IsPlaying() {
time.Sleep(time.Millisecond)
}
//This will cause `Read` to be called infinitely (you will see many logs)
//Doing reset first then seek does the same thing
fw.Seek(0, io.SeekStart)
player.Reset()
//Instead of playing fully then reset+seek we could have done:
//player.Play(); player.Pause()
//Simulate a user thinking Oto isn't doing anything and wanting to seek.
//At some point this will panic because the wrapper detects concurrent use (seek/read called at the same time)
time.Sleep(100 * time.Millisecond)
for {
//Start/End both will panic
// fw.Seek(0, io.SeekStart)
fw.Seek(0, io.SeekEnd)
}
}
I don’t think this is intended behavior. Not only can this cause concurrent access to resources which might not be safe, but will cause very high CPU usage.
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Comments: 19 (7 by maintainers)
Commits related to this issue
- audio: bug fix: potential busy reading after the source reaches EOF Closes #2167 Updates hajimehoshi/oto#171 — committed to hajimehoshi/ebiten by hajimehoshi 2 years ago
I think I have fixed this. Thank you for reporting this!
Whether this is reproducible with any audio or not matters. I’ll try later. Thanks