Sort of working vidplayback
This commit is contained in:
parent
4181d984be
commit
95e7203450
|
@ -18,7 +18,7 @@ func main() {
|
||||||
w := a.NewWindow("Video")
|
w := a.NewWindow("Video")
|
||||||
|
|
||||||
videoWidget = vidplayback.NewVidPlayback()
|
videoWidget = vidplayback.NewVidPlayback()
|
||||||
videoWidget.VideoFilename = "network.mp4"
|
videoWidget.VideoFilename = "1 Minute Timer-CH50zuS8DD0.mp4"
|
||||||
layout := layouts.NewFloatingControlsLayout()
|
layout := layouts.NewFloatingControlsLayout()
|
||||||
layout.FloatingControlsLocation = layouts.FloatingControlsCenter
|
layout.FloatingControlsLocation = layouts.FloatingControlsCenter
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"image/draw"
|
|
||||||
"log"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
|
@ -17,7 +16,16 @@ import (
|
||||||
"github.com/zergon321/reisen"
|
"github.com/zergon321/reisen"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ fyne.WidgetRenderer = (*vidPlaybackRenderer)(nil)
|
func staticNoiseImage(w, h int) *image.RGBA {
|
||||||
|
i := image.NewRGBA(image.Rect(0, 0, w, h))
|
||||||
|
for x := 0; x < w; x++ {
|
||||||
|
for y := 0; y < h; y++ {
|
||||||
|
lum := uint8(rand.Float32() * 255)
|
||||||
|
i.Set(x, y, color.RGBA{lum, lum, lum, 255})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
width = 1280
|
width = 1280
|
||||||
|
@ -36,12 +44,16 @@ type player struct {
|
||||||
ticker <-chan time.Time
|
ticker <-chan time.Time
|
||||||
errs <-chan error
|
errs <-chan error
|
||||||
frameBuffer <-chan *image.RGBA
|
frameBuffer <-chan *image.RGBA
|
||||||
|
audioBuffer <-chan [2]float64
|
||||||
last time.Time
|
last time.Time
|
||||||
deltaTime float64
|
fps int
|
||||||
paused bool
|
paused bool
|
||||||
wid *VidPlayback
|
frameCount int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readVideoAndAudio reads video and audio frames
|
||||||
|
// from the opened media and sends the decoded
|
||||||
|
// data to che channels to be played.
|
||||||
func (p *player) readVideoAndAudio(media *reisen.Media) (<-chan *image.RGBA, <-chan [2]float64, chan error, error) {
|
func (p *player) readVideoAndAudio(media *reisen.Media) (<-chan *image.RGBA, <-chan [2]float64, chan error, error) {
|
||||||
frameBuffer := make(chan *image.RGBA, frameBufferSize)
|
frameBuffer := make(chan *image.RGBA, frameBufferSize)
|
||||||
sampleBuffer := make(chan [2]float64, sampleBufferSize)
|
sampleBuffer := make(chan [2]float64, sampleBufferSize)
|
||||||
|
@ -55,6 +67,7 @@ func (p *player) readVideoAndAudio(media *reisen.Media) (<-chan *image.RGBA, <-c
|
||||||
|
|
||||||
videoStream := media.VideoStreams()[0]
|
videoStream := media.VideoStreams()[0]
|
||||||
err = videoStream.Open()
|
err = videoStream.Open()
|
||||||
|
p.frameCount = videoStream.FrameCount()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
|
@ -167,7 +180,6 @@ func (p *player) readVideoAndAudio(media *reisen.Media) (<-chan *image.RGBA, <-c
|
||||||
// Starts reading samples and frames
|
// Starts reading samples and frames
|
||||||
// of the media file.
|
// of the media file.
|
||||||
func (p *player) open(fname string) error {
|
func (p *player) open(fname string) error {
|
||||||
fmt.Printf("Opening %s...\n", fname)
|
|
||||||
// Sprite for drawing video frames.
|
// Sprite for drawing video frames.
|
||||||
p.pix = image.NewNRGBA(image.Rect(0, 0, width, height))
|
p.pix = image.NewNRGBA(image.Rect(0, 0, width, height))
|
||||||
|
|
||||||
|
@ -175,18 +187,28 @@ func (p *player) open(fname string) error {
|
||||||
media, err := reisen.NewMedia(fname)
|
media, err := reisen.NewMedia(fname)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Open error %s\n", err.Error())
|
fmt.Printf("Error %s\n", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the FPS for playing
|
// Get the FPS for playing
|
||||||
// video frames.
|
// video frames.
|
||||||
videoFPS, _ := media.Streams()[0].FrameRate()
|
videoFPS, _ := media.Streams()[0].FrameRate()
|
||||||
fmt.Printf("FPS %d\n", videoFPS)
|
fmt.Printf("Detected FPS of %d\n", videoFPS)
|
||||||
if videoFPS == 0 {
|
if videoFPS == 0 {
|
||||||
fmt.Printf("Assuming 60fps as FPS was 0.\n")
|
fmt.Println("Setting fps to 60 as no FPS found")
|
||||||
videoFPS = 60
|
videoFPS = 60
|
||||||
}
|
}
|
||||||
|
if videoFPS > 150 {
|
||||||
|
fmt.Printf("FPS of %d seems way too high, assuming it's *1000, so we'll use %d", videoFPS, videoFPS/1000)
|
||||||
|
videoFPS = videoFPS / 1000
|
||||||
|
}
|
||||||
|
p.fps = videoFPS
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error %s\n", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// SPF for frame ticker.
|
// SPF for frame ticker.
|
||||||
spf := 1.0 / float64(videoFPS)
|
spf := 1.0 / float64(videoFPS)
|
||||||
|
@ -199,20 +221,13 @@ func (p *player) open(fname string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start decoding streams.
|
// Start decoding streams.
|
||||||
var sampleSource <-chan [2]float64
|
p.frameBuffer, p.audioBuffer,
|
||||||
p.frameBuffer, sampleSource,
|
|
||||||
p.errs, err = p.readVideoAndAudio(media)
|
p.errs, err = p.readVideoAndAudio(media)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error %s\n", err.Error())
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start playing audio samples.
|
|
||||||
//speaker.Play(p.streamSamples(sampleSource))
|
|
||||||
// nooope.
|
|
||||||
_ = sampleSource
|
|
||||||
|
|
||||||
p.ticker = time.Tick(frameDuration)
|
p.ticker = time.Tick(frameDuration)
|
||||||
|
|
||||||
// Setup metrics.
|
// Setup metrics.
|
||||||
|
@ -220,104 +235,118 @@ func (p *player) open(fname string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *player) update(screen *canvas.Image) error {
|
var _ fyne.WidgetRenderer = (*vidPlaybackRenderer)(nil)
|
||||||
// Compute dt.
|
|
||||||
p.deltaTime = time.Since(p.last).Seconds()
|
|
||||||
p.last = time.Now()
|
|
||||||
|
|
||||||
// Check for incoming errors.
|
|
||||||
select {
|
|
||||||
case err, ok := <-p.errs:
|
|
||||||
if ok {
|
|
||||||
fmt.Printf("Error %s\n", err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read video frames and draw them.
|
|
||||||
select {
|
|
||||||
case <-p.ticker:
|
|
||||||
frame, ok := <-p.frameBuffer
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
p.pix.Pix = frame.Pix
|
|
||||||
screen.Refresh()
|
|
||||||
fmt.Print(".")
|
|
||||||
p.wid.Refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type VidPlayback struct {
|
type VidPlayback struct {
|
||||||
widget.BaseWidget
|
widget.BaseWidget
|
||||||
|
UpdateFPS int64
|
||||||
VideoFilename string
|
VideoFilename string
|
||||||
|
|
||||||
play player
|
fpsTimer *time.Ticker
|
||||||
|
videoOpened bool
|
||||||
|
playerStruct player
|
||||||
|
currentFrameID int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVidPlayback() *VidPlayback {
|
func NewVidPlayback() *VidPlayback {
|
||||||
v := &VidPlayback{}
|
w := &VidPlayback{}
|
||||||
v.play.wid = v
|
w.ExtendBaseWidget(w)
|
||||||
return v
|
w.UpdateFPS = 25
|
||||||
|
w.videoOpened = false
|
||||||
|
w.currentFrameID = 0
|
||||||
|
|
||||||
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VidPlayback) Resize(s fyne.Size) {
|
func fileIsOpenable(path string) bool {
|
||||||
v.BaseWidget.Resize(s)
|
_, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VidPlayback) CreateRenderer() fyne.WidgetRenderer {
|
func (w *VidPlayback) Play() {
|
||||||
return newVidPlaybackRenderer(v)
|
if !w.videoOpened {
|
||||||
|
if fileIsOpenable(w.VideoFilename) {
|
||||||
|
w.playerStruct.open(w.VideoFilename)
|
||||||
|
w.videoOpened = true
|
||||||
|
w.fpsTimer.Stop()
|
||||||
|
spf := 1.0 / float64(w.playerStruct.fps)
|
||||||
|
frameDuration, err := time.
|
||||||
|
ParseDuration(fmt.Sprintf("%fs", spf))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error %s\n", err.Error())
|
||||||
|
}
|
||||||
|
w.fpsTimer.Reset(frameDuration)
|
||||||
|
go func() {
|
||||||
|
// constantly empty the audio buffer in case that's what's jamming things up.
|
||||||
|
for {
|
||||||
|
_ = <-w.playerStruct.audioBuffer
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.BaseWidget.Refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VidPlayback) Play() {
|
func (w *VidPlayback) Resize(s fyne.Size) {
|
||||||
_ = v.play.open(v.VideoFilename)
|
w.BaseWidget.Resize(s)
|
||||||
v.play.paused = false
|
}
|
||||||
|
|
||||||
|
func (w *VidPlayback) CreateRenderer() fyne.WidgetRenderer {
|
||||||
|
w.fpsTimer = time.NewTicker(time.Duration(1000/25) * time.Millisecond)
|
||||||
|
go func(v *VidPlayback) {
|
||||||
|
for {
|
||||||
|
_ = <-w.fpsTimer.C
|
||||||
|
v.Refresh()
|
||||||
|
}
|
||||||
|
}(w)
|
||||||
|
return newVidPlaybackRenderer(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
type vidPlaybackRenderer struct {
|
type vidPlaybackRenderer struct {
|
||||||
vidPlayback *VidPlayback
|
vidPlayback *VidPlayback
|
||||||
currentframe *canvas.Image
|
background *canvas.Rectangle
|
||||||
|
currentframe *canvas.Raster
|
||||||
}
|
}
|
||||||
|
|
||||||
func newVidPlaybackRenderer(v *VidPlayback) *vidPlaybackRenderer {
|
func (r *vidPlaybackRenderer) actualrenderframe(w, h int) image.Image {
|
||||||
renderer := &vidPlaybackRenderer{
|
var frameImage *image.RGBA
|
||||||
vidPlayback: v,
|
if r.vidPlayback.videoOpened {
|
||||||
}
|
frameImage = <-r.vidPlayback.playerStruct.frameBuffer
|
||||||
i := image.NewRGBA(image.Rect(0, 0, 640, 480))
|
r.vidPlayback.currentFrameID = r.vidPlayback.currentFrameID + 1
|
||||||
for x := 0; x < 640; x++ {
|
fmt.Print(".")
|
||||||
for y := 0; y < 480; y++ {
|
if r.vidPlayback.currentFrameID%int64(r.vidPlayback.playerStruct.fps) == 0 {
|
||||||
lum := uint8(rand.Float32() * 255)
|
fmt.Println(r.vidPlayback.currentFrameID)
|
||||||
i.Set(x, y, color.RGBA{lum, lum, lum, 255})
|
|
||||||
}
|
}
|
||||||
|
if r.vidPlayback.currentFrameID > r.vidPlayback.playerStruct.frameCount-5 { //definitely stopping before the end
|
||||||
|
r.vidPlayback.videoOpened = false
|
||||||
|
fmt.Println("Almost at end of file!")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
frameImage = staticNoiseImage(w, h) //no point doing anything other than returning anyway!
|
||||||
}
|
}
|
||||||
renderer.currentframe = canvas.NewImageFromImage(i)
|
return frameImage
|
||||||
go func() {
|
}
|
||||||
for {
|
|
||||||
if v.play.paused {
|
|
||||||
time.Sleep(time.Millisecond * 8)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err := v.play.update(renderer.currentframe)
|
func newVidPlaybackRenderer(w *VidPlayback) *vidPlaybackRenderer {
|
||||||
if err != nil {
|
renderer := &vidPlaybackRenderer{
|
||||||
log.Println("Error playing:", err)
|
vidPlayback: w,
|
||||||
}
|
background: canvas.NewRectangle(color.RGBA{255, 0, 255, 255}),
|
||||||
}
|
}
|
||||||
}()
|
renderer.currentframe = canvas.NewRaster(renderer.actualrenderframe)
|
||||||
return renderer
|
return renderer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *vidPlaybackRenderer) Objects() []fyne.CanvasObject {
|
func (r *vidPlaybackRenderer) Objects() []fyne.CanvasObject {
|
||||||
return []fyne.CanvasObject{r.currentframe}
|
// The order is critical, rect is drawn first then currentframe
|
||||||
|
return []fyne.CanvasObject{r.background, r.currentframe}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *vidPlaybackRenderer) Layout(s fyne.Size) {
|
func (r *vidPlaybackRenderer) Layout(s fyne.Size) {
|
||||||
|
r.background.Resize(s)
|
||||||
r.currentframe.Resize(s)
|
r.currentframe.Resize(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,22 +355,9 @@ func (r *vidPlaybackRenderer) MinSize() fyne.Size {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *vidPlaybackRenderer) Refresh() {
|
func (r *vidPlaybackRenderer) Refresh() {
|
||||||
|
|
||||||
b := r.currentframe.Image.Bounds()
|
|
||||||
fmt.Print("*")
|
|
||||||
m := image.NewRGBA(image.Rect(0, 0, b.Dx(), b.Dy()))
|
|
||||||
draw.Draw(m, m.Bounds(), r.currentframe.Image, b.Min, draw.Src)
|
|
||||||
|
|
||||||
for x := 0; x < b.Dx(); x++ {
|
|
||||||
for y := 0; y < b.Dy(); y++ {
|
|
||||||
lum := uint8(rand.Float32() * 255)
|
|
||||||
m.Set(x, y, color.RGBA{lum, lum, lum, 255})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Print("+")
|
|
||||||
r.currentframe.Image = m
|
|
||||||
r.currentframe.Refresh()
|
r.currentframe.Refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *vidPlaybackRenderer) Destroy() {
|
func (r *vidPlaybackRenderer) Destroy() {
|
||||||
|
r.vidPlayback.fpsTimer.Stop()
|
||||||
} // Called when the renderer is destroyed
|
} // Called when the renderer is destroyed
|
||||||
|
|
Loading…
Reference in New Issue