mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2025-01-12 20:18:59 +01:00
parent
788529ff76
commit
6ced6987cd
@ -136,7 +136,7 @@ func NewContext(sampleRate int) *Context {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.gcPlayers(); err != nil {
|
||||
if err := c.updatePlayers(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@ -197,7 +197,7 @@ func (c *Context) removePlayingPlayer(p *playerImpl) {
|
||||
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).
|
||||
// Copy the playerImpls and iterate them without a lock.
|
||||
var players []*playerImpl
|
||||
@ -218,6 +218,7 @@ func (c *Context) gcPlayers() error {
|
||||
if err := p.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
p.updatePosition()
|
||||
if !p.IsPlaying() {
|
||||
playersToRemove = append(playersToRemove, p)
|
||||
}
|
||||
|
@ -69,7 +69,20 @@ type playerImpl struct {
|
||||
stream *timeStream
|
||||
factory *playerFactory
|
||||
initBufferSize int
|
||||
m sync.Mutex
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (f *playerFactory) newPlayer(context *Context, src io.Reader) (*playerImpl, error) {
|
||||
@ -77,9 +90,10 @@ func (f *playerFactory) newPlayer(context *Context, src io.Reader) (*playerImpl,
|
||||
defer f.m.Unlock()
|
||||
|
||||
p := &playerImpl{
|
||||
src: src,
|
||||
context: context,
|
||||
factory: f,
|
||||
src: src,
|
||||
context: context,
|
||||
factory: f,
|
||||
lastSamples: -1,
|
||||
}
|
||||
runtime.SetFinalizer(p, (*playerImpl).Close)
|
||||
return p, nil
|
||||
@ -178,6 +192,7 @@ func (p *playerImpl) Play() {
|
||||
}
|
||||
p.player.Play()
|
||||
p.context.addPlayingPlayer(p)
|
||||
p.stopwatch.start()
|
||||
}
|
||||
|
||||
func (p *playerImpl) Pause() {
|
||||
@ -193,12 +208,16 @@ func (p *playerImpl) Pause() {
|
||||
|
||||
p.player.Pause()
|
||||
p.context.removePlayingPlayer(p)
|
||||
p.stopwatch.stop()
|
||||
}
|
||||
|
||||
func (p *playerImpl) IsPlaying() bool {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
return p.isPlaying()
|
||||
}
|
||||
|
||||
func (p *playerImpl) isPlaying() bool {
|
||||
if p.player == nil {
|
||||
return false
|
||||
}
|
||||
@ -237,6 +256,7 @@ func (p *playerImpl) Close() error {
|
||||
p.player = nil
|
||||
}()
|
||||
p.player.Pause()
|
||||
p.stopwatch.stop()
|
||||
return p.player.Close()
|
||||
}
|
||||
return nil
|
||||
@ -245,13 +265,7 @@ func (p *playerImpl) Close() error {
|
||||
func (p *playerImpl) Position() time.Duration {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
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)
|
||||
return p.adjustedPosition
|
||||
}
|
||||
|
||||
func (p *playerImpl) Rewind() error {
|
||||
@ -263,6 +277,7 @@ func (p *playerImpl) SetPosition(offset time.Duration) error {
|
||||
defer p.m.Unlock()
|
||||
|
||||
if offset == 0 && p.player == nil {
|
||||
p.adjustedPosition = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -274,6 +289,13 @@ func (p *playerImpl) SetPosition(offset time.Duration) error {
|
||||
if _, err := p.player.Seek(pos, io.SeekStart); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
@ -304,6 +326,34 @@ func (p *playerImpl) source() io.Reader {
|
||||
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 {
|
||||
r io.Reader
|
||||
sampleRate int
|
||||
@ -376,3 +426,10 @@ func (s *timeStream) position() int64 {
|
||||
|
||||
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