Compare commits
9 Commits
Author | SHA1 | Date |
---|---|---|
Stein Ivar Berghei | 9ce9a768b9 | |
Stein Ivar Berghei | d072f85d05 | |
Stein Ivar Berghei | d450fb5bf7 | |
Stein Ivar Berghei | f65a57964f | |
Stein Ivar Berghei | e4d87bf7d6 | |
Stein Ivar Berghei | 8aebf4cfe7 | |
steino | 7047dcc7c3 | |
Stein Ivar Berghei | ad8cbbd57d | |
steino | 2b01cd239a |
|
@ -16,3 +16,6 @@ ambiance
|
||||||
public/
|
public/
|
||||||
node_modules/
|
node_modules/
|
||||||
src/node_modules
|
src/node_modules
|
||||||
|
*.aac
|
||||||
|
*.opus
|
||||||
|
*.mp3
|
|
@ -1,16 +1,61 @@
|
||||||
FROM golang:1.21-bookworm as builder
|
FROM golang:1.21-bookworm as builder
|
||||||
|
|
||||||
|
ENV FFMPEG_VERSION=5.1.2
|
||||||
|
ENV MPD_VERSION=0.23.14
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get -y install \
|
||||||
|
libopus-dev libopusfile-dev \
|
||||||
|
meson g++ nasm \
|
||||||
|
libfmt-dev \
|
||||||
|
libpcre2-dev \
|
||||||
|
libmad0-dev libmpg123-dev libid3tag0-dev \
|
||||||
|
libflac-dev libvorbis-dev libopus-dev libogg-dev \
|
||||||
|
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
|
||||||
|
libsamplerate0-dev libsoxr-dev \
|
||||||
|
libcurl4-gnutls-dev \
|
||||||
|
libboost-dev \
|
||||||
|
zlib1g-dev
|
||||||
|
WORKDIR /tmp
|
||||||
|
RUN curl -LOs http://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.xz && tar xvf ffmpeg-${FFMPEG_VERSION}.tar.xz && \
|
||||||
|
cd ffmpeg-${FFMPEG_VERSION} && \
|
||||||
|
./configure \
|
||||||
|
--enable-version3 \
|
||||||
|
--enable-gpl \
|
||||||
|
--enable-nonfree \
|
||||||
|
--enable-small \
|
||||||
|
--enable-libvorbis \
|
||||||
|
--enable-libopus \
|
||||||
|
--enable-postproc \
|
||||||
|
--enable-openssl \
|
||||||
|
--disable-debug && \
|
||||||
|
make && \
|
||||||
|
make install
|
||||||
|
RUN curl -LOs https://www.musicpd.org/download/mpd/0.23/mpd-${MPD_VERSION}.tar.xz && tar xvf mpd-${MPD_VERSION}.tar.xz && \
|
||||||
|
cd mpd-${MPD_VERSION} && \
|
||||||
|
meson . output/release --buildtype=minsize -Db_ndebug=true && \
|
||||||
|
ninja -C output/release
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN apt-get update && apt-get -y install libopus-dev libopusfile-dev && \
|
RUN go build
|
||||||
go build
|
|
||||||
|
|
||||||
FROM debian:bookworm-slim
|
FROM debian:bookworm-slim
|
||||||
RUN apt-get update && apt-get -y install \
|
RUN apt-get update && apt-get -y install \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
libopus-dev libopusfile-dev \
|
libopus-dev libopusfile-dev \
|
||||||
mpd ffmpeg
|
libfmt-dev \
|
||||||
|
libpcre2-dev \
|
||||||
|
libmad0-dev libmpg123-dev libid3tag0-dev \
|
||||||
|
libflac-dev libvorbis-dev libopus-dev libogg-dev \
|
||||||
|
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
|
||||||
|
libsamplerate0-dev libsoxr-dev \
|
||||||
|
libcurl4-gnutls-dev \
|
||||||
|
curl && \
|
||||||
|
curl -L https://github.com/badaix/snapcast/releases/download/v0.27.0/snapclient_0.27.0-1_without-pulse_amd64.deb -o /tmp/snapcast.deb && \
|
||||||
|
curl -L http://ftp.no.debian.org/debian/pool/main/f/flac/libflac8_1.3.3-2+deb11u2_amd64.deb -o /tmp/libflac8.deb && \
|
||||||
|
apt -y install /tmp/snapcast.deb /tmp/libflac8.deb && rm -rf /tmp/*.deb
|
||||||
COPY --from=builder /src/dndmusicbot /app/
|
COPY --from=builder /src/dndmusicbot /app/
|
||||||
|
COPY --from=builder /tmp/mpd-0.23.14/output/release/mpd /usr/local/bin
|
||||||
|
COPY --from=builder /usr/local/bin/ffmpeg /usr/local/bin
|
||||||
|
COPY mp3 /app/mp3
|
||||||
ADD tmpl /app/tmpl
|
ADD tmpl /app/tmpl
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ENTRYPOINT [ "/app/dndmusicbot" ]
|
ENTRYPOINT [ "/app/dndmusicbot" ]
|
||||||
|
|
52
ambiance.go
52
ambiance.go
|
@ -69,20 +69,17 @@ func GetAmbiances() (amb []Ambiance, err error) {
|
||||||
func AddAmbiance(uri, title string) (Ambiance, error) {
|
func AddAmbiance(uri, title string) (Ambiance, error) {
|
||||||
var amb Ambiance
|
var amb Ambiance
|
||||||
|
|
||||||
msg := make(map[string]interface{})
|
ev := Event{"ambiance_add_start", map[string]string{
|
||||||
msg["event"] = "ambiance_add_start"
|
"name": title,
|
||||||
data := make(map[string]string)
|
}}
|
||||||
data["name"] = title
|
|
||||||
msg["payload"] = data
|
go ws.SendEvent(ev)
|
||||||
ws_msg <- msg
|
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
msg = make(map[string]interface{})
|
ev = Event{"ambiance_add_finish", map[string]string{
|
||||||
msg["event"] = "ambiance_add_finish"
|
"name": title,
|
||||||
data = make(map[string]string)
|
}}
|
||||||
data["name"] = title
|
go ws.SendEvent(ev)
|
||||||
msg["payload"] = data
|
|
||||||
ws_msg <- msg
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
tmpfile, err := exec.Command("mktemp", "/tmp/dnd_XXXXXXXXXXXX.opus").Output()
|
tmpfile, err := exec.Command("mktemp", "/tmp/dnd_XXXXXXXXXXXX.opus").Output()
|
||||||
|
@ -134,16 +131,13 @@ func AddAmbiance(uri, title string) (Ambiance, error) {
|
||||||
|
|
||||||
log.Printf("Start ffmpeg to extract audio to %s", string(tmpfile))
|
log.Printf("Start ffmpeg to extract audio to %s", string(tmpfile))
|
||||||
|
|
||||||
msg = make(map[string]interface{})
|
ev = Event{"ambiance_encode_start", map[string]string{
|
||||||
msg["event"] = "ambiance_encode_start"
|
"name": title,
|
||||||
data = make(map[string]string)
|
}}
|
||||||
data["name"] = title
|
|
||||||
msg["payload"] = data
|
|
||||||
ws_msg <- msg
|
|
||||||
|
|
||||||
msg = make(map[string]interface{})
|
go ws.SendEvent(ev)
|
||||||
msg["event"] = "ambiance_encode_progress"
|
|
||||||
data = make(map[string]string)
|
data := make(map[string]string)
|
||||||
data["name"] = title
|
data["name"] = title
|
||||||
|
|
||||||
scanner := bufio.NewScanner(ffprogress)
|
scanner := bufio.NewScanner(ffprogress)
|
||||||
|
@ -162,8 +156,7 @@ func AddAmbiance(uri, title string) (Ambiance, error) {
|
||||||
data["percent"] = percent
|
data["percent"] = percent
|
||||||
}
|
}
|
||||||
|
|
||||||
msg["payload"] = data
|
go ws.SendEvent(Event{"ambiance_encode_progress", data})
|
||||||
ws_msg <- msg
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,15 +169,14 @@ func AddAmbiance(uri, title string) (Ambiance, error) {
|
||||||
return amb, err
|
return amb, err
|
||||||
}
|
}
|
||||||
|
|
||||||
msg = make(map[string]interface{})
|
ev = Event{"ambiance_encode_complete", map[string]string{
|
||||||
msg["event"] = "ambiance_encode_complete"
|
"name": title,
|
||||||
data = make(map[string]string)
|
}}
|
||||||
data["name"] = title
|
|
||||||
msg["payload"] = data
|
go ws.SendEvent(ev)
|
||||||
ws_msg <- msg
|
|
||||||
|
|
||||||
id := uuid.New()
|
id := uuid.New()
|
||||||
fn := filepath.Join(config.GetString("ambiance.path"), fmt.Sprintf("%s.opus", id.String()))
|
fn := filepath.Join(config.GetString("ambiance.path"), fmt.Sprintf("%s.aac", id.String()))
|
||||||
|
|
||||||
log.Printf("Moving to %s", fn)
|
log.Printf("Moving to %s", fn)
|
||||||
|
|
||||||
|
|
15
bot.go
15
bot.go
|
@ -5,6 +5,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
@ -69,10 +70,21 @@ type App struct {
|
||||||
plcancel context.CancelFunc
|
plcancel context.CancelFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cwd string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ex, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
cwd = filepath.Dir(ex)
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
bfs := afero.NewBasePathFs(afero.NewOsFs(), "cache")
|
bfs := afero.NewBasePathFs(afero.NewOsFs(), "cache")
|
||||||
app.cache = filecache.NewCache(bfs, 1*time.Hour, "")
|
app.cache = filecache.NewCache(bfs, 1*time.Hour, "")
|
||||||
|
|
||||||
|
prune := time.NewTicker(15 * time.Minute)
|
||||||
ticker := time.NewTicker(300 * time.Millisecond)
|
ticker := time.NewTicker(300 * time.Millisecond)
|
||||||
|
|
||||||
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
|
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
|
||||||
|
@ -85,11 +97,14 @@ func main() {
|
||||||
app.mpdw.Close()
|
app.mpdw.Close()
|
||||||
app.mpdc()
|
app.mpdc()
|
||||||
app.voice.Leave(ctx)
|
app.voice.Leave(ctx)
|
||||||
|
app.cache.Prune(false)
|
||||||
dgvc()
|
dgvc()
|
||||||
app.discord.Close()
|
app.discord.Close()
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
app.events.Emit("tick")
|
app.events.Emit("tick")
|
||||||
|
case <-prune.C:
|
||||||
|
app.cache.Prune(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ func init() {
|
||||||
app.discord = s
|
app.discord = s
|
||||||
app.voice = v
|
app.voice = v
|
||||||
|
|
||||||
discordspeaker.Init(v)
|
discordspeaker.Init(v, config.GetInt("discord.bitrate"))
|
||||||
|
|
||||||
log.Println("discord.go done.")
|
log.Println("discord.go done.")
|
||||||
}
|
}
|
||||||
|
|
176
events.go
176
events.go
|
@ -2,19 +2,16 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"dndmusicbot/opus"
|
|
||||||
discordspeaker "dndmusicbot/speaker"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/gopxl/beep"
|
|
||||||
"github.com/gopxl/beep/effects"
|
|
||||||
"github.com/kataras/go-events"
|
"github.com/kataras/go-events"
|
||||||
"github.com/steino/gompd/v2/mpd"
|
"github.com/steino/gompd/v2/mpd"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
|
@ -151,74 +148,70 @@ func (app *App) volset(payload ...interface{}) {
|
||||||
amb_volume.Volume = vol
|
amb_volume.Volume = vol
|
||||||
}
|
}
|
||||||
|
|
||||||
app.sendVolume()
|
ev := Event{"volume", map[string]float64{
|
||||||
|
"playlist": pl_volume.Volume,
|
||||||
|
"ambiance": amb_volume.Volume,
|
||||||
|
}}
|
||||||
|
|
||||||
|
go ws.SendEvent(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) sendVolume() {
|
func (app *App) songInfoEvent(event string) (ev Event, err error) {
|
||||||
msg := make(map[string]interface{})
|
ev.Event = event
|
||||||
out := make(map[string]float64)
|
|
||||||
msg["event"] = "volume"
|
|
||||||
out["playlist"] = pl_volume.Volume
|
|
||||||
out["ambiance"] = amb_volume.Volume
|
|
||||||
msg["payload"] = out
|
|
||||||
ws_msg <- msg
|
|
||||||
}
|
|
||||||
|
|
||||||
func (app *App) songInfoEvent(event string) map[string]interface{} {
|
|
||||||
msg := make(map[string]interface{})
|
|
||||||
msg["event"] = event
|
|
||||||
status, err := app.mpd.Status()
|
status, err := app.mpd.Status()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
return
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cur, err := app.mpd.CurrentSong()
|
cur, err := app.mpd.CurrentSong()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
return
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
info := new(SongInfo)
|
info := new(SongInfo)
|
||||||
|
|
||||||
if status["state"] != "play" {
|
if status["state"] != "play" {
|
||||||
info.Pause = true
|
info.Pause = true
|
||||||
msg["payload"] = info
|
ev.Payload = info
|
||||||
return msg
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
duration, ok := status["duration"]
|
duration, ok := status["duration"]
|
||||||
if ok && duration != "" {
|
if ok && duration != "" {
|
||||||
slen, err := strconv.ParseFloat(duration, 64)
|
var slen float64
|
||||||
|
slen, err = strconv.ParseFloat(duration, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
info.Length = time.Duration(slen * float64(time.Second)).Milliseconds()
|
info.Length = time.Duration(slen * float64(time.Second)).Milliseconds()
|
||||||
}
|
}
|
||||||
|
|
||||||
elapsed, ok := status["elapsed"]
|
elapsed, ok := status["elapsed"]
|
||||||
if ok && elapsed != "" {
|
if ok && elapsed != "" {
|
||||||
spos, err := strconv.ParseFloat(elapsed, 64)
|
var spos float64
|
||||||
|
spos, err = strconv.ParseFloat(elapsed, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
info.Position = time.Duration(spos * float64(time.Second)).Milliseconds()
|
info.Position = time.Duration(spos * float64(time.Second)).Milliseconds()
|
||||||
}
|
}
|
||||||
|
|
||||||
album, ok := cur["Album"]
|
album, ok := cur["Album"]
|
||||||
if ok {
|
if ok {
|
||||||
plid, err := uuid.ParseBytes([]byte(album))
|
var plid uuid.UUID
|
||||||
|
plid, err = uuid.ParseBytes([]byte(album))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
var pl *Playlist
|
||||||
pl, err := app.GetPlaylist(plid)
|
pl, err = app.GetPlaylist(plid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
info.Playlist = pl.Id
|
info.Playlist = pl.Id
|
||||||
|
@ -240,9 +233,9 @@ func (app *App) songInfoEvent(event string) map[string]interface{} {
|
||||||
info.Song = location
|
info.Song = location
|
||||||
}
|
}
|
||||||
|
|
||||||
msg["payload"] = *info
|
ev.Payload = *info
|
||||||
|
|
||||||
return msg
|
return ev, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) ambiancePlay(payload ...interface{}) {
|
func (app *App) ambiancePlay(payload ...interface{}) {
|
||||||
|
@ -271,53 +264,56 @@ func (app *App) ambiancePlay(payload ...interface{}) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.Open(amb.Path)
|
err = app.mpd.Partition("ambiance")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
play, err := opus.New(f)
|
fmt.Println(filepath.Join(cwd, amb.Path))
|
||||||
|
err = app.mpd.Add(filepath.Join(cwd, amb.Path))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
loop := beep.Loop(-1, play)
|
err = app.mpd.Play(0)
|
||||||
|
if err != nil {
|
||||||
volume := &effects.Volume{
|
log.Println(err)
|
||||||
Streamer: loop,
|
return
|
||||||
Base: 2,
|
|
||||||
Volume: -2.5,
|
|
||||||
Silent: false,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
discordspeaker.Lock()
|
err = app.mpd.Partition("default")
|
||||||
app.ambiance.Clear()
|
if err != nil {
|
||||||
app.ambiance.Add(volume)
|
log.Fatal(err)
|
||||||
discordspeaker.Unlock()
|
}
|
||||||
|
|
||||||
msg := make(map[string]interface{})
|
|
||||||
out := make(map[string]interface{})
|
|
||||||
|
|
||||||
app.curamb = amb
|
app.curamb = amb
|
||||||
|
|
||||||
msg["event"] = "ambiance_play"
|
ev := Event{"ambiance_play", map[string]string{
|
||||||
out["id"] = id
|
"id": id,
|
||||||
msg["payload"] = out
|
}}
|
||||||
ws_msg <- msg
|
|
||||||
|
go ws.SendEvent(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) ambianceStop(payload ...interface{}) {
|
func (app *App) ambianceStop(payload ...interface{}) {
|
||||||
log.Println("Stopping ambiance")
|
log.Println("Stopping ambiance")
|
||||||
discordspeaker.Lock()
|
|
||||||
app.ambiance.Clear()
|
|
||||||
discordspeaker.Unlock()
|
|
||||||
|
|
||||||
msg := make(map[string]interface{})
|
err := app.mpd.Partition("ambiance")
|
||||||
msg["event"] = "ambiance_stop"
|
if err != nil {
|
||||||
ws_msg <- msg
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.mpd.Stop()
|
||||||
|
app.mpd.Clear()
|
||||||
|
|
||||||
|
err = app.mpd.Partition("default")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go ws.SendEvent(Event{"ambiance_stop", nil})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) ambianceAdd(payload ...interface{}) {
|
func (app *App) ambianceAdd(payload ...interface{}) {
|
||||||
|
@ -354,13 +350,12 @@ func (app *App) ambianceAdd(payload ...interface{}) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := make(map[string]interface{})
|
ev := Event{"ambiance_add", map[string]string{
|
||||||
out := make(map[string]interface{})
|
"title": amb.Title,
|
||||||
msg["event"] = "ambiance_add"
|
"id": amb.Id,
|
||||||
out["title"] = amb.Title
|
}}
|
||||||
out["id"] = amb.Id
|
|
||||||
msg["payload"] = out
|
go ws.SendEvent(ev)
|
||||||
ws_msg <- msg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) songPosition(payload ...interface{}) {
|
func (app *App) songPosition(payload ...interface{}) {
|
||||||
|
@ -375,18 +370,16 @@ func (app *App) songPosition(payload ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Do(func() {
|
l.Do(func() {
|
||||||
msg := make(map[string]interface{})
|
|
||||||
out := make(map[string]interface{})
|
|
||||||
|
|
||||||
slen, _ := strconv.ParseFloat(status["duration"], 64)
|
slen, _ := strconv.ParseFloat(status["duration"], 64)
|
||||||
spos, _ := strconv.ParseFloat(status["elapsed"], 64)
|
spos, _ := strconv.ParseFloat(status["elapsed"], 64)
|
||||||
|
|
||||||
msg["event"] = "song_position"
|
ev := Event{"song_position", map[string]int64{
|
||||||
out["len"] = time.Duration(slen * float64(time.Second)).Milliseconds()
|
"len": time.Duration(slen * float64(time.Second)).Milliseconds(),
|
||||||
out["position"] = time.Duration(spos * float64(time.Second)).Milliseconds()
|
"position": time.Duration(spos * float64(time.Second)).Milliseconds(),
|
||||||
|
}}
|
||||||
|
|
||||||
msg["payload"] = out
|
go ws.SendEvent(ev)
|
||||||
ws_msg <- msg
|
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -394,10 +387,12 @@ func (app *App) songPosition(payload ...interface{}) {
|
||||||
func (app *App) songInfo(payload ...interface{}) {
|
func (app *App) songInfo(payload ...interface{}) {
|
||||||
log.Println("song_info event received")
|
log.Println("song_info event received")
|
||||||
|
|
||||||
msg := app.songInfoEvent("song_info")
|
ev, err := app.songInfoEvent("song_info")
|
||||||
if msg != nil {
|
if err != nil {
|
||||||
ws_msg <- msg
|
log.Println(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
go ws.SendEvent(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) stop(payload ...interface{}) {
|
func (app *App) stop(payload ...interface{}) {
|
||||||
|
@ -410,10 +405,7 @@ func (app *App) stop(payload ...interface{}) {
|
||||||
app.mpd.Stop()
|
app.mpd.Stop()
|
||||||
app.plmutex.Unlock()
|
app.plmutex.Unlock()
|
||||||
|
|
||||||
msg := make(map[string]interface{})
|
go ws.SendEvent(Event{"stop", nil})
|
||||||
msg["event"] = "stop"
|
|
||||||
|
|
||||||
ws_msg <- msg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) prevSong(payload ...interface{}) {
|
func (app *App) prevSong(payload ...interface{}) {
|
||||||
|
@ -487,12 +479,12 @@ func (app *App) addPlaylist(payload ...interface{}) {
|
||||||
log.Println("Error getting youtube playlist info,", plid)
|
log.Println("Error getting youtube playlist info,", plid)
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := make(map[string]interface{})
|
ev := Event{"new_playlist", map[string]string{
|
||||||
|
"url": id.String(),
|
||||||
|
"title": pltitle,
|
||||||
|
}}
|
||||||
|
|
||||||
msg["event"] = "new_playlist"
|
go ws.SendEvent(ev)
|
||||||
msg["payload"] = map[string]string{"url": id.String(), "title": pltitle}
|
|
||||||
|
|
||||||
ws_msg <- msg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) loadPlaylist(payload ...interface{}) {
|
func (app *App) loadPlaylist(payload ...interface{}) {
|
||||||
|
@ -554,12 +546,6 @@ func (app *App) loadPlaylist(payload ...interface{}) {
|
||||||
for _, ytinfo := range list.Videos {
|
for _, ytinfo := range list.Videos {
|
||||||
log.Printf("Adding %s (%s - %s)", ytinfo.ID, ytinfo.Author, ytinfo.Title)
|
log.Printf("Adding %s (%s - %s)", ytinfo.ID, ytinfo.Author, ytinfo.Title)
|
||||||
|
|
||||||
vinfo, err := app.youtube.GetVideoFromID(ytinfo.ID)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run as a local function so we can defer the mutex unlock incase one of these errors.
|
// Run as a local function so we can defer the mutex unlock incase one of these errors.
|
||||||
ok := func() (ok bool) {
|
ok := func() (ok bool) {
|
||||||
app.plmutex.Lock()
|
app.plmutex.Lock()
|
||||||
|
@ -572,7 +558,7 @@ func (app *App) loadPlaylist(payload ...interface{}) {
|
||||||
ok = true
|
ok = true
|
||||||
|
|
||||||
// state:stop
|
// state:stop
|
||||||
songid, err := app.mpd.AddID(vinfo.GetHLSPlaylist("234"), 0)
|
songid, err := app.mpd.AddID("http://localhost:"+config.GetString("web.port")+"/youtube/"+ytinfo.ID, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
|
@ -608,6 +594,8 @@ func (app *App) loadPlaylist(payload ...interface{}) {
|
||||||
|
|
||||||
app.mpd.Play(-1)
|
app.mpd.Play(-1)
|
||||||
|
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
return
|
return
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
6
go.mod
6
go.mod
|
@ -24,10 +24,10 @@ require (
|
||||||
github.com/markbates/goth v1.76.1
|
github.com/markbates/goth v1.76.1
|
||||||
github.com/peterhellberg/link v1.2.0
|
github.com/peterhellberg/link v1.2.0
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
|
github.com/romantomjak/shoutcast v1.2.0
|
||||||
github.com/spf13/afero v1.9.5
|
github.com/spf13/afero v1.9.5
|
||||||
github.com/spf13/viper v1.16.0
|
github.com/spf13/viper v1.16.0
|
||||||
github.com/steino/gompd/v2 v2.3.1
|
github.com/steino/gompd/v2 v2.3.1
|
||||||
github.com/tejzpr/ordered-concurrently/v3 v3.0.1
|
|
||||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
|
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
|
||||||
golang.org/x/net v0.15.0
|
golang.org/x/net v0.15.0
|
||||||
golang.org/x/time v0.3.0
|
golang.org/x/time v0.3.0
|
||||||
|
@ -62,6 +62,7 @@ require (
|
||||||
github.com/gorilla/schema v1.2.0 // indirect
|
github.com/gorilla/schema v1.2.0 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
|
github.com/icza/bitio v1.1.0 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
||||||
github.com/jdkato/prose v1.2.1 // indirect
|
github.com/jdkato/prose v1.2.1 // indirect
|
||||||
|
@ -69,11 +70,12 @@ require (
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
|
github.com/mewkiz/flac v1.0.9 // indirect
|
||||||
|
github.com/mewkiz/pkg v0.0.0-20230226050401-4010bf0fec14 // indirect
|
||||||
github.com/mitchellh/hashstructure v1.1.0 // indirect
|
github.com/mitchellh/hashstructure v1.1.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/niklasfasching/go-org v1.6.5 // indirect
|
github.com/niklasfasching/go-org v1.6.5 // indirect
|
||||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||||
github.com/oov/audio v0.0.0-20171004131523-88a2be6dbe38 // indirect
|
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||||
github.com/rivo/uniseg v0.4.4 // indirect
|
github.com/rivo/uniseg v0.4.4 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||||
|
|
23
go.sum
23
go.sum
|
@ -139,6 +139,7 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
|
||||||
github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs=
|
github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs=
|
||||||
github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498=
|
github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498=
|
||||||
github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE=
|
github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE=
|
||||||
|
github.com/go-audio/wav v1.1.0/go.mod h1:mpe9qfwbScEbkd8uybLuIpTgHyrISw/OTuvjUW2iGtE=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
@ -264,6 +265,9 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
||||||
github.com/icza/bitio v1.0.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A=
|
github.com/icza/bitio v1.0.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A=
|
||||||
|
github.com/icza/bitio v1.1.0 h1:ysX4vtldjdi3Ygai5m1cWy4oLkhWTAi+SyO6HC8L9T0=
|
||||||
|
github.com/icza/bitio v1.1.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A=
|
||||||
|
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6 h1:8UsGZ2rr2ksmEru6lToqnXgA8Mz1DP11X4zSJ159C3k=
|
||||||
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA=
|
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA=
|
||||||
github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc=
|
github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc=
|
||||||
github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
|
github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
|
||||||
|
@ -282,6 +286,7 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
|
github.com/jszwec/csvutil v1.5.1/go.mod h1:Rpu7Uu9giO9subDyMCIQfHVDuLrcaC36UA4YcJjGBkg=
|
||||||
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||||
github.com/kataras/go-events v0.0.3 h1:o5YK53uURXtrlg7qE/vovxd/yKOJcLuFtPQbf1rYMC4=
|
github.com/kataras/go-events v0.0.3 h1:o5YK53uURXtrlg7qE/vovxd/yKOJcLuFtPQbf1rYMC4=
|
||||||
|
@ -324,7 +329,11 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
|
||||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mewkiz/flac v1.0.7/go.mod h1:yU74UH277dBUpqxPouHSQIar3G1X/QIclVbFahSd1pU=
|
github.com/mewkiz/flac v1.0.7/go.mod h1:yU74UH277dBUpqxPouHSQIar3G1X/QIclVbFahSd1pU=
|
||||||
|
github.com/mewkiz/flac v1.0.9 h1:/+wxe/2fA8YbD1kjJNhlVAyc6pWaX0XXZC4xCF5OtVY=
|
||||||
|
github.com/mewkiz/flac v1.0.9/go.mod h1:l7dt5uFY724eKVkHQtAJAQSkhpC3helU3RDxN0ESAqo=
|
||||||
github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2/go.mod h1:3E2FUC/qYUfM8+r9zAwpeHJzqRVVMIYnpzD/clwWxyA=
|
github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2/go.mod h1:3E2FUC/qYUfM8+r9zAwpeHJzqRVVMIYnpzD/clwWxyA=
|
||||||
|
github.com/mewkiz/pkg v0.0.0-20230226050401-4010bf0fec14 h1:tnAPMExbRERsyEYkmR1YjhTgDM0iqyiBYf8ojRXxdbA=
|
||||||
|
github.com/mewkiz/pkg v0.0.0-20230226050401-4010bf0fec14/go.mod h1:QYCFBiH5q6XTHEbWhR0uhR3M9qNPoD2CSQzr0g75kE4=
|
||||||
github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0=
|
github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0=
|
||||||
github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA=
|
github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
|
@ -340,8 +349,6 @@ github.com/niklasfasching/go-org v1.6.5 h1:5YAIqNTdl6lAOb7lD2AyQ1RuFGPVrAKvUexph
|
||||||
github.com/niklasfasching/go-org v1.6.5/go.mod h1:ybv0eGDnxylFUfFE+ySaQc734j/L3+/ChKZ/h63a2wM=
|
github.com/niklasfasching/go-org v1.6.5/go.mod h1:ybv0eGDnxylFUfFE+ySaQc734j/L3+/ChKZ/h63a2wM=
|
||||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||||
github.com/oov/audio v0.0.0-20171004131523-88a2be6dbe38 h1:4Upfs5rLQdx7KwBct3bmPYAhWsDDJdx660gYb7Lv9TQ=
|
|
||||||
github.com/oov/audio v0.0.0-20171004131523-88a2be6dbe38/go.mod h1:Xj06yMta9R1RSKiHmxL0Bo2TB8wiKVnMgA0KVopHHkk=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
github.com/peterhellberg/link v1.2.0 h1:UA5pg3Gp/E0F2WdX7GERiNrPQrM1K6CVJUUWfHa4t6c=
|
github.com/peterhellberg/link v1.2.0 h1:UA5pg3Gp/E0F2WdX7GERiNrPQrM1K6CVJUUWfHa4t6c=
|
||||||
|
@ -360,6 +367,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
|
github.com/romantomjak/shoutcast v1.2.0 h1:MrksaqAd2De3rwvhlkzY8eUEaRYmkvhy9OH0sqM/w74=
|
||||||
|
github.com/romantomjak/shoutcast v1.2.0/go.mod h1:U4Gem8Jbwbz/A2ml2DYOGK0tVpnfQ5sQpSY3UWXcZM4=
|
||||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
|
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
|
||||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||||
github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
|
github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
|
||||||
|
@ -398,8 +407,6 @@ github.com/tdewolff/parse/v2 v2.6.4 h1:KCkDvNUMof10e3QExio9OPZJT8SbdKojLBumw8YZy
|
||||||
github.com/tdewolff/parse/v2 v2.6.4/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs=
|
github.com/tdewolff/parse/v2 v2.6.4/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs=
|
||||||
github.com/tdewolff/test v1.0.7 h1:8Vs0142DmPFW/bQeHRP3MV19m1gvndjUb1sn8yy74LM=
|
github.com/tdewolff/test v1.0.7 h1:8Vs0142DmPFW/bQeHRP3MV19m1gvndjUb1sn8yy74LM=
|
||||||
github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||||
github.com/tejzpr/ordered-concurrently/v3 v3.0.1 h1:TLHtzlQEDshbmGveS8S+hxLw4s5u67aoJw5LLf+X2xY=
|
|
||||||
github.com/tejzpr/ordered-concurrently/v3 v3.0.1/go.mod h1:mu/neZ6AGXm5jdPc7PEgViYK3rkYNPvVCEm15Cx/iRI=
|
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
@ -443,8 +450,8 @@ golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZ
|
||||||
golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ=
|
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
|
||||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
@ -507,6 +514,7 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||||
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
@ -579,11 +587,13 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
@ -594,6 +604,7 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
|
69
mpd.go
69
mpd.go
|
@ -6,8 +6,10 @@ import (
|
||||||
discordspeaker "dndmusicbot/speaker"
|
discordspeaker "dndmusicbot/speaker"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
@ -21,6 +23,8 @@ import (
|
||||||
type MPD struct {
|
type MPD struct {
|
||||||
file int
|
file int
|
||||||
f beep.Format
|
f beep.Format
|
||||||
|
httpClient *http.Client
|
||||||
|
pcm *io.PipeReader
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -36,7 +40,9 @@ func init() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = t.Execute(f, config.GetStringMapString("mpd"))
|
mpd_conf := config.GetStringMapString("mpd")
|
||||||
|
|
||||||
|
err = t.Execute(f, mpd_conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -90,6 +96,11 @@ func init() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = app.mpd.NewPartition("ambiance")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
app.mpd.Repeat(true)
|
app.mpd.Repeat(true)
|
||||||
app.mpd.Random(true)
|
app.mpd.Random(true)
|
||||||
|
|
||||||
|
@ -98,6 +109,46 @@ func init() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// To force the httpd stream to start.
|
||||||
|
err = app.mpd.Add(filepath.Join(cwd, "mp3/silence.mp3"))
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
app.mpd.Play(0)
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
app.mpd.Stop()
|
||||||
|
app.mpd.Clear()
|
||||||
|
|
||||||
|
err = app.mpd.Partition("ambiance")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.mpd.Repeat(true)
|
||||||
|
|
||||||
|
err = app.mpd.MoveOutput("ambiance")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// To force the httpd stream to start.
|
||||||
|
err = app.mpd.Add(filepath.Join(cwd, "mp3/silence.mp3"))
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
app.mpd.Play(0)
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
app.mpd.Stop()
|
||||||
|
app.mpd.Clear()
|
||||||
|
err = app.mpd.Partition("default")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
app.mpdw, err = mpd.NewWatcher("unix", config.GetString("mpd.sock"), "")
|
app.mpdw, err = mpd.NewWatcher("unix", config.GetString("mpd.sock"), "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -118,15 +169,15 @@ func init() {
|
||||||
log.Println("mpd.go done.")
|
log.Println("mpd.go done.")
|
||||||
}
|
}
|
||||||
|
|
||||||
var MPD_PCM *io.PipeReader
|
func NewMPD(name string) (*MPD, error) {
|
||||||
|
|
||||||
func NewMPD() (*MPD, error) {
|
|
||||||
out := new(MPD)
|
out := new(MPD)
|
||||||
|
|
||||||
var pcm *io.PipeWriter
|
out.httpClient = &http.Client{}
|
||||||
MPD_PCM, pcm = io.Pipe()
|
|
||||||
|
|
||||||
f, err := syscall.Open(config.GetString("mpd.fifo"), syscall.O_CREAT|syscall.O_RDONLY|syscall.O_CLOEXEC|syscall.O_NONBLOCK, 0644)
|
var pcm *io.PipeWriter
|
||||||
|
out.pcm, pcm = io.Pipe()
|
||||||
|
|
||||||
|
f, err := syscall.Open(name, syscall.O_CREAT|syscall.O_RDONLY|syscall.O_CLOEXEC|syscall.O_NONBLOCK, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -159,11 +210,11 @@ func (m *MPD) Err() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MPD) Stream(samples [][2]float64) (n int, ok bool) {
|
func (m *MPD) Stream(samples [][2]float64) (n int, ok bool) {
|
||||||
tmp := make([]byte, m.f.NumChannels+2)
|
tmp := make([]byte, m.f.Width())
|
||||||
|
|
||||||
for i := range samples {
|
for i := range samples {
|
||||||
//dn, err := syscall.Read(m.file, tmp)
|
//dn, err := syscall.Read(m.file, tmp)
|
||||||
dn, err := MPD_PCM.Read(tmp)
|
dn, err := m.pcm.Read(tmp)
|
||||||
if dn == len(tmp) {
|
if dn == len(tmp) {
|
||||||
samples[i], _ = m.f.DecodeSigned(tmp)
|
samples[i], _ = m.f.DecodeSigned(tmp)
|
||||||
ok = true
|
ok = true
|
||||||
|
|
24
queue.go
24
queue.go
|
@ -3,6 +3,8 @@ package main
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"dndmusicbot/snapcast"
|
||||||
|
|
||||||
"github.com/gopxl/beep"
|
"github.com/gopxl/beep"
|
||||||
"github.com/gopxl/beep/effects"
|
"github.com/gopxl/beep/effects"
|
||||||
|
|
||||||
|
@ -14,24 +16,27 @@ var amb_volume *effects.Volume
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
log.Println("beep.go loading..")
|
log.Println("beep.go loading..")
|
||||||
|
amb := beep.Mixer{}
|
||||||
app.ambiance = beep.Mixer{}
|
|
||||||
|
|
||||||
amb_volume = &effects.Volume{
|
amb_volume = &effects.Volume{
|
||||||
Streamer: &app.ambiance,
|
Streamer: &amb,
|
||||||
Base: 2,
|
Base: 2,
|
||||||
Volume: 2,
|
Volume: -2,
|
||||||
Silent: false,
|
Silent: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
discordspeaker.Play(amb_volume)
|
discordspeaker.Play(amb_volume)
|
||||||
|
|
||||||
mpdstream, err := NewMPD()
|
amb_stream, err := snapcast.New("127.0.0.1", config.GetInt("mpd.ambiance"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
amb.Add(amb_stream)
|
||||||
|
|
||||||
|
pl := beep.Mixer{}
|
||||||
|
|
||||||
pl_volume = &effects.Volume{
|
pl_volume = &effects.Volume{
|
||||||
Streamer: mpdstream,
|
Streamer: &pl,
|
||||||
Base: 2,
|
Base: 2,
|
||||||
Volume: -2,
|
Volume: -2,
|
||||||
Silent: false,
|
Silent: false,
|
||||||
|
@ -39,5 +44,12 @@ func init() {
|
||||||
|
|
||||||
discordspeaker.Play(pl_volume)
|
discordspeaker.Play(pl_volume)
|
||||||
|
|
||||||
|
pl_stream, err := snapcast.New("127.0.0.1", config.GetInt("mpd.playlist"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pl.Add(pl_stream)
|
||||||
|
|
||||||
log.Println("beep.go done.")
|
log.Println("beep.go done.")
|
||||||
}
|
}
|
||||||
|
|
29
routes.go
29
routes.go
|
@ -6,6 +6,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
@ -58,6 +59,7 @@ func init() {
|
||||||
app.router.GET("/public/*js", auth(app.ServeFiles))
|
app.router.GET("/public/*js", auth(app.ServeFiles))
|
||||||
app.router.GET("/css/*css", auth(app.ServeFiles))
|
app.router.GET("/css/*css", auth(app.ServeFiles))
|
||||||
app.router.GET("/auth/callback", app.AuthHandler)
|
app.router.GET("/auth/callback", app.AuthHandler)
|
||||||
|
app.router.GET("/youtube/:id", local(ProxyTube))
|
||||||
|
|
||||||
app.router.HandlerFunc("GET", "/ws", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
app.router.HandlerFunc("GET", "/ws", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Printf("WS connection from %v\n", r.RemoteAddr)
|
log.Printf("WS connection from %v\n", r.RemoteAddr)
|
||||||
|
@ -67,14 +69,14 @@ func init() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handleWS(conn)
|
err = ws.join(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("WS connection closed, %v\n", r.RemoteAddr)
|
log.Printf("WS connection closed, %v\n", r.RemoteAddr)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
log.Fatal(http.ListenAndServe(":8824", app.router))
|
log.Fatal(http.ListenAndServe(":"+config.GetString("web.port"), app.router))
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,8 +158,31 @@ func (app App) AuthHandler(w http.ResponseWriter, r *http.Request, _ httprouter.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Middleware to check that the connection is local.
|
||||||
|
func local(n httprouter.Handle) httprouter.Handle {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
addr, err := netip.ParseAddrPort(r.RemoteAddr)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case addr.Addr().IsLoopback():
|
||||||
|
fallthrough
|
||||||
|
case addr.Addr().IsPrivate():
|
||||||
|
n(w, r, ps)
|
||||||
|
default:
|
||||||
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func auth(n httprouter.Handle) httprouter.Handle {
|
func auth(n httprouter.Handle) httprouter.Handle {
|
||||||
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
if os.Getenv("APP_ENV") == "test" {
|
||||||
|
n(w, r, ps)
|
||||||
|
return
|
||||||
|
}
|
||||||
values := r.URL.Query()
|
values := r.URL.Query()
|
||||||
values.Add("provider", "discord")
|
values.Add("provider", "discord")
|
||||||
r.URL.RawQuery = values.Encode()
|
r.URL.RawQuery = values.Encode()
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
package snapcast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gopxl/beep"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Stream struct {
|
||||||
|
sync.Mutex
|
||||||
|
|
||||||
|
Out io.ReadCloser
|
||||||
|
Cancel context.CancelFunc
|
||||||
|
Cmd *exec.Cmd
|
||||||
|
|
||||||
|
f beep.Format
|
||||||
|
buf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(host string, port int) (ff beep.Streamer, err error) {
|
||||||
|
st := new(Stream)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
st.Cancel = cancel
|
||||||
|
|
||||||
|
st.Cmd = exec.CommandContext(
|
||||||
|
ctx,
|
||||||
|
"/usr/bin/snapclient",
|
||||||
|
"-h", host,
|
||||||
|
"-p", strconv.Itoa(port),
|
||||||
|
"--player", "file:filename=stderr",
|
||||||
|
"--logfilter", "*:error",
|
||||||
|
)
|
||||||
|
|
||||||
|
st.Cmd.Stdout = os.Stdin
|
||||||
|
|
||||||
|
st.Out, err = st.Cmd.StderrPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = st.Cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
st.buf = make([]byte, 4)
|
||||||
|
st.f = beep.Format{
|
||||||
|
SampleRate: beep.SampleRate(48000),
|
||||||
|
NumChannels: 2,
|
||||||
|
Precision: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
return st, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Stream) Stream(samples [][2]float64) (n int, ok bool) {
|
||||||
|
for i := range samples {
|
||||||
|
_, err := d.Out.Read(d.buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
ok = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
samples[i], _ = d.f.DecodeSigned(d.buf)
|
||||||
|
ok = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(samples), ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Stream) Err() error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -2,7 +2,6 @@ package discordspeaker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -14,8 +13,6 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
"gopkg.in/hraban/opus.v2"
|
"gopkg.in/hraban/opus.v2"
|
||||||
|
|
||||||
cc "github.com/tejzpr/ordered-concurrently/v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -30,16 +27,20 @@ var (
|
||||||
maxBytes int = (frameSize * 2) * 2
|
maxBytes int = (frameSize * 2) * 2
|
||||||
buf []byte
|
buf []byte
|
||||||
session *voice.Session
|
session *voice.Session
|
||||||
spk bool
|
|
||||||
input chan cc.WorkFunction
|
|
||||||
pw *io.PipeWriter
|
pw *io.PipeWriter
|
||||||
pr *io.PipeReader
|
pr *io.PipeReader
|
||||||
spklimit = rate.NewLimiter(rate.Every(2*time.Second), 1)
|
|
||||||
|
spklimit = rate.NewLimiter(rate.Every(5*time.Second), 1)
|
||||||
|
spk = true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var start time.Time
|
||||||
|
|
||||||
var Silence = [2]float64{}
|
var Silence = [2]float64{}
|
||||||
|
|
||||||
func Init(dgv *voice.Session) error {
|
var dmutex = sync.Mutex{}
|
||||||
|
|
||||||
|
func Init(dgv *voice.Session, bitrate int) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
|
@ -53,51 +54,37 @@ func Init(dgv *voice.Session) error {
|
||||||
|
|
||||||
session = dgv
|
session = dgv
|
||||||
|
|
||||||
encoder, err = opus.NewEncoder(sampleRate, channels, opus.AppVoIP)
|
encoder, err = opus.NewEncoder(sampleRate, channels, opus.AppAudio)
|
||||||
encoder.SetBitrateToMax()
|
encoder.SetBitrate(bitrate)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to initialize speaker")
|
return errors.Wrap(err, "failed to initialize speaker")
|
||||||
}
|
}
|
||||||
|
|
||||||
input = make(chan cc.WorkFunction)
|
|
||||||
ctx := context.Background()
|
|
||||||
output := cc.Process(ctx, input, &cc.Options{PoolSize: 4, OutChannelBuffer: 4})
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
default:
|
default:
|
||||||
update()
|
update()
|
||||||
case out := <-output:
|
|
||||||
fmt.Println(pw.Write(out.Value.([]byte)))
|
|
||||||
case <-done:
|
case <-done:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
go ReadSend()
|
go func() {
|
||||||
|
dmutex.Lock()
|
||||||
|
_, err := io.Copy(session, pr)
|
||||||
|
dmutex.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadSend() {
|
|
||||||
for {
|
|
||||||
buf := make([]byte, maxBytes)
|
|
||||||
n, err := pr.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = session.Write(buf[:n])
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Close() {
|
func Close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,41 +119,57 @@ func Speak(s bool) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func update() {
|
func CheckSilence(samples [][2]float64) {
|
||||||
mu.Lock()
|
|
||||||
mixer.Stream(samples)
|
|
||||||
mu.Unlock()
|
|
||||||
|
|
||||||
if IsSilent(samples) {
|
if IsSilent(samples) {
|
||||||
if spk && spklimit.Allow() {
|
if spk && spklimit.Allow() {
|
||||||
log.Println("Notspeaking")
|
log.Println("Notspeaking")
|
||||||
session.Speaking(context.Background(), voicegateway.NotSpeaking)
|
session.Speaking(context.Background(), voicegateway.NotSpeaking)
|
||||||
spk = false
|
spk = false
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
return
|
if !spk {
|
||||||
}
|
|
||||||
|
|
||||||
if !spk && spklimit.Allow() {
|
|
||||||
log.Println("Speaking")
|
log.Println("Speaking")
|
||||||
session.Speaking(context.Background(), voicegateway.Microphone)
|
session.Speaking(context.Background(), voicegateway.Microphone)
|
||||||
spk = true
|
spk = true
|
||||||
|
spklimit.Reserve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func update() {
|
||||||
|
start = time.Now()
|
||||||
|
mu.Lock()
|
||||||
|
mixer.Stream(samples)
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
|
go CheckSilence(samples)
|
||||||
|
|
||||||
|
if !spk {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var f32 []float32
|
f32 := make([]float32, len(samples)*2)
|
||||||
|
var idx int
|
||||||
|
|
||||||
for _, sample := range samples {
|
for i := 0; i < len(samples); i++ {
|
||||||
f32 = append(f32, float32(sample[0]))
|
f32[idx] = float32(samples[i][0])
|
||||||
f32 = append(f32, float32(sample[1]))
|
idx++
|
||||||
|
f32[idx] = float32(samples[i][1])
|
||||||
|
idx++
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err := encoder.EncodeFloat32(f32, buf)
|
n, err := encoder.EncodeFloat32(f32, buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pw.Write(buf[:n])
|
_, err = pw.Write(buf[:n])
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//fmt.Println(time.Since(start), len(samples), n)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsSilent(in [][2]float64) bool {
|
func IsSilent(in [][2]float64) bool {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "esbuild app/root.tsx --bundle --outfile=../public_test/script.js"
|
"build": "esbuild app/root.tsx --bundle --outfile=/public/script.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/react": "^18.2.33",
|
"@types/react": "^18.2.33",
|
||||||
|
|
|
@ -1,7 +1,33 @@
|
||||||
|
# audio_output {
|
||||||
|
# type "fifo"
|
||||||
|
# name "fifo-output"
|
||||||
|
# path "{{ .fifo }}"
|
||||||
|
# format "48000:16:2"
|
||||||
|
# enabled "yes"
|
||||||
|
#}
|
||||||
audio_output {
|
audio_output {
|
||||||
type "fifo"
|
type "snapcast"
|
||||||
name "fifo-output"
|
name "playlist"
|
||||||
path "{{ .fifo }}"
|
bind_to_address "0.0.0.0"
|
||||||
|
port "{{ .playlist }}"
|
||||||
|
zeroconf "no"
|
||||||
|
format "48000:16:2"
|
||||||
|
enabled "yes"
|
||||||
|
always_on "yes"
|
||||||
|
}
|
||||||
|
audio_output {
|
||||||
|
type "snapcast"
|
||||||
|
name "ambiance"
|
||||||
|
bind_to_address "0.0.0.0"
|
||||||
|
port "{{ .ambiance }}"
|
||||||
|
zeroconf "no"
|
||||||
|
format "48000:16:2"
|
||||||
|
enabled "yes"
|
||||||
|
always_on "yes"
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
plugin "curl"
|
||||||
|
verbose "yes"
|
||||||
}
|
}
|
||||||
decoder {
|
decoder {
|
||||||
plugin "wildmidi"
|
plugin "wildmidi"
|
||||||
|
@ -21,6 +47,6 @@ resampler {
|
||||||
}
|
}
|
||||||
|
|
||||||
volume_normalization "yes"
|
volume_normalization "yes"
|
||||||
audio_output_format "48000:16:2"
|
# audio_output_format "48000:16:2"
|
||||||
bind_to_address "{{ .sock }}"
|
bind_to_address "{{ .sock }}"
|
||||||
pid_file "{{ .pid }}"
|
pid_file "{{ .pid }}"
|
||||||
|
|
66
ws.go
66
ws.go
|
@ -12,18 +12,21 @@ import (
|
||||||
"github.com/kataras/go-events"
|
"github.com/kataras/go-events"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Websocket struct {
|
||||||
|
sync.Mutex
|
||||||
|
|
||||||
|
clients *bcast.Group
|
||||||
|
}
|
||||||
|
|
||||||
|
var ws *Websocket
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
log.Println("ws.go loading..")
|
log.Println("ws.go loading..")
|
||||||
go ws_clients.Broadcast(0)
|
ws = new(Websocket)
|
||||||
ws_msg = make(chan interface{})
|
|
||||||
|
|
||||||
go func() {
|
ws.clients = bcast.NewGroup()
|
||||||
var msg interface{}
|
|
||||||
for {
|
go ws.clients.Broadcast(0)
|
||||||
msg = <-ws_msg
|
|
||||||
ws_clients.Send(msg)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.Println("ws.go done.")
|
log.Println("ws.go done.")
|
||||||
}
|
}
|
||||||
|
@ -33,12 +36,19 @@ type WSmsg struct {
|
||||||
Payload json.RawMessage
|
Payload json.RawMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
var ws_clients = bcast.NewGroup()
|
type Event struct {
|
||||||
var ws_msg chan interface{}
|
Event string `json:"event"`
|
||||||
var WSMutex = &sync.Mutex{}
|
Payload any `json:"payload,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
func handleWS(c *websocket.Conn) error {
|
func (ws *Websocket) SendEvent(e Event) {
|
||||||
memb := ws_clients.Join()
|
ws.Lock()
|
||||||
|
ws.clients.Send(e)
|
||||||
|
ws.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *Websocket) join(c *websocket.Conn) error {
|
||||||
|
memb := ws.clients.Join()
|
||||||
defer memb.Close()
|
defer memb.Close()
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
@ -64,29 +74,27 @@ func handleWS(c *websocket.Conn) error {
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
msg := app.songInfoEvent("song_info")
|
msg, err := app.songInfoEvent("song_info")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
vol := make(map[string]interface{})
|
vol := Event{"volume", map[string]float64{
|
||||||
volout := make(map[string]float64)
|
"playlist": pl_volume.Volume,
|
||||||
vol["event"] = "volume"
|
"ambiance": amb_volume.Volume,
|
||||||
volout["playlist"] = pl_volume.Volume
|
}}
|
||||||
volout["ambiance"] = amb_volume.Volume
|
|
||||||
vol["payload"] = volout
|
|
||||||
|
|
||||||
c.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
c.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||||
c.WriteJSON(msg)
|
c.WriteJSON(msg)
|
||||||
c.WriteJSON(vol)
|
c.WriteJSON(vol)
|
||||||
|
|
||||||
if app.ambiance.Len() > 0 {
|
if app.ambiance.Len() > 0 {
|
||||||
msg := make(map[string]interface{})
|
msg := Event{"ambiance_play", map[string]string{
|
||||||
out := make(map[string]interface{})
|
"id": app.curamb.Id,
|
||||||
msg["event"] = "ambiance_play"
|
}}
|
||||||
out["id"] = app.curamb.Id
|
|
||||||
msg["payload"] = out
|
|
||||||
c.WriteJSON(msg)
|
c.WriteJSON(msg)
|
||||||
} else {
|
} else {
|
||||||
msg := make(map[string]interface{})
|
msg := Event{"ambiance_stop", nil}
|
||||||
msg["event"] = "ambiance_stop"
|
|
||||||
c.WriteJSON(msg)
|
c.WriteJSON(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,6 +105,6 @@ func handleWS(c *websocket.Conn) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
app.events.Emit(events.EventName(msg.Event), msg.Payload)
|
app.events.Emit(events.EventName(msg.Event), msg.Payload, memb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
68
youtube.go
68
youtube.go
|
@ -1,8 +1,16 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"dndmusicbot/youtube"
|
"dndmusicbot/youtube"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafov/m3u8"
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -12,3 +20,63 @@ func init() {
|
||||||
|
|
||||||
log.Println("youtube.go done.")
|
log.Println("youtube.go done.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ProxyTube(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||||
|
os.MkdirAll("cache", 0755)
|
||||||
|
_, data, err := app.cache.GetOrCreateBytes("youtube."+p.ByName("id"), func() (out []byte, err error) {
|
||||||
|
vinfo, err := app.youtube.GetVideoFromID(p.ByName("id"))
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uri := vinfo.GetHLSPlaylist("234")
|
||||||
|
|
||||||
|
resp, err := http.Get(uri)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
pl, _, err := m3u8.DecodeFrom(resp.Body, true)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var segresp *http.Response
|
||||||
|
mediapl := pl.(*m3u8.MediaPlaylist)
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
for _, segment := range mediapl.GetAllSegments() {
|
||||||
|
segresp, err = http.Get(segment.URI)
|
||||||
|
if err != nil {
|
||||||
|
segresp.Body.Close()
|
||||||
|
log.Println(err)
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = io.Copy(buf, segresp.Body)
|
||||||
|
if err != nil {
|
||||||
|
segresp.Body.Close()
|
||||||
|
log.Println(err)
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
seeker := bytes.NewReader(data)
|
||||||
|
w.Header().Add("Content-Type", "audio/mp4")
|
||||||
|
http.ServeContent(w, r, "youtube."+p.ByName("id"), time.Now(), seeker)
|
||||||
|
}
|
||||||
|
|
|
@ -113,6 +113,14 @@ func (c Client) GetVideoFromID(id string) (out Video, err error) {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
func (v Video) GetUrlByItag(itagNo int64) string {
|
||||||
|
for _, format := range v.StreamingData.AdaptiveFormats {
|
||||||
|
if format.Itag == itagNo {
|
||||||
|
return format.URL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (v Video) GetHLSPlaylist(itag string) (out string) {
|
func (v Video) GetHLSPlaylist(itag string) (out string) {
|
||||||
resp, err := http.Get(v.StreamingData.HlsManifestURL)
|
resp, err := http.Get(v.StreamingData.HlsManifestURL)
|
||||||
|
|
83
ytdl.go
83
ytdl.go
|
@ -1,22 +1,66 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"math"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
)
|
)
|
||||||
|
|
||||||
var prate = rate.Sometimes{Interval: 1 * time.Second}
|
var prate = rate.Sometimes{Interval: 1 * time.Second}
|
||||||
var yturl = "https://youtu.be/%s"
|
|
||||||
|
// var yturl = "https://youtu.be/%s"
|
||||||
|
|
||||||
|
func YTUrl(uri string) (vid string, err error) {
|
||||||
|
u, err := url.Parse(uri)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch u.Host {
|
||||||
|
case "youtu.be":
|
||||||
|
vid = u.Path[1:]
|
||||||
|
case "m.youtube.com":
|
||||||
|
fallthrough
|
||||||
|
case "youtube.com":
|
||||||
|
fallthrough
|
||||||
|
case "www.youtube.com":
|
||||||
|
vid = u.Query().Get("v")
|
||||||
|
}
|
||||||
|
|
||||||
|
if vid == "" {
|
||||||
|
return vid, fmt.Errorf("unable to parse vid")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func NewYTdlUrl(vid string) ([]byte, error) {
|
||||||
|
ytdl := config.GetString("youtube.ytdl")
|
||||||
|
yt := exec.Command(
|
||||||
|
ytdl,
|
||||||
|
fmt.Sprintf(yturl, vid),
|
||||||
|
"--cookies", "./cookies.txt",
|
||||||
|
"--no-call-home",
|
||||||
|
"--no-cache-dir",
|
||||||
|
"--ignore-errors",
|
||||||
|
"--newline",
|
||||||
|
"--restrict-filenames",
|
||||||
|
"-f", "234",
|
||||||
|
"--get-url",
|
||||||
|
)
|
||||||
|
|
||||||
|
yt.Stderr = os.Stderr
|
||||||
|
|
||||||
|
uri, err := yt.Output()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return uri[:len(uri)-1], nil
|
||||||
|
}
|
||||||
|
|
||||||
func NewYTdl(vid string) ([]byte, error) {
|
func NewYTdl(vid string) ([]byte, error) {
|
||||||
tmpfile, err := exec.Command("mktemp", "/tmp/dnd_ytdlp_XXXXXXXXXXXX.m4a").Output()
|
tmpfile, err := exec.Command("mktemp", "/tmp/dnd_ytdlp_XXXXXXXXXXXX.m4a").Output()
|
||||||
|
@ -107,29 +151,6 @@ func NewYTdl(vid string) ([]byte, error) {
|
||||||
return tmpfile, nil
|
return tmpfile, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func YTUrl(uri string) (vid string, err error) {
|
|
||||||
u, err := url.Parse(uri)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch u.Host {
|
|
||||||
case "youtu.be":
|
|
||||||
vid = u.Path[1:]
|
|
||||||
case "m.youtube.com":
|
|
||||||
fallthrough
|
|
||||||
case "youtube.com":
|
|
||||||
fallthrough
|
|
||||||
case "www.youtube.com":
|
|
||||||
vid = u.Query().Get("v")
|
|
||||||
}
|
|
||||||
|
|
||||||
if vid == "" {
|
|
||||||
return vid, fmt.Errorf("unable to parse vid")
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
func DownloadAmbiance(uri string, name string) error {
|
func DownloadAmbiance(uri string, name string) error {
|
||||||
|
|
Loading…
Reference in New Issue