audio: NewPlayer now accepts io.ReadCloser

Fixes #414
This commit is contained in:
Hajime Hoshi 2018-03-22 00:33:17 +09:00
parent 9009b293e5
commit 18113f259a
2 changed files with 28 additions and 27 deletions

View File

@ -132,7 +132,7 @@ func (p *players) hasPlayer(player *Player) bool {
return ok
}
func (p *players) hasSource(src ReadSeekCloser) bool {
func (p *players) hasSource(src io.ReadCloser) bool {
p.RLock()
defer p.RUnlock()
for player := range p.players {
@ -328,7 +328,7 @@ func BytesReadSeekCloser(b []byte) ReadSeekCloser {
// Player is an audio player which has one stream.
type Player struct {
players *players
src ReadSeekCloser
src io.ReadCloser
srcEOF bool
sampleRate int
@ -362,11 +362,14 @@ type proceededValues struct {
// without a header (e.g. RIFF header).
// The sample rate must be same as that of the audio context.
//
// The player is seekable when src is io.Seeker.
// Attempt to seek the player that is not io.Seeker causes panic.
//
// Note that the given src can't be shared with other Player objects.
//
// NewPlayer tries to call Seek of src to get the current position.
// NewPlayer returns error when the Seek returns error.
func NewPlayer(context *Context, src ReadSeekCloser) (*Player, error) {
func NewPlayer(context *Context, src io.ReadCloser) (*Player, error) {
if context.players.hasSource(src) {
return nil, errors.New("audio: src cannot be shared with another Player")
}
@ -385,12 +388,14 @@ func NewPlayer(context *Context, src ReadSeekCloser) (*Player, error) {
proceededCh: make(chan proceededValues),
syncCh: make(chan func()),
}
// Get the current position of the source.
pos, err := p.src.Seek(0, io.SeekCurrent)
if err != nil {
return nil, err
if seeker, ok := p.src.(io.Seeker); ok {
// Get the current position of the source.
pos, err := seeker.Seek(0, io.SeekCurrent)
if err != nil {
return nil, err
}
p.pos = pos
}
p.pos = pos
runtime.SetFinalizer(p, (*Player).Close)
go func() {
@ -473,7 +478,11 @@ func (p *Player) readLoop() {
return
case s := <-p.seekCh:
pos, err := p.src.Seek(s.offset, s.whence)
seeker, ok := p.src.(io.Seeker)
if !ok {
panic("not reached")
}
pos, err := seeker.Seek(s.offset, s.whence)
p.buf = nil
p.pos = pos
p.srcEOF = false
@ -600,15 +609,25 @@ func (p *Player) IsPlaying() bool {
// Rewind rewinds the current position to the start.
//
// The passed source to NewPlayer must be io.Seeker, or Rewind panics.
//
// Rewind returns error when seeking the source stream returns error.
func (p *Player) Rewind() error {
if _, ok := p.src.(io.Seeker); !ok {
panic("audio: player to be rewinded must be io.Seeker")
}
return p.Seek(0)
}
// Seek seeks the position with the given offset.
//
// The passed source to NewPlayer must be io.Seeker, or Seek panics.
//
// Seek returns error when seeking the source stream returns error.
func (p *Player) Seek(offset time.Duration) error {
if _, ok := p.src.(io.Seeker); !ok {
panic("audio: player to be sought must be io.Seeker")
}
o := int64(offset) * bytesPerSample * channelNum * int64(p.sampleRate) / int64(time.Second)
o &= mask
select {

View File

@ -19,7 +19,6 @@ package main
import (
"errors"
"fmt"
"io"
"log"
"math"
@ -73,23 +72,6 @@ func (s *stream) Read(data []byte) (int, error) {
return len(data), nil
}
// Seek is io.Seeker's Seek.
//
// whence must be io.SeekStart or io.SeekCurrent.
func (s *stream) Seek(offset int64, whence int) (int64, error) {
const length = sampleRate / frequency
switch whence {
case io.SeekStart:
s.position = offset
case io.SeekCurrent:
s.position += offset
default:
return 0, errors.New("whence must be io.SeekStart or io.SeekCurrent")
}
s.position %= length * 4
return s.position, nil
}
// Close is io.Closer's Close.
func (s *stream) Close() error {
return nil