fix: recover from go-mp3 seek panic on startup (#86)
The go-mp3 library (v0.3.4) has a bug where Seek panics with a slice bounds error for certain byte positions. This crashes the app when restoring a saved playback position on startup. Add bounds clamping and a recover wrapper around the seek call in seekLocked to convert the panic into a graceful error. When the seek fails, playback starts from the beginning instead of crashing.
This commit is contained in:
@@ -100,6 +100,7 @@ var (
|
|||||||
errNoAudioFileLoaded = errors.New("no audio file loaded")
|
errNoAudioFileLoaded = errors.New("no audio file loaded")
|
||||||
errNoStreamerToPlay = errors.New("no streamer to play")
|
errNoStreamerToPlay = errors.New("no streamer to play")
|
||||||
errNoAudioStream = errors.New("no audio stream to pause")
|
errNoAudioStream = errors.New("no audio stream to pause")
|
||||||
|
errSeekPanicked = errors.New("seek panicked (go-mp3 bug)")
|
||||||
)
|
)
|
||||||
|
|
||||||
var speakerSampleRate = beep.SampleRate(44100)
|
var speakerSampleRate = beep.SampleRate(44100)
|
||||||
@@ -766,6 +767,19 @@ func (p *Player) seekLocked(targetSeconds int) error {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Clamp the seek position to valid bounds. The underlying
|
||||||
|
// go-mp3 library (v0.3.4) has a bug where seeking to
|
||||||
|
// positions near the end of certain files causes a slice
|
||||||
|
// bounds panic. Clamping reduces the likelihood of hitting
|
||||||
|
// this, and the recover below catches it if it still occurs.
|
||||||
|
if maxPos := p.seeker.Len() - 1; samples > maxPos {
|
||||||
|
samples = maxPos
|
||||||
|
}
|
||||||
|
|
||||||
|
if samples < 0 {
|
||||||
|
samples = 0
|
||||||
|
}
|
||||||
|
|
||||||
p.logger.Debug(
|
p.logger.Debug(
|
||||||
"attempting to seek",
|
"attempting to seek",
|
||||||
"target-seconds", targetSeconds,
|
"target-seconds", targetSeconds,
|
||||||
@@ -773,9 +787,30 @@ func (p *Player) seekLocked(targetSeconds int) error {
|
|||||||
"samples", samples,
|
"samples", samples,
|
||||||
)
|
)
|
||||||
|
|
||||||
if seekErr := p.seeker.Seek(samples); seekErr != nil {
|
// Wrap the seek in a recover to catch panics from the
|
||||||
|
// go-mp3 library's buggy Seek implementation. See:
|
||||||
|
// github.com/hajimehoshi/go-mp3@v0.3.4/decode.go:111
|
||||||
|
seekErr := func() (err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = fmt.Errorf("%w: %v", errSeekPanicked, r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return p.seeker.Seek(samples)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if seekErr != nil {
|
||||||
speaker.Unlock()
|
speaker.Unlock()
|
||||||
|
|
||||||
|
p.logger.Warn(
|
||||||
|
"Seek failed, playback will start from "+
|
||||||
|
"the beginning",
|
||||||
|
"target-seconds", targetSeconds,
|
||||||
|
"samples", samples,
|
||||||
|
"err", seekErr,
|
||||||
|
)
|
||||||
|
|
||||||
return fmt.Errorf("failed to seek: %w", seekErr)
|
return fmt.Errorf("failed to seek: %w", seekErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user