Golang的GUI库-Fyne使用案例-MP3播放器

先总体对界面进行定义如下:

type AppGUI struct {
	baseDir           string              // 文件目录
	songs             []string            // 歌曲集合
	curSong           *MusicEntry         // 当前歌曲
	currentSongName   *widget.Label       // 当前曲目名称
	progress          *widget.ProgressBar // 播放进度
	consumedTime      *widget.Label       // 已用时间
	remainedTime      *widget.Label       // 剩余时间
	playBtn           *widget.Button      // 播放
	paused            bool                // 是否暂停标志
	nextBtn           *widget.Button      // 下一首
	preBtn            *widget.Button      // 上一首
	forwardBtn        *widget.Button      // 快进
	backwardBtn       *widget.Button      // 快退
	songIdx           int                 // 当前歌曲序号
	appDir            string              // 程序运行目录
	newSongFlag       bool                // 新的一首歌
	endUpdateProgress chan bool           // 停止更新进度条
}

目前已经实现的功能有:MP3文件的切换,播放/暂停,上一首,下一首。
总体界面运行效果如下图,由于Fyne没有原生的文件选择器,所以这里使用的固定目录存放Mp3文件,使用的时候需要将Mp3文件放置在与应用程序同级的music_res目录中,程序启动时会自动识别该目录下的Mp3文件:
在这里插入图片描述
在这里插入图片描述
界面布局首先在最外层使用了单列的GridLayout,所有Widget将会由上往下放置,第一行放置歌曲名,第二行放軒三个元素:已播放时间,进度条,歌曲总时长,第三行放置歌曲的控制按钮。

这里要注意的是第二行采有的是BorderLayout,使已播放时间、歌曲总时长这两个Label分别置于左右两端,而余下的空间全部由ProgressBar占据。

第三行的主要按键功能实现的说明如下:

  • 播放与暂停功能
    这里主要是要将beep.Ctrlbeep.StreamSeekCloser用好,beep.Ctrl中的Paused变量表示是否暂停流,appui.curSong.Format.SampleRate.D(appui.curSong.Streamer.Position()).Round(time.Second).String()可以计算得到当前已经播放的时长。

  • 上一首/下一首功能
    这两个按钮的功能类似,主要是通过通道使AppGUI.PlaySong()启动的两个协程退出,同时还原相关参数状态;这里要注意的是MusicEntry.Play()结束时要调用speaker.Clear(),将流清空,否则下次调用speaker.Init()会形成死锁。

主要代码如下:

  • gui.go
package musicplayer

import (
	"fmt"
	"fyne.io/fyne"
	"fyne.io/fyne/app"
	"fyne.io/fyne/layout"
	"fyne.io/fyne/widget"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"time"
)

type AppGUI struct {
	baseDir           string              // 文件目录
	songs             []string            // 歌曲集合
	curSong           *MusicEntry         // 当前歌曲
	currentSongName   *widget.Label       // 当前曲目名称
	progress          *widget.ProgressBar // 播放进度
	consumedTime      *widget.Label       // 已用时间
	remainedTime      *widget.Label       // 剩余时间
	playBtn           *widget.Button      // 播放
	paused            bool                // 是否暂停标志
	nextBtn           *widget.Button      // 下一首
	preBtn            *widget.Button      // 上一首
	forwardBtn        *widget.Button      // 快进
	backwardBtn       *widget.Button      // 快退
	songIdx           int                 // 当前歌曲序号
	appDir            string              // 程序运行目录
	newSongFlag       bool                // 新的一首歌
	endUpdateProgress chan bool           // 停止更新进度条
}

func (appui *AppGUI) Run() {


	a := app.New()

	appui.newSongFlag = true
	appui.songIdx = 0
	re, _ := os.Executable()
	appui.appDir = filepath.Dir(re)
	fmt.Println("pwd:" + appui.appDir)
	appui.songs = make([]string, 0, 10)
	appui.endUpdateProgress = make(chan bool)
	appui.baseDir = "music_res"
	appui.currentSongName = widget.NewLabel("--")
	appui.progress = widget.NewProgressBar()
	appui.consumedTime = widget.NewLabel("0")
	appui.remainedTime = widget.NewLabel("0")
	appui.playBtn = widget.NewButton("Play", appui.PlaySong)
	appui.paused = true
	appui.nextBtn = widget.NewButton("Next", appui.NextSong)
	appui.preBtn = widget.NewButton("Prev", appui.PrevSong)
	appui.forwardBtn = widget.NewButton("Forward", nil)
	appui.backwardBtn = widget.NewButton("Backward", nil)
	appui.progress.Min = 0
	appui.progress.Max = 100
	appui.progress.SetValue(0)

	files, _ := ioutil.ReadDir(appui.appDir + "/" + appui.baseDir)
	for _, onefile := range files {
		if onefile.IsDir() {
			// do nothing
		} else {
			// 放入曲库
			postfix := path.Ext(onefile.Name())
			if postfix == ".mp3" {
				appui.songs = append(appui.songs, onefile.Name())
			}
		}
	}

	// 显示第一首歌的名字
	if len(appui.songs) != 0 {
		appui.currentSongName.SetText(appui.songs[0])
	}

	w := a.NewWindow("MP3播放器")
	w.SetTitle("MP3 Player")

	w.SetContent(fyne.NewContainerWithLayout(layout.NewGridLayout(1),
		appui.currentSongName,
		fyne.NewContainerWithLayout(layout.NewBorderLayout(nil, nil, appui.consumedTime, appui.remainedTime),
			appui.consumedTime,
			appui.remainedTime,
			appui.progress,
		),
		fyne.NewContainerWithLayout(layout.NewGridLayout(5),
			appui.preBtn,
			appui.backwardBtn,
			appui.playBtn,
			appui.forwardBtn,
			appui.nextBtn,
		),
	))

	appui.curSong = &MusicEntry{}
	if len(appui.songs) != 0 {
		appui.curSong.Source = appui.appDir + "/" + appui.baseDir + "/" + appui.songs[appui.songIdx]
	}

	w.ShowAndRun()
}

// hooks
func (appui *AppGUI) PlaySong() {
	if appui.newSongFlag {
		appui.newSongFlag = false
		appui.curSong.Open()
		appui.remainedTime.SetText(appui.curSong.Format.SampleRate.D(appui.curSong.Streamer.Len()).Round(time.Second).String())
		// 播放音乐
		go appui.curSong.Play()
		// 更新进度条
		go appui.UpdateProcess()
	}

	if appui.paused == true {
		appui.playBtn.SetText("Pause")
		appui.paused = false
		appui.curSong.paused <- false
	} else {
		appui.playBtn.SetText("Play")
		appui.paused = true
		appui.curSong.paused <- true
	}
}

func (appui *AppGUI) UpdateProcess() {
	appui.progress.Min = 0
	appui.progress.Max = float64(appui.curSong.Streamer.Len())
	for {
		select {
		case <-appui.endUpdateProgress:
			return
		case <-time.After(time.Second):
			appui.progress.SetValue(appui.curSong.progress)
			appui.consumedTime.SetText(appui.curSong.Format.SampleRate.D(appui.curSong.Streamer.Position()).Round(time.Second).String())
		}
	}
}

func (appui *AppGUI) NextSong() {
	appui.songIdx = appui.songIdx + 1
	if appui.songIdx >= len(appui.songs) {
		appui.songIdx = 0
	}
	appui.Reset()
}

func (appui *AppGUI) PrevSong() {
	appui.songIdx = appui.songIdx - 1
	if appui.songIdx < 0 {
		appui.songIdx = len(appui.songs) - 1
	}
	appui.Reset()
}

func (appui *AppGUI) Reset() {
	appui.currentSongName.SetText(appui.songs[appui.songIdx])
	appui.curSong.Source = appui.appDir + "/" + appui.baseDir + "/" + appui.songs[appui.songIdx]
	appui.paused = true
	appui.playBtn.SetText("Play")
	if !appui.newSongFlag {
		appui.curSong.Stop()
		appui.endUpdateProgress <- true
	}
	appui.newSongFlag = true
}

  • mplayer.go
package musicplayer

import (
	"github.com/faiface/beep"
	"github.com/faiface/beep/mp3"
	"github.com/faiface/beep/speaker"
	"log"
	"os"
	"time"
)

type MusicEntry struct {
	Id         string                // 编号
	Name       string                // 歌名
	Artist     string                // 作者
	Source     string                // 位置
	Type       string                // 类型
	Filestream *os.File              // 文件流
	Format     beep.Format           // 文件信息
	Streamer   beep.StreamSeekCloser // 流信息
	done       chan bool             // 结束信号
	ctrl       *beep.Ctrl            // 控制器
	paused     chan bool             // 暂停标志
	progress   float64               // 进度值
}

func (me *MusicEntry) Open() {
	var err error
	me.Filestream, err = os.Open(me.Source)
	if err != nil {
		log.Fatal(err)
	}
	me.Streamer, me.Format, err = mp3.Decode(me.Filestream)
	if err != nil {
		log.Fatal(err)
	}
	speaker.Init(me.Format.SampleRate, me.Format.SampleRate.N(time.Second/10))
	me.done = make(chan bool)
	me.paused = make(chan bool)
	me.ctrl = &beep.Ctrl{Streamer: beep.Seq(me.Streamer, beep.Callback(func() {
		me.done <- true
	})), Paused: false}
}

func (me *MusicEntry) Play() {
	defer me.Streamer.Close()
	speaker.Play(me.ctrl)
	for {
		select {
		case  <-me.done:
			// 此处必须调用,否则下次Init会有死锁
			speaker.Clear()
			return
		case value := <-me.paused:
			speaker.Lock()
			me.ctrl.Paused = value
			speaker.Unlock()
		case <-time.After(time.Second):
			speaker.Lock()
			me.progress = float64(me.Streamer.Position())
			speaker.Unlock()
		}
	}
}

func (me *MusicEntry) Stop() {
	select {
	case me.done <- true:
	default:
	}
}

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值