├── .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 | --------------------------------------------------------------------------------