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

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

作者: 柏链教育 | 来源:发表于2019-12-18 14:54 被阅读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的操作在里面!

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/qklunctx.html