Skip to content

Commit 4fa88c0

Browse files
authored
Merge pull request #23 from go-flutter-desktop/plugin/video_player
Plugin/video player
2 parents a96f8df + 5546558 commit 4fa88c0

File tree

6 files changed

+550
-0
lines changed

6 files changed

+550
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Please report issues at the [go-flutter issue tracker](https://github.com/go-flu
1212
- [path_provider](path_provider) - Finding commonly used locations on the filesystem. ([pub.dev](https://pub.dev/packages/path_provider))
1313
- [shared_preferences](shared_preferences) - Provides a persistent store for simple data. ([pub.dev](https://pub.dev/packages/shared_preferences))
1414
- [url_launcher](url_launcher) - Flutter plugin for launching a URL. ([pub.dev](https://pub.dev/packages/url_launcher))
15+
- [video_player](video_player) - Flutter plugin for playing back video on a Widget surface. ([pub.dev](https://pub.dev/packages/video_player)) (:warning: work-in-progress, needs rewrite)
1516

1617

1718
## From the community

video_player/README.md

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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+
The plugin uses a third party library to handle video to image decoding,
20+
[3d0c/gmf](https://github.com/3d0c/gmf), a go
21+
FFmpeg bindings.
22+
If you have trouble installing the plugin, checkout their [installation](https://github.com/3d0c/gmf#installation) procedure.
23+
24+
## :warning: Disclaimer :warning:
25+
26+
This plugin is available for educational purposes, and the go-flutter team isn't
27+
actively working on it.
28+
**`Don't use it in production`** nasty bugs can occur
29+
(mostly memory leak).
30+
The plugin needs a significant rewrite. We are looking for maintainers. Pull Requests are most welcome!
31+
32+
## Issues
33+
34+
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

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

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.29.0
8+
)

video_player/go.sum

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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.29.0 h1:PONS1I6HN64tsBViWSnI/DMUCs2Oth37Gg1eDkGri0g=
7+
github.com/go-flutter-desktop/go-flutter v0.29.0/go.mod h1:jOq/jZc4gZCibc661M7R5nDVc9/mv2dDMpWsyNaVuGY=
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.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
18+
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
19+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
20+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
21+
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
22+
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

0 commit comments

Comments
 (0)