音乐播放器都用过,我们用Go语言给你实现一个

2019-12-18  本文已影响0人  柏链教育

很多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的操作在里面!

上一篇下一篇

猜你喜欢

热点阅读