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
322 lines
6.8 KiB
Go
322 lines
6.8 KiB
Go
package test
|
|
|
|
import (
|
|
"image"
|
|
"image/draw"
|
|
"sync"
|
|
|
|
"fyne.io/fyne/v2"
|
|
"fyne.io/fyne/v2/driver/desktop"
|
|
"fyne.io/fyne/v2/internal"
|
|
intapp "fyne.io/fyne/v2/internal/app"
|
|
"fyne.io/fyne/v2/internal/cache"
|
|
"fyne.io/fyne/v2/internal/scale"
|
|
"fyne.io/fyne/v2/theme"
|
|
)
|
|
|
|
var dummyCanvas WindowlessCanvas
|
|
|
|
// WindowlessCanvas provides functionality for a canvas to operate without a window
|
|
type WindowlessCanvas interface {
|
|
fyne.Canvas
|
|
|
|
Padded() bool
|
|
Resize(fyne.Size)
|
|
SetPadded(bool)
|
|
SetScale(float32)
|
|
}
|
|
|
|
type canvas struct {
|
|
size fyne.Size
|
|
resized bool
|
|
scale float32
|
|
|
|
content fyne.CanvasObject
|
|
overlays internal.OverlayStack
|
|
focusMgr *intapp.FocusManager
|
|
hovered desktop.Hoverable
|
|
padded bool
|
|
transparent bool
|
|
|
|
onTypedRune func(rune)
|
|
onTypedKey func(*fyne.KeyEvent)
|
|
|
|
fyne.ShortcutHandler
|
|
painter SoftwarePainter
|
|
propertyLock sync.RWMutex
|
|
}
|
|
|
|
// Canvas returns a reusable in-memory canvas used for testing
|
|
func Canvas() fyne.Canvas {
|
|
if dummyCanvas == nil {
|
|
dummyCanvas = NewCanvas()
|
|
}
|
|
|
|
return dummyCanvas
|
|
}
|
|
|
|
// NewCanvas returns a single use in-memory canvas used for testing.
|
|
// This canvas has no painter so calls to Capture() will return a blank image.
|
|
func NewCanvas() WindowlessCanvas {
|
|
c := &canvas{
|
|
focusMgr: intapp.NewFocusManager(nil),
|
|
padded: true,
|
|
scale: 1.0,
|
|
size: fyne.NewSize(100, 100),
|
|
}
|
|
c.overlays.Canvas = c
|
|
return c
|
|
}
|
|
|
|
// NewCanvasWithPainter allows creation of an in-memory canvas with a specific painter.
|
|
// The painter will be used to render in the Capture() call.
|
|
func NewCanvasWithPainter(painter SoftwarePainter) WindowlessCanvas {
|
|
c := NewCanvas().(*canvas)
|
|
c.painter = painter
|
|
|
|
return c
|
|
}
|
|
|
|
// NewTransparentCanvasWithPainter allows creation of an in-memory canvas with a specific painter without a background color.
|
|
// The painter will be used to render in the Capture() call.
|
|
//
|
|
// Since: 2.2
|
|
func NewTransparentCanvasWithPainter(painter SoftwarePainter) WindowlessCanvas {
|
|
c := NewCanvasWithPainter(painter).(*canvas)
|
|
c.transparent = true
|
|
|
|
return c
|
|
}
|
|
|
|
func (c *canvas) Capture() image.Image {
|
|
cache.Clean(true)
|
|
size := c.Size()
|
|
bounds := image.Rect(0, 0, scale.ToScreenCoordinate(c, size.Width), scale.ToScreenCoordinate(c, size.Height))
|
|
img := image.NewNRGBA(bounds)
|
|
if !c.transparent {
|
|
draw.Draw(img, bounds, image.NewUniform(theme.Color(theme.ColorNameBackground)), image.Point{}, draw.Src)
|
|
}
|
|
|
|
if c.painter != nil {
|
|
draw.Draw(img, bounds, c.painter.Paint(c), image.Point{}, draw.Over)
|
|
}
|
|
|
|
return img
|
|
}
|
|
|
|
func (c *canvas) Content() fyne.CanvasObject {
|
|
c.propertyLock.RLock()
|
|
defer c.propertyLock.RUnlock()
|
|
|
|
return c.content
|
|
}
|
|
|
|
func (c *canvas) Focus(obj fyne.Focusable) {
|
|
c.focusManager().Focus(obj)
|
|
}
|
|
|
|
func (c *canvas) FocusNext() {
|
|
c.focusManager().FocusNext()
|
|
}
|
|
|
|
func (c *canvas) FocusPrevious() {
|
|
c.focusManager().FocusPrevious()
|
|
}
|
|
|
|
func (c *canvas) Focused() fyne.Focusable {
|
|
return c.focusManager().Focused()
|
|
}
|
|
|
|
func (c *canvas) InteractiveArea() (fyne.Position, fyne.Size) {
|
|
return fyne.NewPos(2, 3), c.Size().SubtractWidthHeight(4, 5)
|
|
}
|
|
|
|
func (c *canvas) OnTypedKey() func(*fyne.KeyEvent) {
|
|
c.propertyLock.RLock()
|
|
defer c.propertyLock.RUnlock()
|
|
|
|
return c.onTypedKey
|
|
}
|
|
|
|
func (c *canvas) OnTypedRune() func(rune) {
|
|
c.propertyLock.RLock()
|
|
defer c.propertyLock.RUnlock()
|
|
|
|
return c.onTypedRune
|
|
}
|
|
|
|
func (c *canvas) Overlays() fyne.OverlayStack {
|
|
c.propertyLock.Lock()
|
|
defer c.propertyLock.Unlock()
|
|
|
|
return &c.overlays
|
|
}
|
|
|
|
func (c *canvas) Padded() bool {
|
|
c.propertyLock.RLock()
|
|
defer c.propertyLock.RUnlock()
|
|
|
|
return c.padded
|
|
}
|
|
|
|
func (c *canvas) PixelCoordinateForPosition(pos fyne.Position) (int, int) {
|
|
return int(pos.X * c.scale), int(pos.Y * c.scale)
|
|
}
|
|
|
|
func (c *canvas) Refresh(fyne.CanvasObject) {
|
|
}
|
|
|
|
func (c *canvas) Resize(size fyne.Size) {
|
|
c.propertyLock.Lock()
|
|
c.resized = true
|
|
c.propertyLock.Unlock()
|
|
|
|
c.doResize(size)
|
|
}
|
|
|
|
func (c *canvas) doResize(size fyne.Size) {
|
|
c.propertyLock.Lock()
|
|
content := c.content
|
|
overlays := c.overlays
|
|
padded := c.padded
|
|
c.size = size
|
|
c.propertyLock.Unlock()
|
|
|
|
if content == nil {
|
|
return
|
|
}
|
|
|
|
// Ensure testcanvas mimics real canvas.Resize behavior
|
|
for _, overlay := range overlays.List() {
|
|
type popupWidget interface {
|
|
fyne.CanvasObject
|
|
ShowAtPosition(fyne.Position)
|
|
}
|
|
if p, ok := overlay.(popupWidget); ok {
|
|
// TODO: remove this when #707 is being addressed.
|
|
// “Notifies” the PopUp of the canvas size change.
|
|
p.Refresh()
|
|
} else {
|
|
overlay.Resize(size)
|
|
}
|
|
}
|
|
|
|
if padded {
|
|
padding := theme.Padding()
|
|
content.Resize(size.Subtract(fyne.NewSquareSize(padding * 2)))
|
|
content.Move(fyne.NewSquareOffsetPos(padding))
|
|
} else {
|
|
content.Resize(size)
|
|
content.Move(fyne.NewPos(0, 0))
|
|
}
|
|
}
|
|
|
|
func (c *canvas) Scale() float32 {
|
|
c.propertyLock.RLock()
|
|
defer c.propertyLock.RUnlock()
|
|
|
|
return c.scale
|
|
}
|
|
|
|
func (c *canvas) SetContent(content fyne.CanvasObject) {
|
|
c.propertyLock.Lock()
|
|
c.content = content
|
|
c.focusMgr = intapp.NewFocusManager(c.content)
|
|
resized := c.resized
|
|
c.propertyLock.Unlock()
|
|
|
|
if content == nil {
|
|
return
|
|
}
|
|
|
|
minSize := content.MinSize()
|
|
if c.padded {
|
|
minSize = minSize.Add(fyne.NewSquareSize(theme.Padding() * 2))
|
|
}
|
|
|
|
if resized {
|
|
c.doResize(c.Size().Max(minSize))
|
|
} else {
|
|
c.doResize(minSize)
|
|
}
|
|
}
|
|
|
|
func (c *canvas) SetOnTypedKey(handler func(*fyne.KeyEvent)) {
|
|
c.propertyLock.Lock()
|
|
defer c.propertyLock.Unlock()
|
|
|
|
c.onTypedKey = handler
|
|
}
|
|
|
|
func (c *canvas) SetOnTypedRune(handler func(rune)) {
|
|
c.propertyLock.Lock()
|
|
defer c.propertyLock.Unlock()
|
|
|
|
c.onTypedRune = handler
|
|
}
|
|
|
|
func (c *canvas) SetPadded(padded bool) {
|
|
c.propertyLock.Lock()
|
|
c.padded = padded
|
|
c.propertyLock.Unlock()
|
|
|
|
c.doResize(c.Size())
|
|
}
|
|
|
|
func (c *canvas) SetScale(scale float32) {
|
|
c.propertyLock.Lock()
|
|
defer c.propertyLock.Unlock()
|
|
|
|
c.scale = scale
|
|
}
|
|
|
|
func (c *canvas) Size() fyne.Size {
|
|
c.propertyLock.RLock()
|
|
defer c.propertyLock.RUnlock()
|
|
|
|
return c.size
|
|
}
|
|
|
|
func (c *canvas) Unfocus() {
|
|
c.focusManager().Focus(nil)
|
|
}
|
|
|
|
func (c *canvas) focusManager() *intapp.FocusManager {
|
|
c.propertyLock.RLock()
|
|
defer c.propertyLock.RUnlock()
|
|
if focusMgr := c.overlays.TopFocusManager(); focusMgr != nil {
|
|
return focusMgr
|
|
}
|
|
return c.focusMgr
|
|
}
|
|
|
|
func (c *canvas) objectTrees() []fyne.CanvasObject {
|
|
overlays := c.Overlays().List()
|
|
trees := make([]fyne.CanvasObject, 0, len(overlays)+1)
|
|
if c.content != nil {
|
|
trees = append(trees, c.content)
|
|
}
|
|
trees = append(trees, overlays...)
|
|
return trees
|
|
}
|
|
|
|
func layoutAndCollect(objects []fyne.CanvasObject, o fyne.CanvasObject, size fyne.Size) []fyne.CanvasObject {
|
|
objects = append(objects, o)
|
|
switch c := o.(type) {
|
|
case fyne.Widget:
|
|
r := c.CreateRenderer()
|
|
r.Layout(size)
|
|
for _, child := range r.Objects() {
|
|
objects = layoutAndCollect(objects, child, child.Size())
|
|
}
|
|
case *fyne.Container:
|
|
if c.Layout != nil {
|
|
c.Layout.Layout(c.Objects, size)
|
|
}
|
|
for _, child := range c.Objects {
|
|
objects = layoutAndCollect(objects, child, child.Size())
|
|
}
|
|
}
|
|
return objects
|
|
}
|