fyne-widgets/pkg/vidplayback/vidplayback.go

348 lines
6.6 KiB
Go
Raw Normal View History

package vidplayback
import (
"bytes"
"encoding/binary"
"fmt"
"image"
"image/color"
"image/draw"
"log"
"math/rand"
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/widget"
"github.com/zergon321/reisen"
)
var _ fyne.WidgetRenderer = (*vidPlaybackRenderer)(nil)
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
last time.Time
deltaTime float64
paused bool
wid *VidPlayback
}
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()
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 {
fmt.Printf("Opening %s...\n", fname)
// 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("Open 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)
if videoFPS == 0 {
fmt.Printf("Assuming 60fps as FPS was 0.\n")
videoFPS = 60
}
// 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.
var sampleSource <-chan [2]float64
p.frameBuffer, sampleSource,
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.
p.last = time.Now()
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
}
type VidPlayback struct {
widget.BaseWidget
VideoFilename string
play player
}
func NewVidPlayback() *VidPlayback {
v := &VidPlayback{}
v.play.wid = v
return v
}
func (v *VidPlayback) Resize(s fyne.Size) {
v.BaseWidget.Resize(s)
}
func (v *VidPlayback) CreateRenderer() fyne.WidgetRenderer {
return newVidPlaybackRenderer(v)
}
func (v *VidPlayback) Play() {
_ = v.play.open(v.VideoFilename)
v.play.paused = false
}
type vidPlaybackRenderer struct {
vidPlayback *VidPlayback
currentframe *canvas.Image
}
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})
}
}
renderer.currentframe = canvas.NewImageFromImage(i)
go func() {
for {
if v.play.paused {
time.Sleep(time.Millisecond * 8)
continue
}
err := v.play.update(renderer.currentframe)
if err != nil {
log.Println("Error playing:", err)
}
}
}()
return renderer
}
func (r *vidPlaybackRenderer) Objects() []fyne.CanvasObject {
return []fyne.CanvasObject{r.currentframe}
}
func (r *vidPlaybackRenderer) Layout(s fyne.Size) {
r.currentframe.Resize(s)
}
func (r *vidPlaybackRenderer) MinSize() fyne.Size {
return fyne.NewSize(200, 200)
}
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() {
} // Called when the renderer is destroyed