mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-26 11:48:55 +01:00
audio: Rename playerImpl -> writerContextPlayerImpl
This commit is contained in:
parent
c42a40a541
commit
7cf32efcd5
226
audio/audio.go
226
audio/audio.go
@ -66,7 +66,7 @@ type Context struct {
|
|||||||
err error
|
err error
|
||||||
ready bool
|
ready bool
|
||||||
|
|
||||||
players map[*playerImpl]struct{}
|
players map[*writerContextPlayerImpl]struct{}
|
||||||
|
|
||||||
m sync.Mutex
|
m sync.Mutex
|
||||||
semaphore chan struct{}
|
semaphore chan struct{}
|
||||||
@ -97,7 +97,7 @@ func NewContext(sampleRate int) *Context {
|
|||||||
c := &Context{
|
c := &Context{
|
||||||
sampleRate: sampleRate,
|
sampleRate: sampleRate,
|
||||||
c: newWriterContext(sampleRate),
|
c: newWriterContext(sampleRate),
|
||||||
players: map[*playerImpl]struct{}{},
|
players: map[*writerContextPlayerImpl]struct{}{},
|
||||||
inited: make(chan struct{}),
|
inited: make(chan struct{}),
|
||||||
semaphore: make(chan struct{}, 1),
|
semaphore: make(chan struct{}, 1),
|
||||||
}
|
}
|
||||||
@ -158,7 +158,7 @@ func (c *Context) setReady() {
|
|||||||
c.m.Unlock()
|
c.m.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) addPlayer(p *playerImpl) {
|
func (c *Context) addPlayer(p *writerContextPlayerImpl) {
|
||||||
c.m.Lock()
|
c.m.Lock()
|
||||||
defer c.m.Unlock()
|
defer c.m.Unlock()
|
||||||
c.players[p] = struct{}{}
|
c.players[p] = struct{}{}
|
||||||
@ -166,15 +166,15 @@ func (c *Context) addPlayer(p *playerImpl) {
|
|||||||
// Check the source duplication
|
// Check the source duplication
|
||||||
srcs := map[io.Reader]struct{}{}
|
srcs := map[io.Reader]struct{}{}
|
||||||
for p := range c.players {
|
for p := range c.players {
|
||||||
if _, ok := srcs[p.src]; ok {
|
if _, ok := srcs[p.source()]; ok {
|
||||||
c.err = errors.New("audio: a same source is used by multiple Player")
|
c.err = errors.New("audio: a same source is used by multiple Player")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
srcs[p.src] = struct{}{}
|
srcs[p.source()] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) removePlayer(p *playerImpl) {
|
func (c *Context) removePlayer(p *writerContextPlayerImpl) {
|
||||||
c.m.Lock()
|
c.m.Lock()
|
||||||
delete(c.players, p)
|
delete(c.players, p)
|
||||||
c.m.Unlock()
|
c.m.Unlock()
|
||||||
@ -220,23 +220,7 @@ func (c *Context) SampleRate() int {
|
|||||||
// This means that if a Player plays an infinite stream,
|
// This means that if a Player plays an infinite stream,
|
||||||
// the object is never GCed unless Close is called.
|
// the object is never GCed unless Close is called.
|
||||||
type Player struct {
|
type Player struct {
|
||||||
p *playerImpl
|
p *writerContextPlayerImpl
|
||||||
}
|
|
||||||
|
|
||||||
type playerImpl struct {
|
|
||||||
context *Context
|
|
||||||
src io.Reader
|
|
||||||
sampleRate int
|
|
||||||
playing bool
|
|
||||||
closedExplicitly bool
|
|
||||||
isLoopActive bool
|
|
||||||
|
|
||||||
buf []byte
|
|
||||||
readbuf []byte
|
|
||||||
pos int64
|
|
||||||
volume float64
|
|
||||||
|
|
||||||
m sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPlayer creates a new player with the given stream.
|
// NewPlayer creates a new player with the given stream.
|
||||||
@ -257,7 +241,7 @@ type playerImpl struct {
|
|||||||
// Closing the source is src owner's responsibility.
|
// Closing the source is src owner's responsibility.
|
||||||
func NewPlayer(context *Context, src io.Reader) (*Player, error) {
|
func NewPlayer(context *Context, src io.Reader) (*Player, error) {
|
||||||
p := &Player{
|
p := &Player{
|
||||||
&playerImpl{
|
&writerContextPlayerImpl{
|
||||||
context: context,
|
context: context,
|
||||||
src: src,
|
src: src,
|
||||||
sampleRate: context.sampleRate,
|
sampleRate: context.sampleRate,
|
||||||
@ -311,151 +295,16 @@ func (p *Player) Close() error {
|
|||||||
return p.p.Close()
|
return p.p.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *playerImpl) Close() error {
|
|
||||||
p.m.Lock()
|
|
||||||
defer p.m.Unlock()
|
|
||||||
|
|
||||||
p.playing = false
|
|
||||||
if p.closedExplicitly {
|
|
||||||
return fmt.Errorf("audio: the player is already closed")
|
|
||||||
}
|
|
||||||
p.closedExplicitly = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Play plays the stream.
|
// Play plays the stream.
|
||||||
func (p *Player) Play() {
|
func (p *Player) Play() {
|
||||||
p.p.Play()
|
p.p.Play()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *playerImpl) Play() {
|
|
||||||
p.m.Lock()
|
|
||||||
defer p.m.Unlock()
|
|
||||||
|
|
||||||
if p.closedExplicitly {
|
|
||||||
p.context.setError(fmt.Errorf("audio: the player is already closed"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
p.playing = true
|
|
||||||
if p.isLoopActive {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set p.isLoopActive to true here, not in the loop. This prevents duplicated active loops.
|
|
||||||
p.isLoopActive = true
|
|
||||||
p.context.addPlayer(p)
|
|
||||||
|
|
||||||
go p.loop()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *playerImpl) loop() {
|
|
||||||
<-p.context.inited
|
|
||||||
|
|
||||||
w := p.context.c.NewPlayer()
|
|
||||||
wclosed := make(chan struct{})
|
|
||||||
defer func() {
|
|
||||||
<-wclosed
|
|
||||||
w.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
p.m.Lock()
|
|
||||||
p.playing = false
|
|
||||||
p.context.removePlayer(p)
|
|
||||||
p.isLoopActive = false
|
|
||||||
p.m.Unlock()
|
|
||||||
}()
|
|
||||||
|
|
||||||
ch := make(chan []byte)
|
|
||||||
defer close(ch)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for buf := range ch {
|
|
||||||
if _, err := w.Write(buf); err != nil {
|
|
||||||
p.context.setError(err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
p.context.setReady()
|
|
||||||
}
|
|
||||||
close(wclosed)
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
buf, ok := p.read()
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ch <- buf
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *playerImpl) read() ([]byte, bool) {
|
|
||||||
p.m.Lock()
|
|
||||||
defer p.m.Unlock()
|
|
||||||
|
|
||||||
if p.context.hasError() {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.closedExplicitly {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// playing can be false when pausing.
|
|
||||||
if !p.playing {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
const bufSize = 2048
|
|
||||||
|
|
||||||
p.context.semaphore <- struct{}{}
|
|
||||||
defer func() {
|
|
||||||
<-p.context.semaphore
|
|
||||||
}()
|
|
||||||
|
|
||||||
if p.readbuf == nil {
|
|
||||||
p.readbuf = make([]byte, bufSize)
|
|
||||||
}
|
|
||||||
n, err := p.src.Read(p.readbuf[:bufSize-len(p.buf)])
|
|
||||||
if err != nil {
|
|
||||||
if err != io.EOF {
|
|
||||||
p.context.setError(err)
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
if n == 0 {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf := append(p.buf, p.readbuf[:n]...)
|
|
||||||
|
|
||||||
n2 := len(buf) - len(buf)%bytesPerSample
|
|
||||||
buf, p.buf = buf[:n2], buf[n2:]
|
|
||||||
|
|
||||||
for i := 0; i < len(buf)/2; i++ {
|
|
||||||
v16 := int16(buf[2*i]) | (int16(buf[2*i+1]) << 8)
|
|
||||||
v16 = int16(float64(v16) * p.volume)
|
|
||||||
buf[2*i] = byte(v16)
|
|
||||||
buf[2*i+1] = byte(v16 >> 8)
|
|
||||||
}
|
|
||||||
p.pos += int64(len(buf))
|
|
||||||
|
|
||||||
return buf, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsPlaying returns boolean indicating whether the player is playing.
|
// IsPlaying returns boolean indicating whether the player is playing.
|
||||||
func (p *Player) IsPlaying() bool {
|
func (p *Player) IsPlaying() bool {
|
||||||
return p.p.IsPlaying()
|
return p.p.IsPlaying()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *playerImpl) IsPlaying() bool {
|
|
||||||
p.m.Lock()
|
|
||||||
r := p.playing
|
|
||||||
p.m.Unlock()
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rewind rewinds the current position to the start.
|
// Rewind rewinds the current position to the start.
|
||||||
//
|
//
|
||||||
// The passed source to NewPlayer must be io.Seeker, or Rewind panics.
|
// The passed source to NewPlayer must be io.Seeker, or Rewind panics.
|
||||||
@ -465,13 +314,6 @@ func (p *Player) Rewind() error {
|
|||||||
return p.p.Rewind()
|
return p.p.Rewind()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *playerImpl) Rewind() error {
|
|
||||||
if _, ok := p.src.(io.Seeker); !ok {
|
|
||||||
panic("audio: player to be rewound must be io.Seeker")
|
|
||||||
}
|
|
||||||
return p.Seek(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seek seeks the position with the given offset.
|
// Seek seeks the position with the given offset.
|
||||||
//
|
//
|
||||||
// The passed source to NewPlayer must be io.Seeker, or Seek panics.
|
// The passed source to NewPlayer must be io.Seeker, or Seek panics.
|
||||||
@ -481,79 +323,27 @@ func (p *Player) Seek(offset time.Duration) error {
|
|||||||
return p.p.Seek(offset)
|
return p.p.Seek(offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *playerImpl) Seek(offset time.Duration) error {
|
|
||||||
p.m.Lock()
|
|
||||||
defer p.m.Unlock()
|
|
||||||
|
|
||||||
o := int64(offset) * bytesPerSample * int64(p.sampleRate) / int64(time.Second)
|
|
||||||
o = o - (o % bytesPerSample)
|
|
||||||
|
|
||||||
seeker, ok := p.src.(io.Seeker)
|
|
||||||
if !ok {
|
|
||||||
panic("audio: the source must be io.Seeker when seeking")
|
|
||||||
}
|
|
||||||
pos, err := seeker.Seek(o, io.SeekStart)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
p.buf = nil
|
|
||||||
p.pos = pos
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pause pauses the playing.
|
// Pause pauses the playing.
|
||||||
func (p *Player) Pause() {
|
func (p *Player) Pause() {
|
||||||
p.p.Pause()
|
p.p.Pause()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *playerImpl) Pause() {
|
|
||||||
p.m.Lock()
|
|
||||||
p.playing = false
|
|
||||||
p.m.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Current returns the current position in time.
|
// Current returns the current position in time.
|
||||||
func (p *Player) Current() time.Duration {
|
func (p *Player) Current() time.Duration {
|
||||||
return p.p.Current()
|
return p.p.Current()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *playerImpl) Current() time.Duration {
|
|
||||||
p.m.Lock()
|
|
||||||
sample := p.pos / bytesPerSample
|
|
||||||
p.m.Unlock()
|
|
||||||
return time.Duration(sample) * time.Second / time.Duration(p.sampleRate)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Volume returns the current volume of this player [0-1].
|
// Volume returns the current volume of this player [0-1].
|
||||||
func (p *Player) Volume() float64 {
|
func (p *Player) Volume() float64 {
|
||||||
return p.p.Volume()
|
return p.p.Volume()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *playerImpl) Volume() float64 {
|
|
||||||
p.m.Lock()
|
|
||||||
v := p.volume
|
|
||||||
p.m.Unlock()
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetVolume sets the volume of this player.
|
// SetVolume sets the volume of this player.
|
||||||
// volume must be in between 0 and 1. SetVolume panics otherwise.
|
// volume must be in between 0 and 1. SetVolume panics otherwise.
|
||||||
func (p *Player) SetVolume(volume float64) {
|
func (p *Player) SetVolume(volume float64) {
|
||||||
p.p.SetVolume(volume)
|
p.p.SetVolume(volume)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *playerImpl) SetVolume(volume float64) {
|
|
||||||
// The condition must be true when volume is NaN.
|
|
||||||
if !(0 <= volume && volume <= 1) {
|
|
||||||
panic("audio: volume must be in between 0 and 1")
|
|
||||||
}
|
|
||||||
|
|
||||||
p.m.Lock()
|
|
||||||
p.volume = volume
|
|
||||||
p.m.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
type hook interface {
|
type hook interface {
|
||||||
OnSuspendAudio(f func())
|
OnSuspendAudio(f func())
|
||||||
OnResumeAudio(f func())
|
OnResumeAudio(f func())
|
||||||
|
@ -15,8 +15,10 @@
|
|||||||
package audio
|
package audio
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/hooks"
|
"github.com/hajimehoshi/ebiten/v2/internal/hooks"
|
||||||
)
|
)
|
||||||
@ -45,3 +47,217 @@ func newWriterContext(sampleRate int) writerContext {
|
|||||||
})
|
})
|
||||||
return newOtoContext(sampleRate, ch)
|
return newOtoContext(sampleRate, ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type writerContextPlayerImpl struct {
|
||||||
|
context *Context
|
||||||
|
src io.Reader
|
||||||
|
sampleRate int
|
||||||
|
playing bool
|
||||||
|
closedExplicitly bool
|
||||||
|
isLoopActive bool
|
||||||
|
|
||||||
|
buf []byte
|
||||||
|
readbuf []byte
|
||||||
|
pos int64
|
||||||
|
volume float64
|
||||||
|
|
||||||
|
m sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *writerContextPlayerImpl) Close() error {
|
||||||
|
p.m.Lock()
|
||||||
|
defer p.m.Unlock()
|
||||||
|
|
||||||
|
p.playing = false
|
||||||
|
if p.closedExplicitly {
|
||||||
|
return fmt.Errorf("audio: the player is already closed")
|
||||||
|
}
|
||||||
|
p.closedExplicitly = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *writerContextPlayerImpl) Play() {
|
||||||
|
p.m.Lock()
|
||||||
|
defer p.m.Unlock()
|
||||||
|
|
||||||
|
if p.closedExplicitly {
|
||||||
|
p.context.setError(fmt.Errorf("audio: the player is already closed"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p.playing = true
|
||||||
|
if p.isLoopActive {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set p.isLoopActive to true here, not in the loop. This prevents duplicated active loops.
|
||||||
|
p.isLoopActive = true
|
||||||
|
p.context.addPlayer(p)
|
||||||
|
|
||||||
|
go p.loop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *writerContextPlayerImpl) loop() {
|
||||||
|
<-p.context.inited
|
||||||
|
|
||||||
|
w := p.context.c.NewPlayer()
|
||||||
|
wclosed := make(chan struct{})
|
||||||
|
defer func() {
|
||||||
|
<-wclosed
|
||||||
|
w.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
p.m.Lock()
|
||||||
|
p.playing = false
|
||||||
|
p.context.removePlayer(p)
|
||||||
|
p.isLoopActive = false
|
||||||
|
p.m.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
ch := make(chan []byte)
|
||||||
|
defer close(ch)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for buf := range ch {
|
||||||
|
if _, err := w.Write(buf); err != nil {
|
||||||
|
p.context.setError(err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
p.context.setReady()
|
||||||
|
}
|
||||||
|
close(wclosed)
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
buf, ok := p.read()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ch <- buf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *writerContextPlayerImpl) read() ([]byte, bool) {
|
||||||
|
p.m.Lock()
|
||||||
|
defer p.m.Unlock()
|
||||||
|
|
||||||
|
if p.context.hasError() {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.closedExplicitly {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// playing can be false when pausing.
|
||||||
|
if !p.playing {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
const bufSize = 2048
|
||||||
|
|
||||||
|
p.context.semaphore <- struct{}{}
|
||||||
|
defer func() {
|
||||||
|
<-p.context.semaphore
|
||||||
|
}()
|
||||||
|
|
||||||
|
if p.readbuf == nil {
|
||||||
|
p.readbuf = make([]byte, bufSize)
|
||||||
|
}
|
||||||
|
n, err := p.src.Read(p.readbuf[:bufSize-len(p.buf)])
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
p.context.setError(err)
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf := append(p.buf, p.readbuf[:n]...)
|
||||||
|
|
||||||
|
n2 := len(buf) - len(buf)%bytesPerSample
|
||||||
|
buf, p.buf = buf[:n2], buf[n2:]
|
||||||
|
|
||||||
|
for i := 0; i < len(buf)/2; i++ {
|
||||||
|
v16 := int16(buf[2*i]) | (int16(buf[2*i+1]) << 8)
|
||||||
|
v16 = int16(float64(v16) * p.volume)
|
||||||
|
buf[2*i] = byte(v16)
|
||||||
|
buf[2*i+1] = byte(v16 >> 8)
|
||||||
|
}
|
||||||
|
p.pos += int64(len(buf))
|
||||||
|
|
||||||
|
return buf, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *writerContextPlayerImpl) IsPlaying() bool {
|
||||||
|
p.m.Lock()
|
||||||
|
r := p.playing
|
||||||
|
p.m.Unlock()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *writerContextPlayerImpl) Rewind() error {
|
||||||
|
if _, ok := p.src.(io.Seeker); !ok {
|
||||||
|
panic("audio: player to be rewound must be io.Seeker")
|
||||||
|
}
|
||||||
|
return p.Seek(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *writerContextPlayerImpl) Seek(offset time.Duration) error {
|
||||||
|
p.m.Lock()
|
||||||
|
defer p.m.Unlock()
|
||||||
|
|
||||||
|
o := int64(offset) * bytesPerSample * int64(p.sampleRate) / int64(time.Second)
|
||||||
|
o = o - (o % bytesPerSample)
|
||||||
|
|
||||||
|
seeker, ok := p.src.(io.Seeker)
|
||||||
|
if !ok {
|
||||||
|
panic("audio: the source must be io.Seeker when seeking")
|
||||||
|
}
|
||||||
|
pos, err := seeker.Seek(o, io.SeekStart)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.buf = nil
|
||||||
|
p.pos = pos
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *writerContextPlayerImpl) Pause() {
|
||||||
|
p.m.Lock()
|
||||||
|
p.playing = false
|
||||||
|
p.m.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *writerContextPlayerImpl) Current() time.Duration {
|
||||||
|
p.m.Lock()
|
||||||
|
sample := p.pos / bytesPerSample
|
||||||
|
p.m.Unlock()
|
||||||
|
return time.Duration(sample) * time.Second / time.Duration(p.sampleRate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *writerContextPlayerImpl) Volume() float64 {
|
||||||
|
p.m.Lock()
|
||||||
|
v := p.volume
|
||||||
|
p.m.Unlock()
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *writerContextPlayerImpl) SetVolume(volume float64) {
|
||||||
|
// The condition must be true when volume is NaN.
|
||||||
|
if !(0 <= volume && volume <= 1) {
|
||||||
|
panic("audio: volume must be in between 0 and 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.m.Lock()
|
||||||
|
p.volume = volume
|
||||||
|
p.m.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *writerContextPlayerImpl) source() io.Reader {
|
||||||
|
return p.src
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user