diff --git a/examples/vidplayback/main.go b/examples/vidplayback/main.go index 4dabb22..58bab8d 100644 --- a/examples/vidplayback/main.go +++ b/examples/vidplayback/main.go @@ -18,7 +18,7 @@ func main() { w := a.NewWindow("Video") videoWidget = vidplayback.NewVidPlayback() - videoWidget.VideoFilename = "network.mp4" + videoWidget.VideoFilename = "1 Minute Timer-CH50zuS8DD0.mp4" layout := layouts.NewFloatingControlsLayout() layout.FloatingControlsLocation = layouts.FloatingControlsCenter diff --git a/pkg/vidplayback/vidplayback.go b/pkg/vidplayback/vidplayback.go index 9a62116..a31186d 100644 --- a/pkg/vidplayback/vidplayback.go +++ b/pkg/vidplayback/vidplayback.go @@ -6,9 +6,8 @@ import ( "fmt" "image" "image/color" - "image/draw" - "log" "math/rand" + "os" "time" "fyne.io/fyne/v2" @@ -17,7 +16,16 @@ import ( "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 ( width = 1280 @@ -36,12 +44,16 @@ type player struct { ticker <-chan time.Time errs <-chan error frameBuffer <-chan *image.RGBA + audioBuffer <-chan [2]float64 last time.Time - deltaTime float64 + fps int 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) { frameBuffer := make(chan *image.RGBA, frameBufferSize) 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] err = videoStream.Open() + p.frameCount = videoStream.FrameCount() if err != nil { 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 // of the media file. func (p *player) open(fname string) error { - fmt.Printf("Opening %s...\n", fname) // Sprite for drawing video frames. 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) if err != nil { - fmt.Printf("Open error %s\n", err.Error()) + fmt.Printf("Error %s\n", err.Error()) return err } // Get the FPS for playing // video frames. videoFPS, _ := media.Streams()[0].FrameRate() - fmt.Printf("FPS %d\n", videoFPS) + fmt.Printf("Detected FPS of %d\n", videoFPS) if videoFPS == 0 { - fmt.Printf("Assuming 60fps as FPS was 0.\n") + fmt.Println("Setting fps to 60 as no FPS found") 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 := 1.0 / float64(videoFPS) @@ -199,20 +221,13 @@ func (p *player) open(fname string) error { } // Start decoding streams. - var sampleSource <-chan [2]float64 - p.frameBuffer, sampleSource, + p.frameBuffer, p.audioBuffer, p.errs, err = p.readVideoAndAudio(media) if err != nil { - fmt.Printf("Error %s\n", err.Error()) return err } - // Start playing audio samples. - //speaker.Play(p.streamSamples(sampleSource)) - // nooope. - _ = sampleSource - p.ticker = time.Tick(frameDuration) // Setup metrics. @@ -220,104 +235,118 @@ func (p *player) open(fname string) error { return nil } -func (p *player) update(screen *canvas.Image) error { - // 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 -} +var _ fyne.WidgetRenderer = (*vidPlaybackRenderer)(nil) type VidPlayback struct { widget.BaseWidget + UpdateFPS int64 VideoFilename string - play player + fpsTimer *time.Ticker + videoOpened bool + playerStruct player + currentFrameID int64 } func NewVidPlayback() *VidPlayback { - v := &VidPlayback{} - v.play.wid = v - return v + w := &VidPlayback{} + w.ExtendBaseWidget(w) + w.UpdateFPS = 25 + w.videoOpened = false + w.currentFrameID = 0 + + return w } -func (v *VidPlayback) Resize(s fyne.Size) { - v.BaseWidget.Resize(s) +func fileIsOpenable(path string) bool { + _, err := os.Open(path) + if err != nil { + return false + } + return true } -func (v *VidPlayback) CreateRenderer() fyne.WidgetRenderer { - return newVidPlaybackRenderer(v) +func (w *VidPlayback) Play() { + 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() { - _ = v.play.open(v.VideoFilename) - v.play.paused = false +func (w *VidPlayback) Resize(s fyne.Size) { + w.BaseWidget.Resize(s) +} + +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 { vidPlayback *VidPlayback - currentframe *canvas.Image + background *canvas.Rectangle + currentframe *canvas.Raster } -func newVidPlaybackRenderer(v *VidPlayback) *vidPlaybackRenderer { - renderer := &vidPlaybackRenderer{ - vidPlayback: v, - } - i := image.NewRGBA(image.Rect(0, 0, 640, 480)) - for x := 0; x < 640; x++ { - for y := 0; y < 480; y++ { - lum := uint8(rand.Float32() * 255) - i.Set(x, y, color.RGBA{lum, lum, lum, 255}) +func (r *vidPlaybackRenderer) actualrenderframe(w, h int) image.Image { + var frameImage *image.RGBA + if r.vidPlayback.videoOpened { + frameImage = <-r.vidPlayback.playerStruct.frameBuffer + r.vidPlayback.currentFrameID = r.vidPlayback.currentFrameID + 1 + fmt.Print(".") + if r.vidPlayback.currentFrameID%int64(r.vidPlayback.playerStruct.fps) == 0 { + fmt.Println(r.vidPlayback.currentFrameID) } + 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) - go func() { - for { - if v.play.paused { - time.Sleep(time.Millisecond * 8) - continue - } + return frameImage +} - err := v.play.update(renderer.currentframe) - if err != nil { - log.Println("Error playing:", err) - } - } - }() +func newVidPlaybackRenderer(w *VidPlayback) *vidPlaybackRenderer { + renderer := &vidPlaybackRenderer{ + vidPlayback: w, + background: canvas.NewRectangle(color.RGBA{255, 0, 255, 255}), + } + renderer.currentframe = canvas.NewRaster(renderer.actualrenderframe) return renderer } 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) { + r.background.Resize(s) r.currentframe.Resize(s) } @@ -326,22 +355,9 @@ func (r *vidPlaybackRenderer) MinSize() fyne.Size { } 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() } func (r *vidPlaybackRenderer) Destroy() { + r.vidPlayback.fpsTimer.Stop() } // Called when the renderer is destroyed