├── .gitignore
├── README.md
└── mp3-player.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # Folders
7 | _obj
8 | _test
9 |
10 | # Architecture specific extensions/prefixes
11 | *.[568vq]
12 | [568vq].out
13 |
14 | *.cgo1.go
15 | *.cgo2.c
16 | _cgo_defun.c
17 | _cgo_gotypes.go
18 | _cgo_export.*
19 |
20 | _testmain.go
21 |
22 | *.exe
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
go-mp3-player
=============
mp3 player in golang by gstreamer
n ==> Next song
p ==> Previous song
s ==> Stop song
r ==> Play song
t ==> seek song(5s)
q ==> Quit
2 |
--------------------------------------------------------------------------------
/mp3-player.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | /*
4 | #cgo pkg-config: gstreamer-1.0
5 | #include
6 |
7 |
8 | // ******************** 定义消息处理函数 ********************
9 | gboolean bus_call(GstBus *bus, GstMessage *msg, gpointer data)
10 | {
11 | GMainLoop *loop = (GMainLoop *)data;//这个是主循环的指针,在接受EOS消息时退出循环
12 | gchar *debug;
13 | GError *error;
14 |
15 | switch (GST_MESSAGE_TYPE(msg)) {
16 | case GST_MESSAGE_EOS:
17 | g_main_loop_quit(loop);
18 | //g_print("EOF\n");
19 | break;
20 | case GST_MESSAGE_ERROR:
21 | gst_message_parse_error(msg,&error,&debug);
22 | g_free(debug);
23 | g_printerr("ERROR:%s\n",error->message);
24 | g_error_free(error);
25 | g_main_loop_quit(loop);
26 | break;
27 | default:
28 | break;
29 | }
30 |
31 | return TRUE;
32 | }
33 |
34 | static GstBus *pipeline_get_bus(void *pipeline)
35 | {
36 | return gst_pipeline_get_bus(GST_PIPELINE(pipeline));
37 | }
38 |
39 | static void bus_add_watch(void *bus, void *loop)
40 | {
41 | gst_bus_add_watch(bus, bus_call, loop);
42 | gst_object_unref(bus);
43 | }
44 |
45 | static void set_path(void *play, gchar *path)
46 | {
47 | g_object_set(G_OBJECT(play), "uri", path, NULL);
48 | }
49 |
50 | static void object_unref(void *pipeline)
51 | {
52 | gst_object_unref(GST_OBJECT(pipeline));
53 | }
54 |
55 | static void media_ready(void *pipeline)
56 | {
57 | gst_element_set_state(pipeline, GST_STATE_READY);
58 | }
59 |
60 | static void media_pause(void *pipeline)
61 | {
62 | gst_element_set_state(pipeline, GST_STATE_PAUSED);
63 | }
64 |
65 | static void media_play(void *pipeline)
66 | {
67 | gst_element_set_state(pipeline, GST_STATE_PLAYING);
68 | }
69 |
70 | static void media_stop(void *pipeline)
71 | {
72 | gst_element_set_state(pipeline, GST_STATE_NULL);
73 | }
74 |
75 | static void set_mute(void *play)
76 | {
77 | g_object_set(G_OBJECT(play), "mute", FALSE, NULL);
78 | }
79 |
80 | static void set_volume(void *play, int vol)
81 | {
82 | int ret = vol % 101;
83 |
84 | g_object_set(G_OBJECT(play), "volume", ret/10.0, NULL);
85 | }
86 | static void media_seek(void *pipeline, gint64 pos)
87 | {
88 | gint64 cpos;
89 |
90 | gst_element_query_position (pipeline, GST_FORMAT_TIME, &cpos);
91 | cpos += pos*1000*1000*1000;
92 | if (!gst_element_seek (pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH,
93 | GST_SEEK_TYPE_SET, cpos,
94 | GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) {
95 | g_print ("Seek failed!\n");
96 | }
97 | }
98 |
99 | */
100 | import "C"
101 |
102 | import (
103 | "container/list"
104 | "flag"
105 | "fmt"
106 | "math/rand"
107 | "os"
108 | "path/filepath"
109 | "runtime/debug"
110 | "sync"
111 | "time"
112 | "unsafe"
113 | )
114 |
115 | const MP3_FILE_MAX = 10
116 |
117 | const (
118 | PLAY_STYLE_ORDER = 0x100
119 | PLAY_STYLE_SINGLE = 0x200
120 | PLAY_STYLE_SLOOP = 0x300
121 | PLAY_STYLE_ALOOP = 0x400
122 | PLAY_STYLE_SHUFFLE = 0x500
123 | )
124 |
125 | var g_list *list.List
126 | var g_wg *sync.WaitGroup
127 | var g_isQuit bool = false
128 | var g_play_style int
129 | var g_isOutOfOrder bool
130 | var g_volume_size int = 10
131 |
132 | func GString(s string) *C.gchar {
133 | return (*C.gchar)(C.CString(s))
134 | }
135 |
136 | func GFree(s unsafe.Pointer) {
137 | C.g_free(C.gpointer(s))
138 | }
139 |
140 | func walkFunc(fpath string, info os.FileInfo, err error) error {
141 | if info.IsDir() {
142 | return nil
143 | }
144 | switch filepath.Ext(fpath) {
145 | case ".mp3":
146 | case ".wav":
147 | case ".ogg":
148 | case ".wma":
149 | case ".rmvb":
150 | default:
151 | return nil
152 | }
153 | if x, err0 := filepath.Abs(fpath); err != nil {
154 | err = err0
155 | return err
156 | } else {
157 | p := fmt.Sprintf("file://%s", x)
158 | g_list.PushBack(p)
159 | }
160 |
161 | return err
162 | }
163 |
164 | func outOfOrder(l *list.List) {
165 | iTotal := 25
166 | if iTotal > l.Len() {
167 | iTotal = l.Len()
168 | }
169 | ll := make([]*list.List, iTotal)
170 |
171 | for i := 0; i < iTotal; i++ {
172 | ll[i] = list.New()
173 | }
174 | r := rand.New(rand.NewSource(time.Now().UnixNano()))
175 | for e := l.Front(); e != nil; e = e.Next() {
176 | fpath, ok := e.Value.(string)
177 | if !ok {
178 | panic("The path is invalid string")
179 | }
180 | if rand.Int()%2 == 0 {
181 | ll[r.Intn(iTotal)].PushFront(fpath)
182 | } else {
183 | ll[r.Intn(iTotal)].PushBack(fpath)
184 | }
185 | }
186 |
187 | r0 := rand.New(rand.NewSource(time.Now().UnixNano()))
188 | l.Init()
189 | for i := 0; i < iTotal; i++ {
190 | if r0.Intn(2) == 0 {
191 | l.PushBackList(ll[i])
192 | } else {
193 | l.PushFrontList(ll[i])
194 | }
195 | ll[i].Init()
196 | }
197 | }
198 |
199 | func SinglePlayProcess(fpath string, loop *C.GMainLoop) {
200 | // fmt.Printf("filename[%s]\n", fpath)
201 | var pipeline *C.GstElement // 定义组件
202 | var bus *C.GstBus
203 |
204 | switch t := filepath.Ext(fpath); t {
205 | case ".mp3":
206 | case ".wav":
207 | case ".ogg":
208 | case ".wma":
209 | case ".rmvb":
210 | default:
211 | fmt.Printf("不支持此文件格式[%s]\n", t)
212 | return
213 | }
214 |
215 | v0 := GString("playbin")
216 | v1 := GString("play")
217 | pipeline = C.gst_element_factory_make(v0, v1)
218 | GFree(unsafe.Pointer(v0))
219 | GFree(unsafe.Pointer(v1))
220 | v2 := GString(fpath)
221 | C.set_path(unsafe.Pointer(pipeline), v2)
222 | GFree(unsafe.Pointer(v2))
223 |
224 | // 得到 管道的消息总线
225 | bus = C.pipeline_get_bus(unsafe.Pointer(pipeline))
226 | if bus == (*C.GstBus)(nil) {
227 | fmt.Println("GstBus element could not be created.Exiting.")
228 | return
229 | }
230 | C.bus_add_watch(unsafe.Pointer(bus), unsafe.Pointer(loop))
231 |
232 | C.media_ready(unsafe.Pointer(pipeline))
233 | C.media_play(unsafe.Pointer(pipeline))
234 |
235 | // 开始循环
236 | C.g_main_loop_run(loop)
237 | C.media_stop(unsafe.Pointer(pipeline))
238 | C.object_unref(unsafe.Pointer(pipeline))
239 | }
240 |
241 | func PlayProcess(cs chan byte, loop *C.GMainLoop) {
242 | var pipeline *C.GstElement // 定义组件
243 | var bus *C.GstBus
244 |
245 | wg := new(sync.WaitGroup)
246 | sig_out := make(chan bool)
247 |
248 | g_wg.Add(1)
249 | defer close(sig_out)
250 | defer g_wg.Done()
251 | if g_isOutOfOrder {
252 | outOfOrder(g_list)
253 | debug.FreeOSMemory()
254 | }
255 |
256 | start := g_list.Front()
257 | end := g_list.Back()
258 | e := g_list.Front()
259 |
260 | v0 := GString("playbin")
261 | v1 := GString("play")
262 | pipeline = C.gst_element_factory_make(v0, v1)
263 | GFree(unsafe.Pointer(v0))
264 | GFree(unsafe.Pointer(v1))
265 | // 得到 管道的消息总线
266 | bus = C.pipeline_get_bus(unsafe.Pointer(pipeline))
267 | if bus == (*C.GstBus)(nil) {
268 | fmt.Println("GstBus element could not be created.Exiting.")
269 | return
270 | }
271 | C.bus_add_watch(unsafe.Pointer(bus), unsafe.Pointer(loop))
272 | // 开始循环
273 |
274 | go func(sig_quit chan bool) {
275 | wg.Add(1)
276 | i := 0
277 | LOOP_RUN:
278 | for !g_isQuit {
279 | if i != 0 {
280 | C.media_ready(unsafe.Pointer(pipeline))
281 | C.media_play(unsafe.Pointer(pipeline))
282 | }
283 | C.g_main_loop_run(loop)
284 | C.media_stop(unsafe.Pointer(pipeline))
285 | switch g_play_style {
286 | case PLAY_STYLE_SINGLE:
287 | sig_quit <- true
288 | break LOOP_RUN
289 |
290 | case PLAY_STYLE_ORDER:
291 | if e != end {
292 | e = e.Next()
293 | } else {
294 | break LOOP_RUN
295 | }
296 |
297 | case PLAY_STYLE_SHUFFLE:
298 | if e != end {
299 | e = e.Next()
300 | } else {
301 | break LOOP_RUN
302 | }
303 |
304 | case PLAY_STYLE_SLOOP:
305 |
306 | case PLAY_STYLE_ALOOP:
307 | if e != end {
308 | e = e.Next()
309 | } else {
310 | e = start
311 | }
312 |
313 | }
314 | fpath, ok := e.Value.(string)
315 | if ok {
316 | v2 := GString(fpath)
317 | C.set_path(unsafe.Pointer(pipeline), v2)
318 | GFree(unsafe.Pointer(v2))
319 |
320 | } else {
321 | break
322 | }
323 | i++
324 | }
325 |
326 | C.object_unref(unsafe.Pointer(pipeline))
327 | wg.Done()
328 |
329 | }(sig_out)
330 |
331 | fpath, ok := e.Value.(string)
332 | if ok {
333 | // fmt.Printf("filename[%s]\n", fpath)
334 | v2 := GString(fpath)
335 | C.set_path(unsafe.Pointer(pipeline), v2)
336 | GFree(unsafe.Pointer(v2))
337 |
338 | C.media_ready(unsafe.Pointer(pipeline))
339 | C.media_play(unsafe.Pointer(pipeline))
340 | //C.set_mute(unsafe.Pointer(pipeline))
341 |
342 | lb := true
343 | for lb {
344 | select {
345 | case op := <-cs:
346 | switch op {
347 | case 's':
348 | C.media_pause(unsafe.Pointer(pipeline))
349 | case 'r':
350 | C.media_play(unsafe.Pointer(pipeline))
351 | case 'n':
352 | switch g_play_style {
353 | case PLAY_STYLE_SINGLE:
354 | lb = false
355 | g_isQuit = true
356 | case PLAY_STYLE_ORDER:
357 | fallthrough
358 | case PLAY_STYLE_SHUFFLE:
359 |
360 | C.media_stop(unsafe.Pointer(pipeline))
361 | if e != end {
362 | e = e.Next()
363 | } else {
364 | lb = false
365 | g_isQuit = true
366 | }
367 | case PLAY_STYLE_SLOOP:
368 | C.media_stop(unsafe.Pointer(pipeline))
369 |
370 | case PLAY_STYLE_ALOOP:
371 | if e != end {
372 | e = e.Next()
373 | } else {
374 | e = start
375 | }
376 |
377 | }
378 | if !lb {
379 | fpath, ok := e.Value.(string)
380 | if ok {
381 | v2 := GString(fpath)
382 | C.set_path(unsafe.Pointer(pipeline), v2)
383 | GFree(unsafe.Pointer(v2))
384 | C.media_ready(unsafe.Pointer(pipeline))
385 | C.media_play(unsafe.Pointer(pipeline))
386 | } else {
387 | lb = false
388 | g_isQuit = true
389 | }
390 | }
391 | //C.g_main_loop_quit(loop)
392 | case 'p':
393 | switch g_play_style {
394 | case PLAY_STYLE_SINGLE:
395 | // do nothing ???
396 | case PLAY_STYLE_ORDER:
397 | fallthrough
398 | case PLAY_STYLE_SHUFFLE:
399 |
400 | C.media_stop(unsafe.Pointer(pipeline))
401 | if e != start {
402 | e = e.Prev()
403 | fpath, ok := e.Value.(string)
404 | if ok {
405 | v2 := GString(fpath)
406 | C.set_path(unsafe.Pointer(pipeline), v2)
407 | GFree(unsafe.Pointer(v2))
408 | C.media_ready(unsafe.Pointer(pipeline))
409 | C.media_play(unsafe.Pointer(pipeline))
410 | } else {
411 | lb = false
412 | g_isQuit = true
413 | }
414 | } else {
415 | lb = false
416 | g_isQuit = true
417 | }
418 | case PLAY_STYLE_SLOOP:
419 | C.media_stop(unsafe.Pointer(pipeline))
420 | fpath, ok := e.Value.(string)
421 | if ok {
422 | v2 := GString(fpath)
423 | C.set_path(unsafe.Pointer(pipeline), v2)
424 | GFree(unsafe.Pointer(v2))
425 | C.media_ready(unsafe.Pointer(pipeline))
426 | C.media_play(unsafe.Pointer(pipeline))
427 | }
428 | case PLAY_STYLE_ALOOP:
429 | C.media_stop(unsafe.Pointer(pipeline))
430 | if e != start {
431 | e = e.Prev()
432 | } else {
433 | e = end
434 | }
435 | fpath, ok := e.Value.(string)
436 | if ok {
437 | v2 := GString(fpath)
438 | C.set_path(unsafe.Pointer(pipeline), v2)
439 | GFree(unsafe.Pointer(v2))
440 | C.media_ready(unsafe.Pointer(pipeline))
441 | C.media_play(unsafe.Pointer(pipeline))
442 | }
443 | }
444 |
445 | case 'q':
446 | lb = false
447 | g_isQuit = true
448 | case '+':
449 | g_volume_size++
450 | C.set_volume(unsafe.Pointer(pipeline), C.int(g_volume_size))
451 | case '-':
452 | g_volume_size--
453 | if g_volume_size < 0 {
454 | g_volume_size = 0
455 | }
456 | C.set_volume(unsafe.Pointer(pipeline), C.int(g_volume_size))
457 | case 't':
458 | C.media_seek(unsafe.Pointer(pipeline), C.gint64(5))
459 |
460 | }
461 | case vv0 := <-sig_out:
462 | if vv0 {
463 | C.g_main_loop_quit(loop)
464 | wg.Wait()
465 | g_wg.Done()
466 | g_wg.Wait()
467 | close(sig_out)
468 | os.Exit(0)
469 | }
470 | }
471 | }
472 |
473 | } else {
474 | // 路径非法
475 | return
476 | }
477 |
478 | C.g_main_loop_quit(loop)
479 | wg.Wait()
480 |
481 | }
482 |
483 | func main() {
484 | var loop *C.GMainLoop
485 | var s0 byte
486 | mdir := ""
487 | mfile := ""
488 | style := ""
489 |
490 | flag.StringVar(&mdir, "dir", "", "mp3文件目录")
491 | flag.StringVar(&mfile, "file", "", "mp3文件")
492 | flag.StringVar(&style, "style", "order", "播放方式[顺序:order|乱序:shuffle|单曲:single|单曲循环:sloop|全部循环:aloop]")
493 | flag.Parse()
494 |
495 | switch style {
496 | case "shuffle":
497 | g_isOutOfOrder = true
498 | g_play_style = PLAY_STYLE_SHUFFLE
499 |
500 | case "order":
501 | g_play_style = PLAY_STYLE_ORDER
502 | case "single":
503 | g_play_style = PLAY_STYLE_SINGLE
504 | case "sloop":
505 | g_play_style = PLAY_STYLE_SLOOP
506 | case "aloop":
507 | g_play_style = PLAY_STYLE_ALOOP
508 | default:
509 | flag.PrintDefaults()
510 | return
511 | }
512 | g_list = list.New()
513 | if mfile != "" {
514 | p, err := filepath.Abs(mfile)
515 | if err != nil {
516 | fmt.Printf("Error: %v\n", err)
517 | return
518 | }
519 | C.gst_init((*C.int)(unsafe.Pointer(nil)),
520 | (***C.char)(unsafe.Pointer(nil)))
521 | loop = C.g_main_loop_new((*C.GMainContext)(unsafe.Pointer(nil)),
522 | C.gboolean(0)) // 创建主循环,在执行 g_main_loop_run后正式开始循环
523 | mfile = fmt.Sprintf("file://%s", p)
524 | g_list.PushBack(mfile)
525 | g_play_style = PLAY_STYLE_SINGLE
526 | } else {
527 | if mdir == "" {
528 | flag.PrintDefaults()
529 | return
530 | }
531 | if err := filepath.Walk(mdir, walkFunc); err != nil {
532 | fmt.Printf("Error: %v\n", err)
533 | return
534 | }
535 | }
536 |
537 | g_wg = new(sync.WaitGroup)
538 | C.gst_init((*C.int)(unsafe.Pointer(nil)),
539 | (***C.char)(unsafe.Pointer(nil)))
540 | loop = C.g_main_loop_new((*C.GMainContext)(unsafe.Pointer(nil)),
541 | C.gboolean(0)) // 创建主循环,在执行 g_main_loop_run后正式开始循环
542 |
543 | s := make(chan byte)
544 | defer close(s)
545 | go PlayProcess(s, loop)
546 |
547 | isQuit := false
548 | for !isQuit {
549 | fmt.Fscanf(os.Stdin, "%c\n", &s0)
550 | switch s0 {
551 | case 's':
552 | fallthrough
553 | case 'r':
554 | fallthrough
555 | case 'n':
556 | fallthrough
557 | case 'p':
558 | fallthrough
559 | case 't':
560 | fallthrough
561 | case '+':
562 | fallthrough
563 | case '-':
564 | s <- s0
565 | case 'q':
566 | s <- s0
567 | isQuit = true
568 | case 'h':
569 | fmt.Print("'s' -> 暂停\n" +
570 | "'r' -> 继续\n" +
571 | "'n' -> 下一首\n" +
572 | "'p' -> 上一首\n" +
573 | "'q' -> 退出\n")
574 | }
575 | s0 = 0
576 | }
577 |
578 | g_wg.Wait()
579 |
580 | }
581 |
--------------------------------------------------------------------------------