结合前面所学的知识,本节我们将设计并实现了一个音乐播放器程序。这个程序只是用于演示Go语言的特性,因此大家就不要期望能看到华丽的播放界面,听到优美的音乐。

本节我们将实现以下功能:

音乐库功能,使用者可以查看、添加和删除里面的音乐曲目;

播放音乐;

支持 MP3 和 WAV,但也能随时扩展以支持更多的音乐类型;

退出程序。

由于Go语言初始定位为高并发的服务器端程序,尚未在 GUI 的支持上花费大量的精力,而当前版本的Go语言标准库中没有提供 GUI 相关的功能,也没有成熟的第三方界面库,因此不太适合开发 GUI 程序。

因此,这个程序仍然会是一个命令行程序,我们将其命名为 Simple Media Player(SMP)。该程序在运行后进入一个循环,用于监听命令输入的状态。该程序将接受以下命令。

音乐库管理命令:lib,包括 list/add/remove 命令。

播放管理:play 命令,play 后带歌曲名参数。

退出程序:q 命令。

音乐库

我们先来实现音乐库的管理模块,它管理的对象为音乐。每首音乐都包含以下信息:

唯一的 ID;

音乐名;

艺术家名;

音乐位置;

音乐文件类型(MP3 和 WAV 等)。

下面我们先定义音乐的结构体,具体如下所示:

type Music struct {

Id string

Name string

Artist string

Source string

Type string

}

然后开始实现这个音乐库管理类型,其中我们使用了一个数组切片作为基础存储结构,其他的操作其实都只是对这个数组切片的包装,代码如下所示。

//manager.go

package library

import "errors"

type MusicManager struct {

musics []MusicEntry

}

func NewMusicManager() *MusicManager {

return &MusicManager{make([]MusicEntry, 0)}

}

func (m *MusicManager) Len() int {

return len(m.musics)

}

func (m *MusicManager) Get(index int) (music *MusicEntry, err error) {

if index < 0 || index >= len(m.musics) {

return nil, errors.New("Index out of range.")

}

return &m.musics[index], nil

}

func (m *MusicManager) Find(name string) *MusicEntry {

if len(m.musics) == 0 {

return nil

}

for _, m := range m.musics {

if m.Name == name {

return &m

}

}

return nil

}

func (m *MusicManager) Add(music *MusicEntry) {

m.musics = append(m.musics, *music)

}

func (m *MusicManager) Remove(index int) *MusicEntry {

if index < 0 || index >= len(m.musics) {

return nil

}

removedMusic := &m.musics[index]

// 从数组切片中删除元素

if index < len(m.musics)-1 { // 中间元素

m.musics = append(m.musics[:index-1], m.musics[index+1:]...)

} elseif index == 0 { // 删除仅有的一个元素

m.musics = make([]MusicEntry, 0)

} else { // 删除的是最后一个元素

m.musics = m.musics[:index-1]

}

return removedMusic

}

实现了这么重要的一个基础数据管理模块后,我们应该马上编写单元测试,而不是给自己借口说等将来有空的时候再补上。下面的代码实现了 MusicManager 类型的单元测试。

//manager_test.go

package library

import (

"testing"

)

func TestOps(t *testing.T) {

mm := NewMusicManager()

if mm == nil {

t.Error("NewMusicManager failed.")

}

if mm.Len() != 0 {

t.Error("NewMusicManager failed, not empty.")

}

m0 := &MusicEntry{

"1", "My Heart Will Go On", "Celion Dion", Pop,

"http://qbox.me/24501234", MP3}

mm.Add(m0)

if mm.Len() != 1 {

t.Error("MusicManager.Add() failed.")

}

m := mm.Find(m0.Name)

if m == nil {

t.Error("MusicManager.Find() failed.")

}

if m.Id != m0.Id || m.Artist != m0.Artist ||

m.Name != m0.Name || m.Genre != m0.Genre ||

m.Source != m0.Source || m.Type != m0.Type {

t.Error("MusicManager.Find() failed. Found item mismatch.")

}

m, err := mm.Get(0)

if m == nil {

t.Error("MusicManager.Get() failed.", err)

}

m = mm.Remove(0)

if m == nil || mm.Len() != 0 {

t.Error("MusicManager.Remove() failed.", err)

}

}

这个单元测试看起来似乎有些偷懒,但它基本上已经覆盖了 MusicManager 的所有功能,实际上也确实测出了 MusicManager 实现过程中的几个问题。因此,养成良好的单元测试习惯还是非常有价值的。

音乐播放

我们接下来设计和实现音乐播放模块。按我们之前设置的目标,音乐播放模块应该是很容易扩展的,不应该在每次增加一种新音乐文件类型支持时都就需要大幅调整代码。我们来设计一个简单但又足够通用的播放函数:

func Play(source, mtype string)

这里没有直接将 MusicEntry 作为参数传入,这是因为 MusicEntry 包含了一些多余的信息。本着最小原则,我们只需要将真正需要的信息传入即可,即音乐文件的位置以及音乐的类型。

下面我们设计一个简单的接口:

type Player interface {

Play(source string)

}

然后我们可以通过一批类型(比如 MP3Player 和 WAVPlayer 等)来实现这个接口,已达到尽量的架构灵活性。因此,我们可以实现如下代码所示的总入口函数。

//play.go

package mp

import "fmt"

type Player interface {

Play(source string)

}

func Play(source, mtype string) {

var p Player

switch mtype {

case "MP3":

p = &MP3Player{}

case "WAV":

p = &WAVPlayer{}

default:

fmt.Println("Unsupported music type", mtype)

return

}

p.Play(source)

}

因为我们这个例子并不会真正实现多媒体文件的解码和播放过程,所以对于 MP3Player 和 WAVPlayer,我们只实现其中一个作为示例,代码如下所示。

//mp3.go

package mp

import (

"fmt"

"time"

)

type MP3Player struct {

stat int

progress int

}

func (p *MP3Player)Play(source string) {

fmt.Println("Playing MP3 music", source)

p.progress = 0

for p.progress < 100 {

time.Sleep(100 * time.Millisecond) // 假装正在播放

fmt.Print(".")

p.progress += 10

}

fmt.Println("\nFinished playing", source)

}

当然,我们也应该对播放流程进行单元测试。因为单元测试比较简单,这里就不再列出完整的单元测试代码了。

主程序

核心模块已经设计和实现完毕,现在就该使用它们了。我们的主程序是一个命令行交互程序,用户可以通过输入命令来控制播放过程以及获取播放信息。因为主程序与面向对象关系不大,所以我们只是为了完整性而把源代码列在这里,但不作过多解释。

在这里,我们可以顺便了解一下命令行交互程序在Go语言中的常规实现方式。下面的代码实现了音乐播放器的主程序。

//mplayer.go

package main

import (

"bufio"

"fmt"

"os"

"strconv"

"strings"

"pkg/mplayer/mlib"

"pkg/mplayer/mp"

)

var lib *library.MusicManager

var id int = 1

var ctrl, signal chan int

func handleLibCommands(tokens []string) {

switch tokens[1] {

case "list":

for i := 0; i < lib.Len(); i++ {

e, _ := lib.Get(i)

fmt.Println(i+1, ":", e.Name, e.Artist, e.Source, e.Type)

}

case "add": {

if len(tokens) == 6 {

id++

lib.Add(&library.MusicEntry{strconv.Itoa(id),

tokens[2], tokens[3], tokens[4], tokens[5]})

} else {

fmt.Println("USAGE: lib add ")

}

}

case "remove":

if len(tokens) == 3 {

lib.RemoveByName(tokens[2])

} else {

fmt.Println("USAGE: lib remove ")

}

default:

fmt.Println("Unrecognized lib command:", tokens[1])

}

}

func handlePlayCommand(tokens []string) {

if len(tokens) != 2 {

fmt.Println("USAGE: play ")

return

}

e := lib.Find(tokens[1])

if e == nil {

fmt.Println("The music", tokens[1], "does not exist.")

return

}

mp.Play(e.Source, e.Type, ctrl, signal)

}

func main() {

fmt.Println(`

Enter following commands to control the player:

lib list -- View the existing music lib

lib add -- Add a music to the music lib

lib remove -- Remove the specified music from the lib

play -- Play the specified music

`)

lib = library.NewMusicManager()

r := bufio.NewReader(os.Stdin)

for {

fmt.Print("Enter command-> ")

rawLine, _, _ := r.ReadLine()

line := string(rawLine)

if line == "q" || line == "e" {

break

}

tokens := strings.Split(line, " ")

if tokens[0] == "lib" {

handleLibCommands(tokens)

} elseif tokens[0] == "play" {

handlePlayCommand(tokens)

} else {

fmt.Println("Unrecognized command:", tokens[0])

}

}

}

构建运行

所有代码已经写完,现在可以开始构建并运行程序了,具体如下所示:

$ go run mplayer.go

Enter following commands to control the player:

lib list -- View the existing music lib

lib add -- Add a music to the music lib

lib remove -- Remove the specified music from the lib

play -- Play the specified music

Enter command-> lib add HugeStone MJ ~/MusicLib/hs.mp3 MP3

Enter command-> play HugeStone

Playing MP3 music ~/MusicLib/hs.mp3

..........

Finished playing ~/MusicLib/hs.mp3

Enter command-> lib list

1 : HugeStone MJ ~/MusicLib/hs.mp3 MP3

Enter command-> lib view

Enter command-> q

遗留问题

这个程序虽然已经写好,但是很显然它离一个可实际使用的程序还相差很远,下面我们就来谈谈遗留问题以及对策。

1)多任务

当前,我们这个程序还只是单任务程序,即同时只能执行一个任务,比如音乐正在播放时,用户不能做其他任何事情。作为一个运行在现代多任务操作系统上的应用程序,这种做法肯定是无法被用户接受的。

音乐播放过程不应导致用户界面无法响应,因此播放应该在一个单独的线程中,并能够与主程序相互通信。而且像一般的媒体播放器一样,在播放音乐的同时,我们甚至也要支持一些视觉效果的播放,即至少需要这么几个线程:用户界面、音乐播放和视频播放。

考虑到这个需求,我们自然而然地想到了使用 Go语言的看家本领 goroutine,比如将上面的播放进行稍微修改后即可将 Play() 函数作为一个独立的 goroutine 运行。

2)控制播放

因为当前这个设计是单任务的,所以播放过程无法接受外部的输入。然而作为一个成熟的播放器,我们至少需要支持暂停和停止等功能,甚至包括设置当前播放位置等。假设我们已经将播放过程放到一个独立的 goroutine 中,那么现在就是如何对这个 goroutine 进行控制的问题,这可以使用 Go语言的 channel 功能来实现。

mp3播放程序c语言,Go语言音乐播放器相关推荐

  1. 【音乐播放】基于matlab GUI动感音乐播放【含Matlab源码 778期】

    ⛄一.获取代码方式 获取代码方式1: 完整代码已上传我的资源:[音乐播放]基于matlab GUI动感音乐播放[含Matlab源码 778期] 点击上面蓝色字体,直接付费下载,即可. 获取代码方式2: ...

  2. C语言实现的音乐播放器

    C语言实现音乐播放器 #include <stdio.h> #include<dirent.h> #include<stdlib.h> #include<un ...

  3. C语言实现简易音乐播放器

    注:在显示音乐播放器的图形界面时,根据的是页面的坐标来显示,故在运行时终端的大小应为Xshell全屏时终端的大小.(否则页面会显示的混乱) 具体运行效果: 原理: 切割歌词,存入数组,通过sleep函 ...

  4. 微信小程序 :模仿酷狗音乐播放器等界面

    也没啥...就设计师要求按照他的界面来画..所以就没办法了 wxml代码: <!--当前为停止状态 --> <view style="width:250rpx;height ...

  5. 微信小程序入门与实战之音乐播放

    浮动居中方案-通过left和top定位音乐图标 我们首先要让该音乐图标脱离文档流,设置 position: absolute,再通过 left: 50%;设置居中,但此时我们的居中是对图标的左侧而言, ...

  6. 基于html的音乐播放设计,基于HTML5技术的音乐播放器的设计与实现

    Vol.33No.11 Nov.2017 赤峰学院学报(自然科学版) JournalofChifengUniversity(NaturalScienceEdition) 第 33 卷第11 期(下) ...

  7. android+多米音乐+自动播放,android 高仿多米音乐播放器

    半年前写了个音乐播放器,仿的是多米的UI界面 之前发表在eoe社区,今天也发到csdn上来 不罗嗦,先上效果图: 下面简单介绍下代码: MusicPlayer 播放音乐的核心类,该类有以下成员变量 p ...

  8. android+多米音乐+自动播放,android 高仿多米音乐播放器 (有图有码有真相)

    半年前写了个音乐播放器,仿的是多米的UI界面 之前发表在eoe社区,今天也发到csdn上来 不罗嗦,先上效果图: 下面简单介绍下代码: MusicPlayer 播放音乐的核心类,该类有以下成员变量 p ...

  9. 猿大师VLC播放程序在Chrome浏览器中同时播放25路RTSP摄像头视频流效果

    猿大师VLC播放程序在Chrome同时播放25路RTSP摄像头视频流效果,CPU及内存占用情况

  10. 项目: 用C语言写一个音乐播放器

    目录 最终效果 代码 资源地址 最终效果 代码 /************ 音乐文件中 1开头的是周杰伦的歌 2开头的是林俊杰的歌 3开头的是许嵩的歌 *************/ #include& ...

最新文章

  1. JavaScript实现智能搜索框
  2. Freebsd 下用 sshguard 防止暴力破解 ssh 密码
  3. HDU-1162-Eddy's picture
  4. mysql服务在tcp6_为什么 netstat 对某些服务只显示了 tcp6 监听端口
  5. SAP方丈-写给新手的SAP成本核算流程
  6. mysql表的视图怎么建立_MySQL如何创建视图
  7. std::recursive_mutex嵌套锁/递归锁
  8. 最近调试人脸问题的总结--命令行+抽取第二级子目录的名称
  9. 通过Mybatis获取mysql表中重复记录的方法
  10. 流利说公布上市后首份财报:第三季净收入1.8亿
  11. python两个dataframe求差集_spark计算两个DataFrame的差集、交集、合集
  12. 倒计时_考研倒计时30天,拼了
  13. python3绝对路径,相对路径
  14. 如何确定品种?——期货品种量化分类课题研究
  15. PCWorld评10大科技产品:IBM超级计算机上榜
  16. Mybatis拦截器
  17. java 几何平均数_统计学——平均数
  18. python可以ps吗_Python功能确实非常强大!不止PS可以美化照片Python也可以!满分...
  19. 0基础学软件测试工程师好学吗?
  20. 加贺电子发表手掌大小的小型轻量DLP放映机

热门文章

  1. Mac homebrew报错Error: homebrew-core is a shallow clone.
  2. 嵌入网站的挖矿代码——Cryptoloot
  3. 精雕道路怎么遍弧形_有网友私信我问郑州融信奥体世纪这个楼盘怎么样他今天来...
  4. android清理缓存功能吗,Android清理缓存功能实现
  5. python入门题目及答案_Python基础知识的一些练习与解答,python,部分,习题,及,答案...
  6. 利用MFC进行开发的通用方法介绍
  7. Linux Hung Task分析
  8. Python开发第一篇 基础篇(下)
  9. (转载)linux中编译安装log4cpp
  10. hdu3535 (分组背包,最少选一 + 最多选一 + 随意)