Compare commits
No commits in common. "95e72034504d6d2cbbacb75b65b621fb12f26652" and "f97341fc5fbecbeb470dafe09c3a6980b22caf62" have entirely different histories.
95e7203450
...
f97341fc5f
3 changed files with 0 additions and 393 deletions
|
@ -1,30 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fyne.io/fyne/v2"
|
|
||||||
"fyne.io/fyne/v2/app"
|
|
||||||
"fyne.io/fyne/v2/container"
|
|
||||||
"fyne.io/fyne/v2/widget"
|
|
||||||
"git.martyn.berlin/martyn/fyne-widgets/pkg/layouts"
|
|
||||||
"git.martyn.berlin/martyn/fyne-widgets/pkg/vidplayback"
|
|
||||||
)
|
|
||||||
|
|
||||||
var videoWidget *vidplayback.VidPlayback
|
|
||||||
|
|
||||||
func unimplemented() {}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
a := app.New()
|
|
||||||
w := a.NewWindow("Video")
|
|
||||||
|
|
||||||
videoWidget = vidplayback.NewVidPlayback()
|
|
||||||
videoWidget.VideoFilename = "1 Minute Timer-CH50zuS8DD0.mp4"
|
|
||||||
layout := layouts.NewFloatingControlsLayout()
|
|
||||||
layout.FloatingControlsLocation = layouts.FloatingControlsCenter
|
|
||||||
|
|
||||||
buttons := container.NewHBox(widget.NewButton(">", func() { videoWidget.Play() }), widget.NewButton("[]", unimplemented))
|
|
||||||
|
|
||||||
w.SetContent(container.New(&layout, videoWidget, buttons))
|
|
||||||
w.Resize(fyne.NewSize(640, 480))
|
|
||||||
w.ShowAndRun()
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,363 +0,0 @@
|
||||||
package vidplayback
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"math/rand"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
|
||||||
"fyne.io/fyne/v2/canvas"
|
|
||||||
"fyne.io/fyne/v2/widget"
|
|
||||||
"github.com/zergon321/reisen"
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
height = 720
|
|
||||||
frameBufferSize = 1024
|
|
||||||
sampleRate = 44100
|
|
||||||
channelCount = 2
|
|
||||||
bitDepth = 8
|
|
||||||
sampleBufferSize = 32 * channelCount * bitDepth * 1024
|
|
||||||
)
|
|
||||||
|
|
||||||
// player holds all the data
|
|
||||||
// necessary for playing video.
|
|
||||||
type player struct {
|
|
||||||
pix *image.NRGBA
|
|
||||||
ticker <-chan time.Time
|
|
||||||
errs <-chan error
|
|
||||||
frameBuffer <-chan *image.RGBA
|
|
||||||
audioBuffer <-chan [2]float64
|
|
||||||
last time.Time
|
|
||||||
fps int
|
|
||||||
paused bool
|
|
||||||
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)
|
|
||||||
errs := make(chan error)
|
|
||||||
|
|
||||||
err := media.OpenDecode()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
videoStream := media.VideoStreams()[0]
|
|
||||||
err = videoStream.Open()
|
|
||||||
p.frameCount = videoStream.FrameCount()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
audioStream := media.AudioStreams()[0]
|
|
||||||
err = audioStream.Open()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
packet, gotPacket, err := media.ReadPacket()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
go func(err error) {
|
|
||||||
errs <- err
|
|
||||||
}(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !gotPacket {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
switch packet.Type() {
|
|
||||||
case reisen.StreamVideo:
|
|
||||||
s := media.Streams()[packet.StreamIndex()].(*reisen.VideoStream)
|
|
||||||
videoFrame, gotFrame, err := s.ReadVideoFrame()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
go func(err error) {
|
|
||||||
errs <- err
|
|
||||||
}(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !gotFrame {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if videoFrame == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
frameBuffer <- videoFrame.Image()
|
|
||||||
|
|
||||||
case reisen.StreamAudio:
|
|
||||||
s := media.Streams()[packet.StreamIndex()].(*reisen.AudioStream)
|
|
||||||
audioFrame, gotFrame, err := s.ReadAudioFrame()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
go func(err error) {
|
|
||||||
errs <- err
|
|
||||||
}(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !gotFrame {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if audioFrame == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Turn the raw byte data into
|
|
||||||
// audio samples of type [2]float64.
|
|
||||||
reader := bytes.NewReader(audioFrame.Data())
|
|
||||||
|
|
||||||
// See the README.md file for
|
|
||||||
// detailed scheme of the sample structure.
|
|
||||||
for reader.Len() > 0 {
|
|
||||||
sample := [2]float64{0, 0}
|
|
||||||
var result float64
|
|
||||||
err = binary.Read(reader, binary.LittleEndian, &result)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
go func(err error) {
|
|
||||||
errs <- err
|
|
||||||
}(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sample[0] = result
|
|
||||||
|
|
||||||
err = binary.Read(reader, binary.LittleEndian, &result)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
go func(err error) {
|
|
||||||
errs <- err
|
|
||||||
}(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sample[1] = result
|
|
||||||
sampleBuffer <- sample
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
videoStream.Close()
|
|
||||||
audioStream.Close()
|
|
||||||
media.CloseDecode()
|
|
||||||
close(frameBuffer)
|
|
||||||
close(sampleBuffer)
|
|
||||||
close(errs)
|
|
||||||
}()
|
|
||||||
|
|
||||||
return frameBuffer, sampleBuffer, errs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Starts reading samples and frames
|
|
||||||
// of the media file.
|
|
||||||
func (p *player) open(fname string) error {
|
|
||||||
// Sprite for drawing video frames.
|
|
||||||
p.pix = image.NewNRGBA(image.Rect(0, 0, width, height))
|
|
||||||
|
|
||||||
// Open the media file.
|
|
||||||
media, err := reisen.NewMedia(fname)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error %s\n", err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the FPS for playing
|
|
||||||
// video frames.
|
|
||||||
videoFPS, _ := media.Streams()[0].FrameRate()
|
|
||||||
fmt.Printf("Detected FPS of %d\n", videoFPS)
|
|
||||||
if videoFPS == 0 {
|
|
||||||
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)
|
|
||||||
frameDuration, err := time.
|
|
||||||
ParseDuration(fmt.Sprintf("%fs", spf))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error %s\n", err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start decoding streams.
|
|
||||||
p.frameBuffer, p.audioBuffer,
|
|
||||||
p.errs, err = p.readVideoAndAudio(media)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
p.ticker = time.Tick(frameDuration)
|
|
||||||
|
|
||||||
// Setup metrics.
|
|
||||||
p.last = time.Now()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fyne.WidgetRenderer = (*vidPlaybackRenderer)(nil)
|
|
||||||
|
|
||||||
type VidPlayback struct {
|
|
||||||
widget.BaseWidget
|
|
||||||
UpdateFPS int64
|
|
||||||
VideoFilename string
|
|
||||||
|
|
||||||
fpsTimer *time.Ticker
|
|
||||||
videoOpened bool
|
|
||||||
playerStruct player
|
|
||||||
currentFrameID int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewVidPlayback() *VidPlayback {
|
|
||||||
w := &VidPlayback{}
|
|
||||||
w.ExtendBaseWidget(w)
|
|
||||||
w.UpdateFPS = 25
|
|
||||||
w.videoOpened = false
|
|
||||||
w.currentFrameID = 0
|
|
||||||
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
func fileIsOpenable(path string) bool {
|
|
||||||
_, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (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
|
|
||||||
background *canvas.Rectangle
|
|
||||||
currentframe *canvas.Raster
|
|
||||||
}
|
|
||||||
|
|
||||||
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!
|
|
||||||
}
|
|
||||||
return frameImage
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *vidPlaybackRenderer) MinSize() fyne.Size {
|
|
||||||
return fyne.NewSize(200, 200)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *vidPlaybackRenderer) Refresh() {
|
|
||||||
r.currentframe.Refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *vidPlaybackRenderer) Destroy() {
|
|
||||||
r.vidPlayback.fpsTimer.Stop()
|
|
||||||
} // Called when the renderer is destroyed
|
|
Loading…
Add table
Reference in a new issue