Skip to content

Commit 776378e

Browse files
committed
new video_player example
1 parent 941ef6f commit 776378e

File tree

5 files changed

+545
-0
lines changed

5 files changed

+545
-0
lines changed

video_player/README.md

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# video_player
2+
3+
This Go package implements the host-side of the Flutter [video_player](https://github.com/flutter/plugins/tree/master/packages/video_player) plugin.
4+
5+
## Usage
6+
7+
Import as:
8+
9+
```go
10+
import "github.com/go-flutter-desktop/plugins/video_player"
11+
```
12+
13+
Then add the following option to your go-flutter [application options](https://github.com/go-flutter-desktop/go-flutter/wiki/Plugin-info):
14+
15+
```go
16+
flutter.AddPlugin(&video_player.VideoPlayerPlugin{}),
17+
```
18+
19+
## :warning: Disclaimer :warning:
20+
21+
This plugin is available for educational purposes, and the go-flutter team isn't
22+
actively working on it.
23+
**`Don't use it in production`** nasty bugs can occur
24+
(mostly memory leak).
25+
We are looking for maintainers. Pull Requests are most welcome!
26+
27+
## Issues
28+
29+
Please report issues in the [go-flutter **video_player** issue tracker :warning:](https://github.com/go-flutter-desktop/go-flutter/issues/134).

video_player/ffmpeg-video.go

+287
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
package video_player
2+
3+
// This file hides the ffmpeg related computations.
4+
//
5+
// The api exposed by github.com/3d0c/gmf is messy (all go-ffmpeg lib are)
6+
// which explain the following mess.
7+
//
8+
// Be very careful when you make changes to this file.
9+
//
10+
// Based on the examples 'video-to-goImage.go' of 3d0c/gmf.
11+
// TODO: fix the memory leak will occur..
12+
import (
13+
"errors"
14+
"fmt"
15+
"io"
16+
"sync"
17+
"sync/atomic"
18+
19+
"github.com/3d0c/gmf"
20+
)
21+
22+
// #include "libavformat/avformat.h"
23+
import "C"
24+
25+
type ffmpegVideo struct {
26+
swsctx *gmf.SwsCtx
27+
ist *gmf.Stream
28+
cc *gmf.CodecCtx
29+
inputCtx *gmf.FmtCtx
30+
srcVideoStream *gmf.Stream
31+
Frames chan *ffmpegFrame
32+
paused chan bool
33+
player *playerStatus
34+
pausedFlag bool
35+
width int
36+
height int
37+
}
38+
39+
type ffmpegFrame struct {
40+
// ffmpeg packet
41+
packet *gmf.Packet
42+
// in second
43+
time float64
44+
}
45+
46+
func (f *ffmpegFrame) Time() float64 {
47+
return f.time
48+
}
49+
50+
func (f *ffmpegFrame) Data() []byte {
51+
return f.packet.Data()
52+
}
53+
54+
func (f *ffmpegFrame) Free() {
55+
f.packet.Free()
56+
}
57+
58+
type playerStatus struct{ flag int32 }
59+
60+
const (
61+
allImagesProcessed = 2
62+
playing = 1
63+
noImagesAvailable = 0
64+
)
65+
66+
func (f *ffmpegVideo) Init(srcFileName string, bufferSize int) (err error) {
67+
f.player = new(playerStatus)
68+
f.paused = make(chan bool)
69+
70+
f.inputCtx, err = gmf.NewInputCtx(srcFileName)
71+
f.Frames = make(chan *ffmpegFrame, bufferSize)
72+
73+
f.srcVideoStream, err = f.inputCtx.GetBestStream(gmf.AVMEDIA_TYPE_VIDEO)
74+
if err != nil {
75+
return errors.New("No video stream found in " + srcFileName + "\n")
76+
}
77+
78+
codec, err := gmf.FindEncoder(gmf.AV_CODEC_ID_RAWVIDEO)
79+
if err != nil {
80+
return err
81+
}
82+
83+
f.cc = gmf.NewCodecCtx(codec)
84+
85+
f.cc.
86+
SetTimeBase(gmf.AVR{Num: 1, Den: 1}).
87+
SetPixFmt(gmf.AV_PIX_FMT_RGBA).
88+
SetWidth(f.srcVideoStream.CodecCtx().Width()).
89+
SetHeight(f.srcVideoStream.CodecCtx().Height())
90+
91+
f.width, f.height = f.cc.Width(), f.cc.Height()
92+
93+
if codec.IsExperimental() {
94+
f.cc.SetStrictCompliance(gmf.FF_COMPLIANCE_EXPERIMENTAL)
95+
}
96+
97+
if err := f.cc.Open(nil); err != nil {
98+
return err
99+
}
100+
101+
f.ist, err = f.inputCtx.GetStream(f.srcVideoStream.Index())
102+
if err != nil {
103+
return err
104+
}
105+
106+
// convert source pix_fmt into AV_PIX_FMT_RGBA
107+
// which is set up by codec context above
108+
icc := f.srcVideoStream.CodecCtx()
109+
if f.swsctx, err = gmf.NewSwsCtx(icc.Width(), icc.Height(), icc.PixFmt(), f.cc.Width(), f.cc.Height(), f.cc.PixFmt(), gmf.SWS_BICUBIC); err != nil {
110+
return err
111+
}
112+
113+
return nil
114+
}
115+
116+
func (f *ffmpegVideo) Free() {
117+
f.Cancel()
118+
f.srcVideoStream.Free()
119+
f.swsctx.Free()
120+
f.ist.Free()
121+
f.inputCtx.Free()
122+
gmf.Release(f.cc)
123+
f.cc.Free()
124+
close(f.Frames)
125+
}
126+
127+
func (f *ffmpegVideo) Stream(onFirstFrame func()) {
128+
drain := -1
129+
hasConsumer := false
130+
var wg sync.WaitGroup
131+
132+
defer func() {
133+
if r := recover(); r != nil {
134+
fmt.Println("go-flutter/plugins/video_player: recover: ", r)
135+
for len(f.Frames) > 0 { // clean the frame channel
136+
pixels := <-f.Frames
137+
defer pixels.Free()
138+
}
139+
f.Cancel()
140+
}
141+
}()
142+
143+
for {
144+
if drain >= 0 {
145+
break
146+
}
147+
148+
pkt, err := f.inputCtx.GetNextPacket()
149+
if err != nil && err != io.EOF {
150+
if pkt != nil {
151+
pkt.Free()
152+
}
153+
fmt.Printf("go-flutter/plugins/video_player: error getting next packet - %s\n", err)
154+
break
155+
} else if err != nil && pkt == nil {
156+
drain = 0
157+
}
158+
159+
if pkt != nil && pkt.StreamIndex() != f.srcVideoStream.Index() {
160+
continue
161+
}
162+
163+
frames, err := f.ist.CodecCtx().Decode(pkt)
164+
if err != nil {
165+
fmt.Printf("go-flutter/plugins/video_player: Fatal error during decoding - %s\n", err)
166+
break
167+
}
168+
169+
// Decode() method doesn't treat EAGAIN and EOF as errors
170+
// it returns empty frames slice instead. Countinue until
171+
// input EOF or frames received.
172+
if len(frames) == 0 && drain < 0 {
173+
continue
174+
}
175+
176+
if frames, err = gmf.DefaultRescaler(f.swsctx, frames); err != nil {
177+
panic(err)
178+
}
179+
180+
packets, err := f.cc.Encode(frames, drain)
181+
if err != nil {
182+
fmt.Printf("go-flutter/plugins/video_player: Error encoding - %s\n", err)
183+
panic(err)
184+
}
185+
186+
for _, p := range packets {
187+
188+
if f.Closed() && hasConsumer {
189+
break
190+
}
191+
192+
timebase := f.srcVideoStream.TimeBase()
193+
time := float64(timebase.AVR().Num) / float64(timebase.AVR().Den) * float64(p.Pts())
194+
f.Frames <- &ffmpegFrame{packet: p, time: time}
195+
if !hasConsumer {
196+
f.play()
197+
wg.Add(1)
198+
go func() {
199+
onFirstFrame()
200+
wg.Done()
201+
}()
202+
hasConsumer = true
203+
}
204+
205+
}
206+
207+
for i := range frames {
208+
frames[i].Free()
209+
}
210+
211+
if pkt != nil {
212+
pkt.Free()
213+
pkt = nil
214+
}
215+
}
216+
if !f.Closed() {
217+
f.EndOfVideo()
218+
}
219+
for i := 0; i < f.inputCtx.StreamsCnt(); i++ {
220+
st, _ := f.inputCtx.GetStream(i)
221+
defer st.CodecCtx().Free()
222+
defer st.Free()
223+
}
224+
225+
wg.Wait()
226+
}
227+
228+
func (f *ffmpegVideo) Bounds() (int, int) {
229+
return f.width, f.height
230+
}
231+
232+
func (f *ffmpegVideo) GetFrameRate() float64 {
233+
a := f.srcVideoStream.GetRFrameRate().AVR()
234+
return float64(a.Den) / float64(a.Num)
235+
}
236+
237+
func (f *ffmpegVideo) Duration() float64 {
238+
return f.inputCtx.Duration()
239+
}
240+
241+
func (f *ffmpegVideo) EndOfVideo() {
242+
f.Set(allImagesProcessed)
243+
}
244+
245+
func (f *ffmpegVideo) play() {
246+
f.Set(playing)
247+
}
248+
249+
func (f *ffmpegVideo) Pause() {
250+
f.pausedFlag = true
251+
}
252+
253+
func (f *ffmpegVideo) UnPause() {
254+
f.pausedFlag = false
255+
f.paused <- true
256+
}
257+
258+
func (f *ffmpegVideo) Cancel() {
259+
f.Set(noImagesAvailable)
260+
}
261+
262+
func (f *ffmpegVideo) Set(value int32) {
263+
atomic.StoreInt32(&(f.player.flag), value)
264+
}
265+
266+
func (f *ffmpegVideo) Closed() bool {
267+
flag := atomic.LoadInt32(&(f.player.flag))
268+
if flag == allImagesProcessed && len(f.Frames) <= 1 {
269+
defer f.Set(noImagesAvailable)
270+
}
271+
return flag != playing && len(f.Frames) == 0
272+
}
273+
274+
func (f *ffmpegVideo) HasFrameAvailable() bool {
275+
flag := atomic.LoadInt32(&(f.player.flag))
276+
277+
if flag == allImagesProcessed || flag == playing {
278+
return true
279+
}
280+
return false
281+
}
282+
283+
func (f *ffmpegVideo) WaitUnPause() {
284+
if f.pausedFlag {
285+
<-f.paused
286+
}
287+
}

video_player/go.mod

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module github.com/go-flutter-desktop/plugins/video_player
2+
3+
go 1.12
4+
5+
require (
6+
github.com/3d0c/gmf v0.0.0-20190724130615-f4b5acb7db5c
7+
github.com/go-flutter-desktop/go-flutter v0.27.4-0.20190804194945-75d1c8142b5d
8+
)

video_player/go.sum

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
github.com/3d0c/gmf v0.0.0-20190724130615-f4b5acb7db5c h1:0MMmgSFC0S35+N5rZlUIuQL56GhKz3ISBVnLkzxKmaI=
2+
github.com/3d0c/gmf v0.0.0-20190724130615-f4b5acb7db5c/go.mod h1:0QMRcUq2JsDECeAq7bj4h79k7XbhtTsrPUQf6G7qfPs=
3+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
5+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6+
github.com/go-flutter-desktop/go-flutter v0.27.4-0.20190804194945-75d1c8142b5d h1:hAF4o4UQzPlDCrP1pxSrFdJJEg82Dn1K4SaESYv+5KU=
7+
github.com/go-flutter-desktop/go-flutter v0.27.4-0.20190804194945-75d1c8142b5d/go.mod h1:j9eXkEzV8ku7a3Ve4s8cYKPiOkMIAg549S8K9VomyY4=
8+
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw=
9+
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
10+
github.com/go-gl/glfw v0.0.0-20190519095719-e6da0acd62b1 h1:noz9OnjV5PMOZWNOI+y1cS5rnxuJfpY6leIgQEEdBQw=
11+
github.com/go-gl/glfw v0.0.0-20190519095719-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
12+
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
13+
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
14+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
15+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
16+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
17+
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
18+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=

0 commit comments

Comments
 (0)