mirror of
https://github.com/issadarkthing/gomu.git
synced 2025-04-26 13:49:21 +08:00
182 lines
2.7 KiB
Go
182 lines
2.7 KiB
Go
![]() |
package main
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"net/http"
|
||
|
"os"
|
||
|
"path"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"github.com/faiface/beep"
|
||
|
"github.com/faiface/beep/mp3"
|
||
|
"github.com/faiface/beep/speaker"
|
||
|
)
|
||
|
|
||
|
type Song struct {
|
||
|
name string
|
||
|
path string
|
||
|
position string
|
||
|
format string
|
||
|
}
|
||
|
|
||
|
type Player struct {
|
||
|
list []string
|
||
|
IsRunning bool
|
||
|
hasInit bool
|
||
|
ctrl *beep.Ctrl
|
||
|
current string
|
||
|
format *beep.Format
|
||
|
position string
|
||
|
currentSong Song
|
||
|
}
|
||
|
|
||
|
func Init(paths []string) (*Player, error) {
|
||
|
|
||
|
p := &Player{list: paths, IsRunning: false, hasInit: false}
|
||
|
|
||
|
if len(paths) == 0 {
|
||
|
return nil, errors.New("Cannot play with empty list")
|
||
|
}
|
||
|
|
||
|
return p, nil
|
||
|
|
||
|
}
|
||
|
|
||
|
func (p *Player) Push(song string) {
|
||
|
p.list = append(p.list, song)
|
||
|
}
|
||
|
|
||
|
func (p *Player) Pop() (string, error) {
|
||
|
|
||
|
if len(p.list) == 0 {
|
||
|
return "", errors.New("Empty list")
|
||
|
}
|
||
|
a := p.list[0]
|
||
|
p.list = p.list[1:]
|
||
|
p.current = a
|
||
|
|
||
|
return a, nil
|
||
|
}
|
||
|
|
||
|
func (p *Player) Run() error {
|
||
|
|
||
|
first, err := p.Pop()
|
||
|
|
||
|
if err != nil {
|
||
|
p.IsRunning = false
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
f, err := os.Open(first)
|
||
|
|
||
|
fformat, err := GetFileContentType(f)
|
||
|
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
song := &Song{name: GetName(f.Name()), path: first, format: fformat}
|
||
|
p.currentSong = *song
|
||
|
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
defer f.Close()
|
||
|
|
||
|
streamer, format, err := mp3.Decode(f)
|
||
|
|
||
|
p.format = &format
|
||
|
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
defer streamer.Close()
|
||
|
|
||
|
if !p.hasInit {
|
||
|
speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
|
||
|
p.hasInit = true
|
||
|
}
|
||
|
|
||
|
done := make(chan bool)
|
||
|
|
||
|
sstreamer := beep.Seq(streamer, beep.Callback(func() {
|
||
|
done <- true
|
||
|
close(done)
|
||
|
}))
|
||
|
|
||
|
p.ctrl = &beep.Ctrl{Streamer: sstreamer, Paused: false}
|
||
|
|
||
|
speaker.Play(p.ctrl)
|
||
|
position := func() string {
|
||
|
return format.SampleRate.D(streamer.Position()).Round(time.Second).String()
|
||
|
}
|
||
|
|
||
|
p.position = position()
|
||
|
|
||
|
p.IsRunning = true
|
||
|
|
||
|
for {
|
||
|
select {
|
||
|
case <-done:
|
||
|
p.position = ""
|
||
|
p.current = ""
|
||
|
if len(p.list) != 0 {
|
||
|
go p.Run()
|
||
|
} else {
|
||
|
p.IsRunning = false
|
||
|
p.format = nil
|
||
|
}
|
||
|
goto next
|
||
|
case <-time.After(time.Second):
|
||
|
speaker.Lock()
|
||
|
p.position = position()
|
||
|
speaker.Unlock()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
next:
|
||
|
|
||
|
return nil
|
||
|
|
||
|
}
|
||
|
|
||
|
func (p *Player) Pause() {
|
||
|
speaker.Lock()
|
||
|
p.ctrl.Paused = true
|
||
|
p.IsRunning = false
|
||
|
speaker.Unlock()
|
||
|
}
|
||
|
|
||
|
func (p *Player) Play() {
|
||
|
speaker.Lock()
|
||
|
p.ctrl.Paused = false
|
||
|
p.IsRunning = true
|
||
|
speaker.Unlock()
|
||
|
}
|
||
|
|
||
|
func (p *Player) CurrentSong() Song {
|
||
|
return p.currentSong
|
||
|
}
|
||
|
|
||
|
|
||
|
func GetFileContentType(out *os.File) (string, error) {
|
||
|
|
||
|
buffer := make([]byte, 512)
|
||
|
|
||
|
_, err := out.Read(buffer)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
|
||
|
contentType := http.DetectContentType(buffer)
|
||
|
|
||
|
return strings.SplitAfter(contentType, "/")[1], nil
|
||
|
}
|
||
|
|
||
|
func GetName(fn string) string {
|
||
|
return strings.TrimSuffix(fn, path.Ext(fn))
|
||
|
}
|