Major improvements to UnifiedPlayer: 1. GetFrameImage() now works when paused for responsive UI updates 2. Play() method properly starts FFmpeg process 3. Frame display loop runs continuously for smooth video display 4. Disabled audio temporarily to fix video playback fundamentals 5. Simplified FFmpeg command to focus on video stream only Player now: - Generates video frames correctly - Shows video when paused - Has responsive progress tracking - Starts playback properly Next steps: Re-enable audio playback once video is stable
175 lines
4.3 KiB
Go
175 lines
4.3 KiB
Go
package animation
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
"fyne.io/fyne/v2"
|
|
)
|
|
|
|
// Runner is the main driver for animations package
|
|
type Runner struct {
|
|
// animationMutex synchronizes access to `animations` and `pendingAnimations`
|
|
// between the runner goroutine and calls to Start and Stop
|
|
animationMutex sync.RWMutex
|
|
|
|
// animations is the list of animations that are being ticked in the current frame
|
|
animations []*anim
|
|
|
|
// pendingAnimations is animations that have been started but not yet picked up
|
|
// by the runner goroutine to be ticked each frame
|
|
pendingAnimations []*anim
|
|
|
|
// nextFrameAnimations is the list of animations that will be ticked in the next frame.
|
|
// It is accessed only by the runner goroutine and accumulates the continuing animations
|
|
// during a tick that are not completed, plus the pendingAnimations picked up at the end of the frame.
|
|
// At the end of a full frame of animations, the nextFrameAnimations slice is swapped with
|
|
// the current `animations` slice which is then cleared out, while holding the mutex.
|
|
nextFrameAnimations []*anim
|
|
|
|
runnerStarted bool
|
|
}
|
|
|
|
// Start will register the passed application and initiate its ticking.
|
|
func (r *Runner) Start(a *fyne.Animation) {
|
|
r.animationMutex.Lock()
|
|
defer r.animationMutex.Unlock()
|
|
|
|
if !r.runnerStarted {
|
|
r.runnerStarted = true
|
|
if r.animations == nil {
|
|
// initialize with excess capacity to avoid re-allocations
|
|
// on subsequent Starts
|
|
r.animations = make([]*anim, 0, 16)
|
|
}
|
|
r.animations = append(r.animations, newAnim(a))
|
|
} else {
|
|
if r.pendingAnimations == nil {
|
|
// initialize with excess capacity to avoid re-allocations
|
|
// on subsequent Starts
|
|
r.pendingAnimations = make([]*anim, 0, 16)
|
|
}
|
|
r.pendingAnimations = append(r.pendingAnimations, newAnim(a))
|
|
}
|
|
}
|
|
|
|
// Stop causes an animation to stop ticking (if it was still running) and removes it from the runner.
|
|
func (r *Runner) Stop(a *fyne.Animation) {
|
|
r.animationMutex.Lock()
|
|
defer r.animationMutex.Unlock()
|
|
|
|
newList := make([]*anim, 0, len(r.animations))
|
|
stopped := false
|
|
for _, item := range r.animations {
|
|
if item.a != a {
|
|
newList = append(newList, item)
|
|
} else {
|
|
item.setStopped()
|
|
stopped = true
|
|
}
|
|
}
|
|
r.animations = newList
|
|
if stopped {
|
|
return
|
|
}
|
|
|
|
newList = make([]*anim, 0, len(r.pendingAnimations))
|
|
for _, item := range r.pendingAnimations {
|
|
if item.a != a {
|
|
newList = append(newList, item)
|
|
} else {
|
|
item.setStopped()
|
|
}
|
|
}
|
|
r.pendingAnimations = newList
|
|
}
|
|
|
|
// TickAnimations progresses all running animations by one tick.
|
|
// This will be called from the driver to update objects immediately before next paint.
|
|
func (r *Runner) TickAnimations() {
|
|
if !r.runnerStarted {
|
|
return
|
|
}
|
|
|
|
done := r.runOneFrame()
|
|
|
|
if done {
|
|
r.animationMutex.Lock()
|
|
r.runnerStarted = false
|
|
r.animationMutex.Unlock()
|
|
}
|
|
}
|
|
|
|
func (r *Runner) runOneFrame() (done bool) {
|
|
r.animationMutex.Lock()
|
|
oldList := r.animations
|
|
r.animationMutex.Unlock()
|
|
for _, a := range oldList {
|
|
if !a.isStopped() && r.tickAnimation(a) {
|
|
r.nextFrameAnimations = append(r.nextFrameAnimations, a)
|
|
}
|
|
}
|
|
|
|
r.animationMutex.Lock()
|
|
// nil out old r.animations for re-use as next r.nextFrameAnimations
|
|
tmp := r.animations
|
|
for i := range tmp {
|
|
tmp[i] = nil
|
|
}
|
|
r.animations = append(r.nextFrameAnimations, r.pendingAnimations...)
|
|
r.nextFrameAnimations = tmp[:0]
|
|
// nil out r.pendingAnimations
|
|
for i := range r.pendingAnimations {
|
|
r.pendingAnimations[i] = nil
|
|
}
|
|
r.pendingAnimations = r.pendingAnimations[:0]
|
|
done = len(r.animations) == 0
|
|
r.animationMutex.Unlock()
|
|
return done
|
|
}
|
|
|
|
// tickAnimation will process a frame of animation and return true if this should continue animating
|
|
func (r *Runner) tickAnimation(a *anim) bool {
|
|
if time.Now().After(a.end) {
|
|
if a.reverse {
|
|
a.a.Tick(0.0)
|
|
if a.repeatsLeft == 0 {
|
|
return false
|
|
}
|
|
a.reverse = false
|
|
} else {
|
|
a.a.Tick(1.0)
|
|
if a.a.AutoReverse {
|
|
a.reverse = true
|
|
}
|
|
}
|
|
if !a.reverse {
|
|
if a.repeatsLeft == 0 {
|
|
return false
|
|
}
|
|
if a.repeatsLeft > 0 {
|
|
a.repeatsLeft--
|
|
}
|
|
}
|
|
|
|
a.start = time.Now()
|
|
a.end = a.start.Add(a.a.Duration)
|
|
return true
|
|
}
|
|
|
|
delta := time.Since(a.start).Milliseconds()
|
|
|
|
val := float32(delta) / float32(a.total)
|
|
curve := a.a.Curve
|
|
if curve == nil {
|
|
curve = fyne.AnimationEaseInOut
|
|
}
|
|
if a.reverse {
|
|
a.a.Tick(curve(1 - val))
|
|
} else {
|
|
a.a.Tick(curve(val))
|
|
}
|
|
|
|
return true
|
|
}
|