mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
parent
788529ff76
commit
6ced6987cd
@ -136,7 +136,7 @@ func NewContext(sampleRate int) *Context {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.gcPlayers(); err != nil {
|
if err := c.updatePlayers(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -197,7 +197,7 @@ func (c *Context) removePlayingPlayer(p *playerImpl) {
|
|||||||
c.m.Unlock()
|
c.m.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) gcPlayers() error {
|
func (c *Context) updatePlayers() error {
|
||||||
// A Context must not call playerImpl's functions with a lock, or this causes a deadlock (#2737).
|
// A Context must not call playerImpl's functions with a lock, or this causes a deadlock (#2737).
|
||||||
// Copy the playerImpls and iterate them without a lock.
|
// Copy the playerImpls and iterate them without a lock.
|
||||||
var players []*playerImpl
|
var players []*playerImpl
|
||||||
@ -218,6 +218,7 @@ func (c *Context) gcPlayers() error {
|
|||||||
if err := p.Err(); err != nil {
|
if err := p.Err(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
p.updatePosition()
|
||||||
if !p.IsPlaying() {
|
if !p.IsPlaying() {
|
||||||
playersToRemove = append(playersToRemove, p)
|
playersToRemove = append(playersToRemove, p)
|
||||||
}
|
}
|
||||||
|
@ -69,6 +69,19 @@ type playerImpl struct {
|
|||||||
stream *timeStream
|
stream *timeStream
|
||||||
factory *playerFactory
|
factory *playerFactory
|
||||||
initBufferSize int
|
initBufferSize int
|
||||||
|
|
||||||
|
// adjustedPosition is the player's more accurate position.
|
||||||
|
// The underlying buffer might not be changed even if the player is playing.
|
||||||
|
// adjustedPosition is adjusted by the time duration during the player position doesn't change while its playing.
|
||||||
|
adjustedPosition time.Duration
|
||||||
|
|
||||||
|
// lastSamples is the last value of the number of samples.
|
||||||
|
// When lastSamples is a negative number, this value is not initialized yet.
|
||||||
|
lastSamples int64
|
||||||
|
|
||||||
|
// stopwatch is a stopwatch to measure the time duration during the player position doesn't change while its playing.
|
||||||
|
stopwatch stopwatch
|
||||||
|
|
||||||
m sync.Mutex
|
m sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,6 +93,7 @@ func (f *playerFactory) newPlayer(context *Context, src io.Reader) (*playerImpl,
|
|||||||
src: src,
|
src: src,
|
||||||
context: context,
|
context: context,
|
||||||
factory: f,
|
factory: f,
|
||||||
|
lastSamples: -1,
|
||||||
}
|
}
|
||||||
runtime.SetFinalizer(p, (*playerImpl).Close)
|
runtime.SetFinalizer(p, (*playerImpl).Close)
|
||||||
return p, nil
|
return p, nil
|
||||||
@ -178,6 +192,7 @@ func (p *playerImpl) Play() {
|
|||||||
}
|
}
|
||||||
p.player.Play()
|
p.player.Play()
|
||||||
p.context.addPlayingPlayer(p)
|
p.context.addPlayingPlayer(p)
|
||||||
|
p.stopwatch.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *playerImpl) Pause() {
|
func (p *playerImpl) Pause() {
|
||||||
@ -193,12 +208,16 @@ func (p *playerImpl) Pause() {
|
|||||||
|
|
||||||
p.player.Pause()
|
p.player.Pause()
|
||||||
p.context.removePlayingPlayer(p)
|
p.context.removePlayingPlayer(p)
|
||||||
|
p.stopwatch.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *playerImpl) IsPlaying() bool {
|
func (p *playerImpl) IsPlaying() bool {
|
||||||
p.m.Lock()
|
p.m.Lock()
|
||||||
defer p.m.Unlock()
|
defer p.m.Unlock()
|
||||||
|
return p.isPlaying()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *playerImpl) isPlaying() bool {
|
||||||
if p.player == nil {
|
if p.player == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -237,6 +256,7 @@ func (p *playerImpl) Close() error {
|
|||||||
p.player = nil
|
p.player = nil
|
||||||
}()
|
}()
|
||||||
p.player.Pause()
|
p.player.Pause()
|
||||||
|
p.stopwatch.stop()
|
||||||
return p.player.Close()
|
return p.player.Close()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -245,13 +265,7 @@ func (p *playerImpl) Close() error {
|
|||||||
func (p *playerImpl) Position() time.Duration {
|
func (p *playerImpl) Position() time.Duration {
|
||||||
p.m.Lock()
|
p.m.Lock()
|
||||||
defer p.m.Unlock()
|
defer p.m.Unlock()
|
||||||
|
return p.adjustedPosition
|
||||||
if p.player == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
samples := (p.stream.position() - int64(p.player.BufferedSize())) / bytesPerSampleInt16
|
|
||||||
return time.Duration(samples) * time.Second / time.Duration(p.factory.sampleRate)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *playerImpl) Rewind() error {
|
func (p *playerImpl) Rewind() error {
|
||||||
@ -263,6 +277,7 @@ func (p *playerImpl) SetPosition(offset time.Duration) error {
|
|||||||
defer p.m.Unlock()
|
defer p.m.Unlock()
|
||||||
|
|
||||||
if offset == 0 && p.player == nil {
|
if offset == 0 && p.player == nil {
|
||||||
|
p.adjustedPosition = 0
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,6 +289,13 @@ func (p *playerImpl) SetPosition(offset time.Duration) error {
|
|||||||
if _, err := p.player.Seek(pos, io.SeekStart); err != nil {
|
if _, err := p.player.Seek(pos, io.SeekStart); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
p.lastSamples = -1
|
||||||
|
// Just after setting a position, the buffer size should be 0 as no data is sent.
|
||||||
|
p.adjustedPosition = p.stream.positionInTimeDuration()
|
||||||
|
p.stopwatch.reset()
|
||||||
|
if p.isPlaying() {
|
||||||
|
p.stopwatch.start()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,6 +326,34 @@ func (p *playerImpl) source() io.Reader {
|
|||||||
return p.src
|
return p.src
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *playerImpl) updatePosition() {
|
||||||
|
p.m.Lock()
|
||||||
|
defer p.m.Unlock()
|
||||||
|
|
||||||
|
if p.player == nil {
|
||||||
|
p.adjustedPosition = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
samples := (p.stream.position() - int64(p.player.BufferedSize())) / bytesPerSampleInt16
|
||||||
|
|
||||||
|
var adjustingTime time.Duration
|
||||||
|
if p.lastSamples >= 0 && p.lastSamples == samples {
|
||||||
|
// If the number of samples is not changed from the last tick,
|
||||||
|
// the underlying buffer is not updated yet. Adjust the position by the time (#2901).
|
||||||
|
adjustingTime = p.stopwatch.current()
|
||||||
|
} else {
|
||||||
|
p.lastSamples = samples
|
||||||
|
p.stopwatch.reset()
|
||||||
|
if p.isPlaying() {
|
||||||
|
p.stopwatch.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the adjusted position every tick. This is necessary to keep the position accurate.
|
||||||
|
p.adjustedPosition = time.Duration(samples)*time.Second/time.Duration(p.factory.sampleRate) + adjustingTime
|
||||||
|
}
|
||||||
|
|
||||||
type timeStream struct {
|
type timeStream struct {
|
||||||
r io.Reader
|
r io.Reader
|
||||||
sampleRate int
|
sampleRate int
|
||||||
@ -376,3 +426,10 @@ func (s *timeStream) position() int64 {
|
|||||||
|
|
||||||
return s.pos
|
return s.pos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *timeStream) positionInTimeDuration() time.Duration {
|
||||||
|
s.m.Lock()
|
||||||
|
defer s.m.Unlock()
|
||||||
|
|
||||||
|
return time.Duration(s.pos) * time.Second / (time.Duration(s.sampleRate) * bytesPerSampleInt16)
|
||||||
|
}
|
||||||
|
70
audio/stopwatch.go
Normal file
70
audio/stopwatch.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// Copyright 2024 The Ebitengine Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package audio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stopwatch struct {
|
||||||
|
duration time.Duration
|
||||||
|
lastStarted time.Time
|
||||||
|
running bool
|
||||||
|
|
||||||
|
m sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stopwatch) start() {
|
||||||
|
s.m.Lock()
|
||||||
|
defer s.m.Unlock()
|
||||||
|
|
||||||
|
if s.running {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.lastStarted = time.Now()
|
||||||
|
s.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stopwatch) stop() {
|
||||||
|
s.m.Lock()
|
||||||
|
defer s.m.Unlock()
|
||||||
|
|
||||||
|
if !s.running {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.duration += time.Since(s.lastStarted)
|
||||||
|
s.running = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stopwatch) current() time.Duration {
|
||||||
|
s.m.Lock()
|
||||||
|
defer s.m.Unlock()
|
||||||
|
|
||||||
|
d := s.duration
|
||||||
|
if s.running {
|
||||||
|
d += time.Since(s.lastStarted)
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stopwatch) reset() {
|
||||||
|
s.m.Lock()
|
||||||
|
defer s.m.Unlock()
|
||||||
|
|
||||||
|
s.duration = 0
|
||||||
|
s.lastStarted = time.Time{}
|
||||||
|
s.running = false
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user