diff --git a/examples/plugins/async-identity/asyncidentity.go b/examples/plugins/async-identity/asyncidentity.go new file mode 100644 index 0000000..74f0ef5 --- /dev/null +++ b/examples/plugins/async-identity/asyncidentity.go @@ -0,0 +1,232 @@ +//lint:file-ignore U1000 Ignore all unused code, this is example code + +// +plugin:Name=async-identity +// +plugin:Description=A go-gst example plugin with async state changes +// +plugin:Version=v0.0.1 +// +plugin:License=gst.LicenseLGPL +// +plugin:Source=go-gst +// +plugin:Package=examples +// +plugin:Origin=https://github.com/go-gst/go-gst +// +plugin:ReleaseDate=2024-09-13 +// +// +element:Name=asyncidentity +// +element:Rank=gst.RankNone +// +element:Impl=asyncidentity +// +element:Subclass=gst.ExtendsElement +// +//go:generate gst-plugin-gen +package main + +import ( + "fmt" + "sync/atomic" + "time" + + "github.com/go-gst/go-glib/glib" + "github.com/go-gst/go-gst/gst" +) + +var ( + _cat = gst.NewDebugCategory( + "asyncidentity", + gst.DebugColorNone, + "asyncidentity element", + ) + + _srcPadTemplate = gst.NewPadTemplate("generic-src", gst.PadDirectionSource, + gst.PadPresenceAlways, gst.NewAnyCaps()) + _sinkPadTemplate = gst.NewPadTemplate("generic-sink", gst.PadDirectionSink, + gst.PadPresenceAlways, gst.NewAnyCaps()) + _properties = []*glib.ParamSpec{ + glib.NewUint64Param( + "delay", + "ns state change delay", + "Duration in nanoseconds to wait until a state changes", + _delayNsMin, _delayNsMax, _delayNsDefault, + glib.ParameterReadWrite, + ), + } +) + +const ( + _propDelayNs = 0 + + _delayNsMin = uint64(0) + _delayNsMax = uint64(time.Second) * 10 + _delayNsDefault = uint64(time.Second) +) + +func main() {} + +type asyncidentity struct { + // inner state + sinkpad *gst.Pad + srcpad *gst.Pad + + asyncPending atomic.Bool + + // property storage + delayNs atomic.Uint64 +} + +var _ glib.GoObjectSubclass = (*asyncidentity)(nil) + +func (g *asyncidentity) New() glib.GoObjectSubclass { return &asyncidentity{} } + +func (g *asyncidentity) ClassInit(klass *glib.ObjectClass) { + class := gst.ToElementClass(klass) + class.SetMetadata( + "Async Identity Example", + "General", + "An async state changing identity like element", + "Artem Martus ", + ) + + class.AddStaticPadTemplate(_srcPadTemplate) + class.AddStaticPadTemplate(_sinkPadTemplate) + + class.InstallProperties(_properties) +} + +var _ glib.GoObject = (*asyncidentity)(nil) + +func (g *asyncidentity) SetProperty(obj *glib.Object, id uint, value *glib.Value) { + self := gst.ToElement(obj) + + switch id { + case _propDelayNs: + newDelayErased, err := value.GoValue() + if err != nil { + self.Error("Failed unmarshalling the 'delay' property", err) + return + } + newDelay, ok := newDelayErased.(uint64) + if !ok { + self.Error("Failed Go-casting the 'delay' interface{} into uint64", + fmt.Errorf("interfaced value: %+v", newDelayErased)) + return + } + oldDelay := g.delayNs.Swap(newDelay) + + self.Log(_cat, gst.LevelInfo, + fmt.Sprintf("Changed delay property %s => %s", + time.Duration(oldDelay), + time.Duration(newDelay), + )) + default: + self.Error("Tried to set unknown property", + fmt.Errorf("prop id %d: %s", id, value.TypeName())) + } +} + +func (g *asyncidentity) GetProperty(obj *glib.Object, id uint) *glib.Value { + var ( + out *glib.Value + err error + ) + + switch id { + case _propDelayNs: + out, err = glib.GValue(g.delayNs.Load()) + default: + err = fmt.Errorf("unknown property id: %d", id) + } + + if err != nil { + self := gst.ToElement(obj) + self.Error("Get property error", err) + out = nil + } + + return out +} + +func (g *asyncidentity) Constructed(self *glib.Object) { + elem := gst.ToElement(self) + srcPad := gst.NewPadFromTemplate(_srcPadTemplate, "src") + sinkPad := gst.NewPadFromTemplate(_sinkPadTemplate, "sink") + + sinkPad.SetChainFunction(g.sink_chain_function) + + // Have to set proxy flags on a pads + proxyFlags := gst.PadFlagProxyAllocation | gst.PadFlagProxyCaps | gst.PadFlagProxyScheduling + sinkPad.SetFlags(proxyFlags) + srcPad.SetFlags(proxyFlags) + + // Or setup query & event functions like so + + // sinkPad.SetQueryFunction(func(self *gst.Pad, parent *gst.Object, query *gst.Query) bool { + // return srcPad.PeerQuery(query) + // }) + // sinkPad.SetEventFunction(func(self *gst.Pad, parent *gst.Object, event *gst.Event) bool { + // return srcPad.PushEvent(event) + // }) + + // srcPad.SetQueryFunction(func(self *gst.Pad, parent *gst.Object, query *gst.Query) bool { + // return sinkPad.PeerQuery(query) + // }) + // srcPad.SetEventFunction(func(self *gst.Pad, parent *gst.Object, event *gst.Event) bool { + // return sinkPad.PushEvent(event) + // }) + + elem.AddPad(srcPad) + elem.AddPad(sinkPad) + + g.srcpad = srcPad + g.sinkpad = sinkPad + + g.delayNs.Store(_delayNsDefault) +} + +func (g *asyncidentity) sink_chain_function( + _self *gst.Pad, + _parent *gst.Object, + buffer *gst.Buffer, +) gst.FlowReturn { + + return g.srcpad.Push(buffer) +} + +// var _ gst.ElementImpl = (*asyncidentity)(nil) + +func (g *asyncidentity) ChangeState(el *gst.Element, transition gst.StateChange) gst.StateChangeReturn { + if ret := el.ParentChangeState(transition); ret == gst.StateChangeFailure { + return ret + } + + switch transition { + case gst.StateChangeNullToReady: + // async will be ignored due to target state <= READY + case gst.StateChangeReadyToPaused: + // async will be ignored due to no_preroll + return gst.StateChangeNoPreroll + case gst.StateChangePausedToPlaying: + fallthrough + case gst.StateChangePlayingToPaused: + g.asyncStateChange(el) + return gst.StateChangeAsync + case gst.StateChangePausedToReady: + // async will be ignored due to target state <= READY + case gst.StateChangeReadyToNull: + } + + // check against forcing state change + if g.asyncPending.Load() { + return gst.StateChangeAsync + } + + return gst.StateChangeSuccess +} + +func (g *asyncidentity) asyncStateChange(el *gst.Element) { + msg := gst.NewAsyncStartMessage(el) + _ = el.PostMessage(msg) + go func(el *gst.Element) { + g.asyncPending.Store(true) + delay := time.Duration(g.delayNs.Load()) + <-time.After(delay) + msg := gst.NewAsyncDoneMessage(el, gst.ClockTimeNone) + _ = el.PostMessage(msg) + g.asyncPending.Store(false) + }(el) +}