Add support for the jukebox endpoint

This supports most of jukeboxControl.view as far as i can tell. Things
seem to be playing ok without freaking out

I've also only tested it a little bit with ultrasonic but it does
appear to be working pretty well
This commit is contained in:
Alex McGrath
2020-04-17 13:53:45 +01:00
parent f262c66c76
commit 64d0aee8dc
7 changed files with 292 additions and 0 deletions

1
go.mod
View File

@@ -7,6 +7,7 @@ require (
github.com/Masterminds/sprig v2.20.0+incompatible
github.com/cespare/xxhash v1.1.0
github.com/dustin/go-humanize v1.0.0
github.com/faiface/beep v1.0.2
github.com/google/uuid v1.1.1 // indirect
github.com/gorilla/mux v1.7.3
github.com/gorilla/securecookie v1.1.1

30
go.sum
View File

@@ -38,7 +38,11 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/faiface/beep v1.0.2 h1:UB5DiRNmA4erfUYnHbgU4UB6DlBOrsdEFRtcc8sCkdQ=
github.com/faiface/beep v1.0.2/go.mod h1:1yLb5yRdHMsovYYWVqYLioXkVuziCSITW1oarTeduQM=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v1.1.1/go.mod h1:K1udHkiR3cOtlpKG5tZPD5XxrF7v2y7lDq7Whcj+xkQ=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
@@ -66,6 +70,12 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw=
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherwasm v0.1.1/go.mod h1:kx4n9a+MzHH0BJJhvlsQ65hqLFXDO/m256AsaDPQ+/4=
github.com/gopherjs/gopherwasm v1.0.0 h1:32nge/RlujS1Im4HNCJPp0NbBOAeBXFuT1KonUuLl+Y=
github.com/gopherjs/gopherwasm v1.0.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@@ -75,6 +85,11 @@ github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyC
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9RU=
github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
github.com/hajimehoshi/go-mp3 v0.1.1 h1:Y33fAdTma70fkrxnc9u50Uq0lV6eZ+bkAlssdMmCwUc=
github.com/hajimehoshi/go-mp3 v0.1.1/go.mod h1:4i+c5pDNKDrxl1iu9iG90/+fhP37lio6gNhjCx9WBJw=
github.com/hajimehoshi/oto v0.1.1/go.mod h1:hUiLWeBQnbDu4pZsAhOnGqMI1ZGibS6e2qhQdfpwz04=
github.com/hajimehoshi/oto v0.3.1 h1:cpf/uIv4Q0oc5uf9loQn7PIehv+mZerh+0KKma6gzMk=
github.com/hajimehoshi/oto v0.3.1/go.mod h1:e9eTLBB9iZto045HLbzfHJIc+jP3xaKrjZTghvb6fdM=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
@@ -82,6 +97,8 @@ github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/jfreymuth/oggvorbis v1.0.0/go.mod h1:abe6F9QRjuU9l+2jek3gj46lu40N4qlYxh2grqkLEDM=
github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0=
github.com/jinzhu/gorm v1.9.2/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo=
github.com/jinzhu/gorm v1.9.10 h1:HvrsqdhCW78xpJF67g1hMxS6eCToo9PZH4LDB8WKPac=
github.com/jinzhu/gorm v1.9.10/go.mod h1:Kh6hTsSGffh4ui079FHrR5Gg+5D0hgihqDcsDN2BBJY=
@@ -111,9 +128,13 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mewkiz/flac v1.0.5 h1:dHGW/2kf+/KZ2GGqSVayNEhL9pluKn/rr/h/QqD9Ogc=
github.com/mewkiz/flac v1.0.5/go.mod h1:EHZNU32dMF6alpurYyKHDLYpW1lYpBZ5WrXi/VuNIGs=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nicksellen/audiotags v0.0.0-20160226222119-94015fa599bd h1:xKn/gU8lZupoZt/HE7a/R3aH93iUO6JwyRsYelQUsRI=
github.com/nicksellen/audiotags v0.0.0-20160226222119-94015fa599bd/go.mod h1:B6icauz2l4tkYQxmDtCH4qmNWz/evSW5CsOqp6IE5IE=
@@ -161,12 +182,18 @@ golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20180710024300-14dda7b62fcd/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4 h1:c2HOrn5iMezYjSlGPncknSEr/8x5LELb/ilJbXi9DEA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81 h1:00VmoueYNlNz/aHIilyyQz/MHSqGoWJzpFv/HW8xpzI=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20180806140643-507816974b79 h1:t2JRgCWkY7Qaa1J2jal+wqC9OjbyHCHwIA9rVlRUSMo=
golang.org/x/mobile v0.0.0-20180806140643-507816974b79/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -192,10 +219,12 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c h1:+EXw7AwNOKzPFXMZ1yNjO40aWCh3PIquJB2fYlv9wcs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
@@ -230,6 +259,7 @@ google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dT
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=

192
jukebox/jukebox.go Normal file
View File

@@ -0,0 +1,192 @@
package jukebox
import (
"os"
"path"
"sync"
"time"
"github.com/faiface/beep"
"github.com/faiface/beep/flac"
"github.com/faiface/beep/mp3"
"github.com/faiface/beep/speaker"
"go.senan.xyz/gonic/db"
"go.senan.xyz/gonic/server/ctrlsubsonic/spec"
)
type strmInfo struct {
ctrlStrmr beep.Ctrl
strm beep.StreamSeekCloser
format beep.Format
}
type Jukebox struct {
playlist []*db.Track
index int
playing bool
// used to notify the player to re read the members
updates chan struct{}
done chan bool
info *strmInfo
sync.Mutex
}
func (j *Jukebox) Init(musicPath string) error {
j.updates = make(chan struct{})
sr := beep.SampleRate(48000)
err := speaker.Init(sr, sr.N(time.Second/2))
if err != nil {
return err
}
j.done = make(chan bool)
go func() {
for range j.updates {
var streamer beep.Streamer
var format beep.Format
f, err := os.Open(path.Join(musicPath, j.playlist[j.index].RelPath()))
if err != nil {
j.index++
continue
}
switch j.playlist[j.index].Ext() {
case "mp3":
streamer, format, err = mp3.Decode(f)
case "flac":
streamer, format, err = flac.Decode(f)
default:
j.index++
continue
}
if err != nil {
j.index++
continue
}
if j.playing {
j.Lock()
{
j.info = &strmInfo{}
j.info.strm = streamer.(beep.StreamSeekCloser)
j.info.ctrlStrmr.Streamer = beep.Resample(4, format.SampleRate, sr, j.info.strm)
j.info.format = format
}
j.Unlock()
speaker.Play(beep.Seq(&j.info.ctrlStrmr, beep.Callback(func() {
j.done <- false
})))
if v := <-j.done; !v {
j.index++
j.Lock()
if j.index > len(j.playlist) {
j.index = 0
j.playing = false
}
j.Unlock()
// in a go routine as otherwise this hangs as the
go func() {
j.updates <- struct{}{}
}()
} else {
continue
}
}
}
}()
return nil
}
func (j *Jukebox) SetTracks(tracks []*db.Track) {
j.Lock()
j.index = 0
j.playing = true
if len(j.playlist) > 0 {
j.done <- true
j.playlist = []*db.Track{}
speaker.Clear()
}
j.playlist = tracks
j.Unlock()
j.updates <- struct{}{}
}
func (j *Jukebox) AddTracks(tracks []*db.Track) {
j.Lock()
j.playlist = append(j.playlist, tracks...)
j.Unlock()
}
func (j *Jukebox) ClearTracks() {
j.Lock()
j.index = 0
j.playing = false
j.playlist = []*db.Track{}
j.Unlock()
}
func (j *Jukebox) RemoveTrack(i int) {
j.Lock()
defer j.Unlock()
if i < 0 || i > len(j.playlist) {
return
}
j.playlist = append(j.playlist[:i], j.playlist[i+1:]...)
}
func (j *Jukebox) Status() *spec.JukeboxStatus {
position := 0
if j.info != nil {
length := j.info.format.SampleRate.D(j.info.strm.Position())
position = int(length.Round(time.Millisecond).Seconds())
}
return &spec.JukeboxStatus{
CurrentIndex: j.index,
Playing: j.playing,
Gain: 0.9,
Position: position,
}
}
func (j *Jukebox) GetTracks() *spec.JukeboxPlaylist {
j.Lock()
defer j.Unlock()
jb := &spec.JukeboxPlaylist{}
jb.List = make([]*spec.TrackChild, len(j.playlist))
for i, track := range j.playlist {
jb.List[i] = spec.NewTrackByTags(track, track.Album)
}
jb.CurrentIndex = j.index
jb.Playing = j.playing
jb.Gain = 0.9
jb.Position = 0
if j.info != nil {
length := j.info.format.SampleRate.D(j.info.strm.Position())
jb.Position = int(length.Round(time.Millisecond).Seconds())
}
return jb
}
func (j *Jukebox) Stop() {
j.Lock()
j.playing = false
j.info.ctrlStrmr.Paused = true
j.Unlock()
}
func (j *Jukebox) Start() {
j.Lock()
j.playing = false
j.info.ctrlStrmr.Paused = false
j.Unlock()
}
func (j *Jukebox) Skip(i int, skipCurrent bool) {
j.Lock()
if skipCurrent {
j.index++
} else {
j.index = i
}
speaker.Clear()
j.done <- true
j.updates <- struct{}{}
j.Unlock()
}

View File

@@ -7,6 +7,7 @@ import (
"path"
"go.senan.xyz/gonic/db"
"go.senan.xyz/gonic/jukebox"
"go.senan.xyz/gonic/scanner"
)
@@ -49,6 +50,7 @@ type Controller struct {
MusicPath string
Scanner *scanner.Scanner
ProxyPrefix string
Jukebox *jukebox.Jukebox
}
// Path returns a URL path with the proxy prefix included

View File

@@ -112,6 +112,7 @@ func (c *Controller) ServeGetUser(r *http.Request) *spec.Response {
sub.User = &spec.User{
Username: user.Name,
AdminRole: user.IsAdmin,
JukeboxRole: true,
ScrobblingEnabled: user.LastFMSession != "",
Folder: []int{1},
}
@@ -313,3 +314,51 @@ func (c *Controller) ServeGetRandomSongs(r *http.Request) *spec.Response {
}
return sub
}
func (c *Controller) ServeJukebox(r *http.Request) *spec.Response {
params := r.Context().Value(CtxParams).(params.Params)
switch params.Get("action") {
case "set":
var tracks []*db.Track
ids := params.GetFirstListInt("id")
if len(ids) == 0 {
c.Jukebox.ClearTracks()
}
for _, id := range ids {
track := &db.Track{}
err := c.DB.Preload("Album").First(track, id).Error
if err != nil {
return spec.NewError(10, "couldn't find tracks with provided ids")
}
tracks = append(tracks, track)
}
c.Jukebox.SetTracks(tracks)
case "clear":
c.Jukebox.ClearTracks()
case "remove":
index, err := params.GetInt("index")
if err != nil {
return spec.NewError(10, "please provide an id for remove actions")
}
c.Jukebox.RemoveTrack(index)
case "stop":
c.Jukebox.Stop()
case "start":
c.Jukebox.Start()
case "skip":
index, err := params.GetInt("index")
var skipCurrent bool
if err != nil {
skipCurrent = true
}
c.Jukebox.Skip(index, skipCurrent)
case "get":
sub := spec.NewResponse()
sub.JukeboxPlaylist = c.Jukebox.GetTracks()
return sub
}
// All actions except get are expected to return a status
sub := spec.NewResponse()
sub.JukeboxStatus = c.Jukebox.Status()
return sub
}

View File

@@ -41,6 +41,8 @@ type Response struct {
ArtistInfoTwo *ArtistInfo `xml:"artistInfo2" json:"artistInfo2,omitempty"`
Genres *Genres `xml:"genres" json:"genres,omitempty"`
PlayQueue *PlayQueue `xml:"playQueue" json:"playQueue,omitempty"`
JukeboxStatus *JukeboxStatus `xml:"jukeboxStatus" json:"jukeboxStatus,omitempty"`
JukeboxPlaylist *JukeboxPlaylist `xml:"jukeboxPlaylist" json:"jukeboxPlaylist,omitempty"`
}
func NewResponse() *Response {
@@ -269,3 +271,15 @@ type PlayQueue struct {
ChangedBy string `xml:"changedBy,attr" json:"changedBy"`
List []*TrackChild `xml:"entry,omitempty" json:"entry,omitempty"`
}
type JukeboxStatus struct {
CurrentIndex int `xml:"currentIndex,attr" json:"currentIndex"`
Playing bool `xml:"playing,attr" json:"playing"`
Gain float64 `xml:"gain,attr" json:"gain"`
Position int `xml:"position,attr" json:"position"`
}
type JukeboxPlaylist struct {
List []*TrackChild `xml:"entry,omitempty" json:"entry,omitempty"`
JukeboxStatus
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/gorilla/mux"
"go.senan.xyz/gonic/db"
"go.senan.xyz/gonic/jukebox"
"go.senan.xyz/gonic/scanner"
"go.senan.xyz/gonic/server/assets"
"go.senan.xyz/gonic/server/ctrladmin"
@@ -46,7 +47,9 @@ func New(opts Options) *Server {
MusicPath: opts.MusicPath,
ProxyPrefix: opts.ProxyPrefix,
Scanner: scanner,
Jukebox: &jukebox.Jukebox{},
}
base.Jukebox.Init(opts.MusicPath)
// router with common wares for admin / subsonic
r := mux.NewRouter()
r.Use(base.WithLogging)
@@ -154,6 +157,7 @@ func setupSubsonic(r *mux.Router, ctrl *ctrlsubsonic.Controller) {
r.Handle("/getSong{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetSong))
r.Handle("/getRandomSongs{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetRandomSongs))
r.Handle("/getSongsByGenre{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetSongsByGenre))
r.Handle("/jukeboxControl{_:(?:\\.view)?}", ctrl.H(ctrl.ServeJukebox))
// ** begin raw
r.Handle("/download{_:(?:\\.view)?}", ctrl.HR(ctrl.ServeDownload))
r.Handle("/getCoverArt{_:(?:\\.view)?}", ctrl.HR(ctrl.ServeGetCoverArt))