很多linux的开发者在研究shell的时候,都会发现有一个示例是用shell编写一个音乐播放器。作为Go语言的学习者,我们同样可以实现这样一个音乐播放器。这要用到Go语言的面向对象编程与接口编程,接下来我们逐步实现它!
首先,我们先定义一个音乐类数据的结构体,它可以包含如下信息:
type MusicEntry struct {
Id string //编号
Name string //歌名
Artist string //作者
Source string //位置
Type string //类型
}
我们来定义一个音乐库,也就是把上述内容形成一个集合,Go语言的集合可以很方便的达成要求。
type MusicManager struct {
musics []MusicEntry
}
听过我们直播课程的小伙伴一定知道,在定义结构体后,一般情况下都会提供一个入口函数,便于操作。
//入口函数
func NewMusicManager() *MusicManager {
return &MusicManager{make([]MusicEntry, 0)}
}
接下来一口气实现3个功能函数:求曲库数量,根据编号获得歌曲,根据名称查找歌曲,添加歌曲
//返回曲库当前数量
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) (me *MusicEntry, index int) {
if len(m.musics) == 0 {
return nil, -1
}
for k, m := range m.musics {
if m.Name == name {
return &m, k
}
}
return nil, -1
}
//像歌曲库添加一首歌曲
func (m *MusicManager) Add(music *MusicEntry) {
m.musics = append(m.musics, *music)
}
曲库当然也可以删除歌曲,我们提供2个接口:按编号或者按名称
//按编号删除歌曲
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:]...)
} else if index == 0 { // 删除仅有的一个元素
m.musics = make([]MusicEntry, 0)
} else { // 删除的是最后一个元素
m.musics = m.musics[:index-1]
}
return removedMusic
}
//按名称删除歌曲
func (m *MusicManager) RemoveByName(name string) (me *MusicEntry, index int) {
e, idx := m.Find(name)
if e == nil {
return nil, idx
}
m.Remove(idx)
return e, idx
}
完整的manager.go的代码如下:
/*
author:Yekai
company:Pdj
filename:manager.go
*/
package main
import "errors"
type MusicEntry struct {
Id string //编号
Name string //歌名
Artist string //作者
Source string //位置
Type string //类型
}
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) (me *MusicEntry, index int) {
if len(m.musics) == 0 {
return nil, -1
}
for k, m := range m.musics {
if m.Name == name {
return &m, k
}
}
return nil, -1
}
//像歌曲库添加一首歌曲
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:]...)
} else if index == 0 { // 删除仅有的一个元素
m.musics = make([]MusicEntry, 0)
} else { // 删除的是最后一个元素
m.musics = m.musics[:index-1]
}
return removedMusic
}
func (m *MusicManager) RemoveByName(name string) (me *MusicEntry, index int) {
e, idx := m.Find(name)
if e == nil {
return nil, idx
}
m.Remove(idx)
return e, idx
}
接下来我们考虑实现play这个动作,为了支持扩展,支持多种类型,我们定义一个接口,对于不同的歌曲类型,都支持此接口就行了。
type Player interface {
Play(source string)
}
我们先来实现一个mp3的播放器,只需要实现play接口即可!
//mp3 播放器
type MP3Player struct {
stat int //状态
progress int //进度
}
func (p *MP3Player) Play(source string) {
fmt.Println("Playing MP3 music", source)
for p.progress < 100 {
time.Sleep(100 * time.Millisecond) // 这一定是一个假的播放器,只有进度,没有声音
fmt.Print(".")
p.progress += 10
}
fmt.Println("\nFinished playing", source)
}
当然这里的播放并非去调用解码器、硬件驱动等等,我们只是一个模拟播放。
同样的,我们可以再支持wav类型的歌曲播放,套路相同。
/*
author:Yekai
company:Pdj
filename:wav.go
*/
package main
import (
"fmt"
"time"
)
//wav 播放器
type WavPlayer struct {
stat int //状态
progress int //进度
}
//播放音乐,显示进度
func (p *WavPlayer) Play(source string) {
fmt.Println("Playing wav music", source)
for p.progress < 100 {
time.Sleep(100 * time.Millisecond) // 这一定是一个假的播放器,只有进度,没有声音
fmt.Print(".")
p.progress += 10
}
fmt.Println("\nFinished playing", source)
}
我们将播放的方法统一封装:
/*
author:Yekai
company:Pdj
filename:play.go
*/
package main
import "fmt"
type Player interface {
Play(source string)
}
//播放只需要知道类型和数据位置就可以了
func Play(source, mtype string) {
var p Player
switch mtype {
case "MP3":
p = &MP3Player{0, 0}
case "WAV":
p = &WavPlayer{0, 0}
default:
fmt.Println("Unsupported music type", mtype)
return
}
p.Play(source)
}
职能函数都写完了,剩下的就是写主控逻辑了,我们编写一个接收输入的命令行窗口,给用户设计一组可以操作的指令来添加歌曲、删除歌曲、播放歌曲等,当然退出也是要给提供的。
func main() {
fmt.Println(`
Enter following commands to control the player:
lib list -- View the existing music lib
lib add <name><artist><source><type> -- Add a music to the music lib
lib remove <name> -- Remove the specified music from the lib
play <name> -- Play the specified music
`)
//曲库的入口
lib = NewMusicManager()
r := bufio.NewReader(os.Stdin)
for {
fmt.Print("Enter command-> ")
rawLine, _, _ := r.ReadLine()
line := string(rawLine)
if line == "quit" || line == "QUIT" {
break
}
tokens := strings.Split(line, " ")
if tokens[0] == "lib" {
handleLibCommands(tokens) //处理库
} else if tokens[0] == "play" {
handlePlayCommand(tokens) //播放指令
} else {
fmt.Println("Unrecognized command:", tokens[0])
}
}
}
很显然,接下来我们实现handleLibCommands和handlePlayCommand就可以了!
//根据歌曲库的处理如下:
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(&MusicEntry{strconv.Itoa(id),
tokens[2], tokens[3], tokens[4], tokens[5]})
} else {
fmt.Println("USAGE: lib add <name><artist><source><type>")
}
}
case "remove":
if len(tokens) == 3 {
lib.RemoveByName(tokens[2])
} else {
fmt.Println("USAGE: lib remove <name>")
}
default:
fmt.Println("Unrecognized lib command:", tokens[1])
}
}
如果用户想要播放歌曲,就是实现handlePlayCommand
func handlePlayCommand(tokens []string) {
if len(tokens) != 2 {
fmt.Println("USAGE: play <name>")
return
}
e, _ := lib.Find(tokens[1])
if e == nil {
fmt.Println("The music", tokens[1], "does not exist.")
return
}
Play(e.Source, e.Type)
}
完整的main.go如下:
/*
author:Yekai
company:Pdj
filename:main.go
*/
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
var lib *MusicManager
var id int = 1
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(&MusicEntry{strconv.Itoa(id),
tokens[2], tokens[3], tokens[4], tokens[5]})
} else {
fmt.Println("USAGE: lib add <name><artist><source><type>")
}
}
case "remove":
if len(tokens) == 3 {
lib.RemoveByName(tokens[2])
} else {
fmt.Println("USAGE: lib remove <name>")
}
default:
fmt.Println("Unrecognized lib command:", tokens[1])
}
}
func handlePlayCommand(tokens []string) {
if len(tokens) != 2 {
fmt.Println("USAGE: play <name>")
return
}
e, _ := lib.Find(tokens[1])
if e == nil {
fmt.Println("The music", tokens[1], "does not exist.")
return
}
Play(e.Source, e.Type)
}
func main() {
fmt.Println(`
Enter following commands to control the player:
lib list -- View the existing music lib
lib add <name><artist><source><type> -- Add a music to the music lib
lib remove <name> -- Remove the specified music from the lib
play <name> -- Play the specified music
`)
lib = NewMusicManager()
r := bufio.NewReader(os.Stdin)
for {
fmt.Print("Enter command-> ")
rawLine, _, _ := r.ReadLine()
line := string(rawLine)
if line == "quit" || line == "QUIT" {
break
}
tokens := strings.Split(line, " ")
if tokens[0] == "lib" {
handleLibCommands(tokens)
} else if tokens[0] == "play" {
handlePlayCommand(tokens)
} else {
fmt.Println("Unrecognized command:", tokens[0])
}
}
}
来跑一跑感受一下吧!
localhost:mediaplayer yekai$ go run *.go
Enter following commands to control the player:
lib list -- View the existing music lib
lib add <name><artist><source><type> -- Add a music to the music lib
lib remove <name> -- Remove the specified music from the lib
play <name> -- Play the specified music
Enter command-> lib list
Enter command-> lib add 浮夸 陈奕迅 浮夸.mp3 MP3
Enter command-> lib list
1 : 浮夸 陈奕迅 浮夸.mp3 MP3
Enter command-> play 浮夸
Playing MP3 music 浮夸.mp3
..........
Finished playing 浮夸.mp3
Enter command-> quit
localhost:mediaplayer yekai$
总结:这个案例核心逻辑在于接口的使用,从案例本身来说除了不能正常播放音乐外,也存在一些问题,比如当正在播放一个音乐的同时不能去做其他的事情,这就和Go语言的并发联系起来了,感兴趣的童鞋可以做一做,添加goroutine和channel的操作在里面!
网友评论