Audio player
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

361 lines
6.5 KiB

package main
import (
"fmt"
"io"
"log"
"math"
"os"
"path"
"strings"
"sync"
"time"
"github.com/faiface/beep"
"github.com/faiface/beep/effects"
"github.com/faiface/beep/flac"
"github.com/faiface/beep/mp3"
"github.com/faiface/beep/speaker"
"github.com/faiface/beep/vorbis"
"github.com/faiface/beep/wav"
)
const defaultBufferSize = 500 * time.Millisecond
var (
startingVolumeLevel float64
startingVolumeSilent bool
playingFileName string
playingFileInfo string
playingFileID int64
playingStreamer beep.StreamSeekCloser
playingFormat beep.Format
playingSampleRate beep.SampleRate
pauseNext bool
seekNext int
volume *effects.Volume
ctrl *beep.Ctrl
audioLock = new(sync.Mutex)
)
type audioFile struct {
File *os.File
Streamer beep.StreamSeekCloser
Format beep.Format
Metadata *Metadata
}
func openFile(filePath string, Metadata *Metadata) (*audioFile, error) {
f, err := os.Open(filePath)
if err != nil {
return nil, err
}
if Metadata == nil {
Metadata = readMetadata(f)
_, err = f.Seek(0, io.SeekStart)
if err != nil {
log.Fatal(err)
}
}
var (
streamer beep.StreamSeekCloser
format beep.Format
)
switch strings.ToLower(path.Ext(filePath)) {
case ".wav":
streamer, format, err = wav.Decode(f)
case ".mp3":
streamer, format, err = mp3.Decode(f)
case ".ogg", ".weba", ".webm":
streamer, format, err = vorbis.Decode(f)
case ".flac":
streamer, format, err = flac.Decode(f)
default:
err = fmt.Errorf("unsupported file format")
}
if err != nil {
return nil, err
}
a := audioFile{File: f, Streamer: streamer, Format: format, Metadata: Metadata}
return &a, nil
}
func play(audioFile *audioFile) {
audioLock.Lock()
defer audioLock.Unlock()
speaker.Lock()
if playingStreamer != nil {
playingStreamer.Close()
}
speaker.Unlock()
thisFileID := time.Now().UnixNano()
playingFileID = thisFileID
playingStreamer = audioFile.Streamer
playingFormat = audioFile.Format
playingFileName = audioFile.File.Name()
playingFileInfo = ""
if audioFile.Metadata.Title != "" {
playingFileInfo = audioFile.Metadata.Title
if audioFile.Metadata.Artist != "" {
playingFileInfo = audioFile.Metadata.Artist + " - " + playingFileInfo
}
}
if streamFdInt == -1 {
if audioFile.Format.SampleRate != playingSampleRate {
err := speaker.Init(audioFile.Format.SampleRate, audioFile.Format.SampleRate.N(bufferSize))
if err != nil {
log.Fatalf("failed to initialize audio device: %s", err)
}
playingSampleRate = audioFile.Format.SampleRate
} else {
speaker.Clear()
}
}
streamer := beep.Seq(audioFile.Streamer, beep.Callback(func() {
if playingFileID != thisFileID {
return
}
go nextTrack()
}))
speaker.Lock()
if volume != nil {
volume.Streamer = streamer
ctrl.Paused = false
} else {
volume = &effects.Volume{
Streamer: streamer,
Base: volumeBase,
Volume: startingVolumeLevel,
Silent: startingVolumeSilent,
}
ctrl = &beep.Ctrl{
Streamer: volume,
Paused: false,
}
}
if pauseNext {
ctrl.Paused = true
pauseNext = false
}
if seekNext != 0 {
_ = playingStreamer.Seek(seekNext)
seekNext = 0
}
speaker.Unlock()
if streamFdInt >= 0 {
if streamFd == nil {
streamFd = os.NewFile(uintptr(streamFdInt), "out")
go func() {
rateLimitedStreamer := newRateLimitedStreamer(ctrl, bufferSize/64)
for {
_ = wav.Encode(streamFd, rateLimitedStreamer, playingFormat)
time.Sleep(250 * time.Millisecond)
}
}()
}
} else {
speaker.Play(ctrl)
}
go app.QueueUpdateDraw(func() {
updateLists()
updateStatus()
})
}
func pause() {
audioLock.Lock()
defer audioLock.Unlock()
if ctrl == nil {
return
}
speaker.Lock()
ctrl.Paused = !ctrl.Paused
speaker.Unlock()
go app.QueueUpdateDraw(updateStatus)
}
func nextTrack() {
if queueList.GetCurrentItemIndex() < len(queueFiles)-1 {
queueNext()
entry := selectedQueueEntry()
audioFile, err := openFile(entry.RealPath, entry.Metadata)
if err != nil {
statusText = err.Error()
go func() {
time.Sleep(5 * time.Second)
statusText = ""
go app.QueueUpdateDraw(updateMain)
}()
go app.QueueUpdateDraw(updateMain)
return
}
queuePlaying = queueList.GetCurrentItemIndex()
play(audioFile)
go app.QueueUpdateDraw(updateQueue)
}
}
func skipPrevious() {
if offsetQueueEntry(-1) == nil {
return
}
queuePrevious()
go queueSelect(queueList.GetCurrentItemIndex())
}
func skipNext() {
if offsetQueueEntry(1) == nil {
return
}
queueNext()
go queueSelect(queueList.GetCurrentItemIndex())
}
func roundUnit(x, unit float64) float64 {
return math.Round(x/unit) * unit
}
func supportedFormat(filePath string) bool {
switch strings.ToLower(path.Ext(filePath)) {
case ".wav":
return true
case ".mp3":
return true
case ".ogg", ".weba":
return true
case ".flac":
return true
default:
return false
}
}
func fileFormat(fileName string) string {
switch strings.ToLower(path.Ext(fileName)) {
case ".wav":
return "WAV"
case ".mp3":
return "MP3"
case ".ogg", ".weba":
return "OGG"
case ".flac":
return "FLAC"
default:
return "?"
}
}
func adjustVolume(adjustment float64) {
audioLock.Lock()
defer audioLock.Unlock()
if volume == nil {
startingVolumeLevel += adjustment
startingVolumeUpdated()
go app.QueueUpdateDraw(updateStatus)
return
}
speaker.Lock()
volume.Volume += adjustment
volumeUpdated()
speaker.Unlock()
go app.QueueUpdateDraw(updateStatus)
}
func setVolume(vol float64) {
audioLock.Lock()
defer audioLock.Unlock()
if volume == nil {
startingVolumeLevel = vol
startingVolumeUpdated()
go app.QueueUpdateDraw(updateStatus)
return
}
speaker.Lock()
volume.Volume = vol
volumeUpdated()
speaker.Unlock()
go app.QueueUpdateDraw(updateStatus)
}
func decreaseVolume() {
adjustVolume(-0.5)
}
func increaseVolume() {
adjustVolume(0.5)
}
func volumeUpdated() {
if volume.Volume <= -7.5 {
volume.Volume = -7.5
volume.Silent = true
} else {
volume.Silent = false
if volume.Volume > 0 {
volume.Volume = 0
}
}
}
func startingVolumeUpdated() {
if startingVolumeLevel <= -7.5 {
startingVolumeLevel = -7.5
startingVolumeSilent = true
} else {
startingVolumeSilent = false
if startingVolumeLevel > 0 {
startingVolumeLevel = 0
}
}
}
func toggleMute() {
audioLock.Lock()
defer audioLock.Unlock()
if volume == nil {
startingVolumeSilent = !startingVolumeSilent
go app.QueueUpdateDraw(updateStatus)
return
}
speaker.Lock()
volume.Silent = !volume.Silent
speaker.Unlock()
go app.QueueUpdateDraw(updateStatus)
}