Add GStreamer player state machine
This commit is contained in:
parent
bc56642d3b
commit
14599f4df5
|
|
@ -84,6 +84,7 @@ var gstInitOnce sync.Once
|
||||||
|
|
||||||
type GStreamerPlayer struct {
|
type GStreamerPlayer struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
seekMu sync.Mutex
|
||||||
pipeline *C.GstElement
|
pipeline *C.GstElement
|
||||||
appsink *C.GstElement
|
appsink *C.GstElement
|
||||||
bus *C.GstBus
|
bus *C.GstBus
|
||||||
|
|
@ -101,6 +102,7 @@ type GStreamerPlayer struct {
|
||||||
eos bool
|
eos bool
|
||||||
state C.GstState
|
state C.GstState
|
||||||
duration time.Duration
|
duration time.Duration
|
||||||
|
mode PlayerState
|
||||||
}
|
}
|
||||||
|
|
||||||
type busEvent struct {
|
type busEvent struct {
|
||||||
|
|
@ -109,6 +111,20 @@ type busEvent struct {
|
||||||
State C.GstState
|
State C.GstState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PlayerState int
|
||||||
|
|
||||||
|
const (
|
||||||
|
StateIdle PlayerState = iota
|
||||||
|
StateLoading
|
||||||
|
StatePaused
|
||||||
|
StatePlaying
|
||||||
|
StateSeeking
|
||||||
|
StateStepping
|
||||||
|
StateStopped
|
||||||
|
StateError
|
||||||
|
StateEOS
|
||||||
|
)
|
||||||
|
|
||||||
func NewGStreamerPlayer(config Config) (*GStreamerPlayer, error) {
|
func NewGStreamerPlayer(config Config) (*GStreamerPlayer, error) {
|
||||||
var initErr error
|
var initErr error
|
||||||
gstInitOnce.Do(func() {
|
gstInitOnce.Do(func() {
|
||||||
|
|
@ -125,6 +141,7 @@ func NewGStreamerPlayer(config Config) (*GStreamerPlayer, error) {
|
||||||
paused: true,
|
paused: true,
|
||||||
volume: config.Volume,
|
volume: config.Volume,
|
||||||
preview: config.PreviewMode,
|
preview: config.PreviewMode,
|
||||||
|
mode: StateIdle,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -224,9 +241,11 @@ func (p *GStreamerPlayer) Load(path string, offset time.Duration) error {
|
||||||
p.paused = true
|
p.paused = true
|
||||||
p.eos = false
|
p.eos = false
|
||||||
p.lastErr = ""
|
p.lastErr = ""
|
||||||
|
p.mode = StateLoading
|
||||||
|
|
||||||
// Set to PAUSED to preroll (loads first frame)
|
// Set to PAUSED to preroll (loads first frame)
|
||||||
if C.gst_element_set_state(playbin, C.GST_STATE_PAUSED) == C.GST_STATE_CHANGE_FAILURE {
|
if C.gst_element_set_state(playbin, C.GST_STATE_PAUSED) == C.GST_STATE_CHANGE_FAILURE {
|
||||||
|
p.mode = StateError
|
||||||
p.closeLocked()
|
p.closeLocked()
|
||||||
return errors.New("gstreamer failed to enter paused state")
|
return errors.New("gstreamer failed to enter paused state")
|
||||||
}
|
}
|
||||||
|
|
@ -244,8 +263,10 @@ func (p *GStreamerPlayer) Load(path string, offset time.Duration) error {
|
||||||
p.closeLocked()
|
p.closeLocked()
|
||||||
if errMsg != nil {
|
if errMsg != nil {
|
||||||
defer C.vt_gst_free_error(errMsg)
|
defer C.vt_gst_free_error(errMsg)
|
||||||
|
p.mode = StateError
|
||||||
return errors.New(C.GoString(errMsg))
|
return errors.New(C.GoString(errMsg))
|
||||||
}
|
}
|
||||||
|
p.mode = StateError
|
||||||
return errors.New("gstreamer error while loading")
|
return errors.New("gstreamer error while loading")
|
||||||
}
|
}
|
||||||
C.gst_message_unref(msg)
|
C.gst_message_unref(msg)
|
||||||
|
|
@ -256,6 +277,7 @@ func (p *GStreamerPlayer) Load(path string, offset time.Duration) error {
|
||||||
_ = p.seekLocked(offset)
|
_ = p.seekLocked(offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.mode = StatePaused
|
||||||
p.startBusLoopLocked()
|
p.startBusLoopLocked()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -268,9 +290,11 @@ func (p *GStreamerPlayer) Play() error {
|
||||||
return errors.New("no pipeline loaded")
|
return errors.New("no pipeline loaded")
|
||||||
}
|
}
|
||||||
if C.gst_element_set_state(p.pipeline, C.GST_STATE_PLAYING) == C.GST_STATE_CHANGE_FAILURE {
|
if C.gst_element_set_state(p.pipeline, C.GST_STATE_PLAYING) == C.GST_STATE_CHANGE_FAILURE {
|
||||||
|
p.mode = StateError
|
||||||
return errors.New("gstreamer failed to enter playing state")
|
return errors.New("gstreamer failed to enter playing state")
|
||||||
}
|
}
|
||||||
p.paused = false
|
p.paused = false
|
||||||
|
p.mode = StatePlaying
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -282,16 +306,33 @@ func (p *GStreamerPlayer) Pause() error {
|
||||||
return errors.New("no pipeline loaded")
|
return errors.New("no pipeline loaded")
|
||||||
}
|
}
|
||||||
if C.gst_element_set_state(p.pipeline, C.GST_STATE_PAUSED) == C.GST_STATE_CHANGE_FAILURE {
|
if C.gst_element_set_state(p.pipeline, C.GST_STATE_PAUSED) == C.GST_STATE_CHANGE_FAILURE {
|
||||||
|
p.mode = StateError
|
||||||
return errors.New("gstreamer failed to enter paused state")
|
return errors.New("gstreamer failed to enter paused state")
|
||||||
}
|
}
|
||||||
p.paused = true
|
p.paused = true
|
||||||
|
p.mode = StatePaused
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *GStreamerPlayer) SeekToTime(offset time.Duration) error {
|
func (p *GStreamerPlayer) SeekToTime(offset time.Duration) error {
|
||||||
|
p.seekMu.Lock()
|
||||||
|
defer p.seekMu.Unlock()
|
||||||
|
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
defer p.mu.Unlock()
|
prevMode := p.mode
|
||||||
return p.seekLocked(offset)
|
p.mode = StateSeeking
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
err := p.seekLocked(offset)
|
||||||
|
|
||||||
|
p.mu.Lock()
|
||||||
|
if err != nil {
|
||||||
|
p.mode = StateError
|
||||||
|
} else {
|
||||||
|
p.mode = prevMode
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *GStreamerPlayer) seekLocked(offset time.Duration) error {
|
func (p *GStreamerPlayer) seekLocked(offset time.Duration) error {
|
||||||
|
|
@ -311,14 +352,30 @@ func (p *GStreamerPlayer) seekLockedWithFlags(offset time.Duration, flags C.GstS
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *GStreamerPlayer) SeekToFrame(frame int64) error {
|
func (p *GStreamerPlayer) SeekToFrame(frame int64) error {
|
||||||
|
p.seekMu.Lock()
|
||||||
|
defer p.seekMu.Unlock()
|
||||||
|
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
defer p.mu.Unlock()
|
|
||||||
if p.fps <= 0 {
|
if p.fps <= 0 {
|
||||||
|
p.mu.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
prevMode := p.mode
|
||||||
|
p.mode = StateStepping
|
||||||
seconds := float64(frame) / p.fps
|
seconds := float64(frame) / p.fps
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
flags := C.GstSeekFlags(C.GST_SEEK_FLAG_FLUSH | C.GST_SEEK_FLAG_ACCURATE)
|
flags := C.GstSeekFlags(C.GST_SEEK_FLAG_FLUSH | C.GST_SEEK_FLAG_ACCURATE)
|
||||||
return p.seekLockedWithFlags(time.Duration(seconds*float64(time.Second)), flags)
|
err := p.seekLockedWithFlags(time.Duration(seconds*float64(time.Second)), flags)
|
||||||
|
|
||||||
|
p.mu.Lock()
|
||||||
|
if err != nil {
|
||||||
|
p.mode = StateError
|
||||||
|
} else {
|
||||||
|
p.mode = prevMode
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *GStreamerPlayer) GetCurrentTime() time.Duration {
|
func (p *GStreamerPlayer) GetCurrentTime() time.Duration {
|
||||||
|
|
@ -472,12 +529,14 @@ func (p *GStreamerPlayer) Stop() error {
|
||||||
if p.pipeline != nil {
|
if p.pipeline != nil {
|
||||||
C.gst_element_set_state(p.pipeline, C.GST_STATE_NULL)
|
C.gst_element_set_state(p.pipeline, C.GST_STATE_NULL)
|
||||||
}
|
}
|
||||||
|
p.mode = StateStopped
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *GStreamerPlayer) Close() {
|
func (p *GStreamerPlayer) Close() {
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
defer p.mu.Unlock()
|
defer p.mu.Unlock()
|
||||||
|
p.mode = StateStopped
|
||||||
p.closeLocked()
|
p.closeLocked()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -485,6 +544,12 @@ func (p *GStreamerPlayer) Events() <-chan busEvent {
|
||||||
return p.events
|
return p.events
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *GStreamerPlayer) State() PlayerState {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
return p.mode
|
||||||
|
}
|
||||||
|
|
||||||
func (p *GStreamerPlayer) closeLocked() {
|
func (p *GStreamerPlayer) closeLocked() {
|
||||||
p.stopBusLoopLocked()
|
p.stopBusLoopLocked()
|
||||||
if p.pipeline != nil {
|
if p.pipeline != nil {
|
||||||
|
|
@ -568,12 +633,14 @@ func (p *GStreamerPlayer) busLoop() {
|
||||||
} else {
|
} else {
|
||||||
p.lastErr = "gstreamer error"
|
p.lastErr = "gstreamer error"
|
||||||
}
|
}
|
||||||
|
p.mode = StateError
|
||||||
evt := busEvent{Kind: "error", Info: p.lastErr}
|
evt := busEvent{Kind: "error", Info: p.lastErr}
|
||||||
p.mu.Unlock()
|
p.mu.Unlock()
|
||||||
p.pushEvent(evt)
|
p.pushEvent(evt)
|
||||||
case C.GST_MESSAGE_EOS:
|
case C.GST_MESSAGE_EOS:
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
p.eos = true
|
p.eos = true
|
||||||
|
p.mode = StateEOS
|
||||||
p.mu.Unlock()
|
p.mu.Unlock()
|
||||||
p.pushEvent(busEvent{Kind: "eos"})
|
p.pushEvent(busEvent{Kind: "eos"})
|
||||||
case C.GST_MESSAGE_STATE_CHANGED:
|
case C.GST_MESSAGE_STATE_CHANGED:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user