288 lines
5.3 KiB
Go
288 lines
5.3 KiB
Go
package main
|
|
|
|
import (
|
|
"container/list"
|
|
"context"
|
|
"dndmusicbot/reisenstreamer"
|
|
"encoding/json"
|
|
"errors"
|
|
"log"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/avast/retry-go/v4"
|
|
"github.com/google/uuid"
|
|
"github.com/gopxl/beep"
|
|
"github.com/julienschmidt/httprouter"
|
|
"github.com/kataras/go-events"
|
|
ytdl "github.com/kkdai/youtube/v2"
|
|
)
|
|
|
|
var player *Player
|
|
|
|
func init() {
|
|
player = new(Player)
|
|
player.playlist = list.New()
|
|
}
|
|
|
|
type Player struct {
|
|
sync.RWMutex
|
|
|
|
current *list.Element
|
|
playlist *list.List
|
|
PlayList *ytdl.Playlist
|
|
PlaylistID uuid.UUID
|
|
Playing bool
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
}
|
|
|
|
type PlayerItem struct {
|
|
Artist string
|
|
Title string
|
|
ID string
|
|
Uri string
|
|
Position time.Duration
|
|
Duration time.Duration
|
|
sr beep.SampleRate
|
|
streamer *reisenstreamer.Streamer
|
|
fn beep.StreamerFunc
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
Loaded bool
|
|
Idx int
|
|
}
|
|
|
|
func (i *PlayerItem) Load() error {
|
|
if i.streamer != nil {
|
|
return nil
|
|
}
|
|
|
|
i.ctx, i.cancel = context.WithCancel(context.Background())
|
|
stream, err := retry.DoWithData(func() (*reisenstreamer.Streamer, error) {
|
|
out := reisenstreamer.New(i.ctx, i.Uri)
|
|
if i.ctx.Err() != nil {
|
|
return nil, retry.Unrecoverable(i.ctx.Err())
|
|
}
|
|
if out == nil {
|
|
return nil, errors.New("streamer is nil")
|
|
}
|
|
return out, nil
|
|
})
|
|
|
|
if err != nil {
|
|
log.Println(err)
|
|
return err
|
|
}
|
|
|
|
i.streamer = stream
|
|
i.fn = stream.StreamerFunc()
|
|
i.sr = beep.SampleRate(stream.SampleRate)
|
|
i.Loaded = true
|
|
|
|
return nil
|
|
}
|
|
|
|
func (i *PlayerItem) Unload() {
|
|
i.Loaded = false
|
|
i.Position = 0
|
|
|
|
if i.cancel != nil {
|
|
i.cancel()
|
|
}
|
|
|
|
if i.streamer != nil {
|
|
i.streamer.Close()
|
|
}
|
|
|
|
i.fn = nil
|
|
i.streamer = nil
|
|
}
|
|
|
|
// TODO use context here to cancel this if someone loads two lists..
|
|
func (p *Player) LoadPlaylist(id uuid.UUID, playlist *ytdl.Playlist) {
|
|
if p.cancel != nil {
|
|
p.cancel()
|
|
}
|
|
|
|
if p.Playing {
|
|
p.Stop()
|
|
}
|
|
|
|
p.PlaylistID = id
|
|
p.PlayList = playlist
|
|
|
|
p.ctx, p.cancel = context.WithCancel(context.Background())
|
|
for i, info := range playlist.Videos {
|
|
select {
|
|
case <-p.ctx.Done():
|
|
log.Printf("LoadPlaylist cancelled: %v\n", p.ctx.Err())
|
|
return
|
|
default:
|
|
item := new(PlayerItem)
|
|
item.Artist = info.Author
|
|
item.Title = info.Title
|
|
item.Duration = info.Duration
|
|
item.ID = info.ID
|
|
item.Uri = "http://localhost:" + config.GetString("web.port") + "/youtube/" + info.ID
|
|
item.Idx = i + 1
|
|
p.playlist.PushBack(item)
|
|
}
|
|
}
|
|
|
|
p.Next()
|
|
}
|
|
|
|
func (p *Player) Stop() {
|
|
if !p.Playing {
|
|
return
|
|
}
|
|
|
|
p.Playing = false
|
|
p.PlaylistID = uuid.UUID{}
|
|
app.events.Emit(events.EventName("player"))
|
|
|
|
if p.current != nil {
|
|
p.current.Value.(*PlayerItem).cancel()
|
|
p.NextValue().Value.(*PlayerItem).cancel()
|
|
}
|
|
|
|
p.current = nil
|
|
|
|
p.playlist = p.playlist.Init()
|
|
}
|
|
|
|
func (p *Player) Play(idx int) {
|
|
var i int
|
|
|
|
var e *list.Element
|
|
|
|
for e = p.playlist.Front(); e != nil; e = e.Next() {
|
|
if idx == i {
|
|
break
|
|
}
|
|
i++
|
|
}
|
|
|
|
if e != nil {
|
|
e.Value.(*PlayerItem).Load()
|
|
cur := p.current
|
|
next := p.NextValue()
|
|
|
|
p.current = e
|
|
p.NextValue().Value.(*PlayerItem).Load()
|
|
|
|
app.events.Emit(events.EventName("player"))
|
|
|
|
cur.Value.(*PlayerItem).Unload()
|
|
next.Value.(*PlayerItem).Unload()
|
|
}
|
|
}
|
|
|
|
func (p *Player) NextValue() *list.Element {
|
|
switch {
|
|
case p.current == nil:
|
|
fallthrough
|
|
case p.current.Next() == nil:
|
|
return p.playlist.Front()
|
|
default:
|
|
return p.current.Next()
|
|
}
|
|
}
|
|
|
|
func (p *Player) PrevValue() *list.Element {
|
|
if p.current.Prev() != nil {
|
|
return p.current.Prev()
|
|
} else {
|
|
return p.playlist.Back()
|
|
}
|
|
}
|
|
|
|
func (p *Player) Next() {
|
|
p.current = p.NextValue()
|
|
p.PrevValue().Value.(*PlayerItem).Unload()
|
|
|
|
p.Playing = true
|
|
app.events.Emit(events.EventName("player"))
|
|
|
|
item := p.current.Value.(*PlayerItem)
|
|
err := item.Load()
|
|
if err != nil {
|
|
log.Println(err)
|
|
return
|
|
}
|
|
|
|
// Preload next song.
|
|
next := p.NextValue()
|
|
err = next.Value.(*PlayerItem).Load()
|
|
if err != nil {
|
|
log.Println(err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (p *Player) Prev() {
|
|
p.current = p.PrevValue()
|
|
p.Playing = true
|
|
app.events.Emit(events.EventName("player"))
|
|
|
|
item := p.current.Value.(*PlayerItem)
|
|
err := item.Load()
|
|
if err != nil {
|
|
log.Println(err)
|
|
return
|
|
}
|
|
|
|
// Reload next song
|
|
next := p.NextValue().Value.(*PlayerItem)
|
|
next.Unload()
|
|
next.Load()
|
|
|
|
// Unload nextnext song.
|
|
if p.NextValue().Next() == nil {
|
|
p.playlist.Front().Value.(*PlayerItem).Unload()
|
|
} else {
|
|
p.NextValue().Next().Value.(*PlayerItem).Unload()
|
|
}
|
|
}
|
|
|
|
func (p *Player) Stream(samples [][2]float64) (n int, ok bool) {
|
|
if !p.Playing || p.current.Value.(*PlayerItem).streamer == nil || !p.current.Value.(*PlayerItem).streamer.Started {
|
|
for i := 0; i < len(samples); i++ {
|
|
samples[i] = [2]float64{}
|
|
}
|
|
return len(samples), true
|
|
}
|
|
|
|
item := p.current.Value.(*PlayerItem)
|
|
n, ok = item.fn.Stream(samples)
|
|
item.Position = item.sr.D(item.sr.N(item.Position) + n)
|
|
|
|
if !ok {
|
|
go p.Next()
|
|
ok = true
|
|
return
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (p *Player) Err() error {
|
|
return nil
|
|
}
|
|
|
|
func CurrentPlaylist(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
w.Header().Add("Content-Type", "application/json")
|
|
|
|
if player.PlayList == nil {
|
|
json.NewEncoder(w).Encode(nil)
|
|
return
|
|
}
|
|
|
|
err := json.NewEncoder(w).Encode(player.PlayList.Videos)
|
|
if err != nil {
|
|
http.Error(w, "Unable to get playlist. "+err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|