haha
This commit is contained in:
commit
052616c318
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.DS_Store
|
||||||
|
serve
|
||||||
|
main
|
||||||
124
archive/archive.go
Normal file
124
archive/archive.go
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package archive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"hash/fnv"
|
||||||
|
"../config"
|
||||||
|
)
|
||||||
|
|
||||||
|
var pln = fmt.Println
|
||||||
|
|
||||||
|
var Archive_map map[uint32]config.Track_t
|
||||||
|
var Archive_map_keys []uint32
|
||||||
|
|
||||||
|
var built = false
|
||||||
|
|
||||||
|
func Build() error {
|
||||||
|
|
||||||
|
if built {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
Archive_map = make(map[uint32]config.Track_t)
|
||||||
|
|
||||||
|
album_dirs, err := ioutil.ReadDir(config.Xcfg.Archive.PATH)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dir := range album_dirs {
|
||||||
|
|
||||||
|
album := new(config.Album_t)
|
||||||
|
album.TRACKS = make(map[uint32]config.Track_t)
|
||||||
|
album.PATH = filepath.Join(config.Xcfg.Archive.PATH, dir.Name())
|
||||||
|
album.ID = hash(album.PATH)
|
||||||
|
|
||||||
|
album.MAKER, album.NAME = parse_album_name(dir.Name())
|
||||||
|
|
||||||
|
track_files, _ := filepath.Glob(album.PATH + "/*.mp3")
|
||||||
|
for _, file := range track_files {
|
||||||
|
track := new(config.Track_t)
|
||||||
|
track.NAME = parse_track_name(filepath.Base(file), album.MAKER, album.NAME)
|
||||||
|
track.MAKER = album.MAKER
|
||||||
|
track.ALBUM = album.NAME
|
||||||
|
track.PATH = file
|
||||||
|
track.ID = hash(track.PATH)
|
||||||
|
track.AID = album.ID
|
||||||
|
|
||||||
|
album.TRACKS[track.ID] = *track
|
||||||
|
|
||||||
|
Archive_map[track.ID] = *track
|
||||||
|
Archive_map_keys = append(Archive_map_keys, track.ID)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
cover, _ := filepath.Glob(album.PATH + "/*.jpg")
|
||||||
|
if len(cover) > 0 {
|
||||||
|
album.COVER = filepath.Join(album.PATH, cover[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// print_album(album)
|
||||||
|
|
||||||
|
config.Xcfg.Archive.ALBUMS[album.ID] = *album
|
||||||
|
}
|
||||||
|
|
||||||
|
built = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse_album_name(name string) (string, string) {
|
||||||
|
chunks := strings.Split(name, " - ")
|
||||||
|
if len(chunks) >= 2 {
|
||||||
|
return chunks[0], chunks[1]
|
||||||
|
}
|
||||||
|
return chunks[0], "n/a"
|
||||||
|
}
|
||||||
|
|
||||||
|
func print_album(a *config.Album_t) {
|
||||||
|
pln(a.MAKER)
|
||||||
|
pln(a.NAME)
|
||||||
|
pln(a.PATH)
|
||||||
|
pln(a.COVER)
|
||||||
|
pln(a.ID)
|
||||||
|
pln(len(a.TRACKS))
|
||||||
|
pln("-----")
|
||||||
|
for _, t := range a.TRACKS {
|
||||||
|
print_track(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ex.: Electronic Works 1958-1995-002-Else Marie Pade-Faust Suite Faust & Mefisto.mp3
|
||||||
|
func parse_track_name(name string, maker string, album string) string {
|
||||||
|
|
||||||
|
chunks := []string{}
|
||||||
|
if strings.HasPrefix(name, album) { // bleep
|
||||||
|
chunks = strings.Split(name, "-" + maker + "-")
|
||||||
|
} else if strings.HasPrefix(name, maker) { // bandcamp
|
||||||
|
chunks = strings.Split(name, " - " + album + " - ")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(chunks) >= 2 {
|
||||||
|
return strings.Split(chunks[1], ".")[0]
|
||||||
|
}
|
||||||
|
return "n/a"
|
||||||
|
}
|
||||||
|
|
||||||
|
func print_track(t config.Track_t) {
|
||||||
|
|
||||||
|
pln(" " + t.NAME)
|
||||||
|
pln(" " + t.PATH)
|
||||||
|
pln(t.ID)
|
||||||
|
pln(t.AID)
|
||||||
|
pln("////////")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func hash(s string) uint32 {
|
||||||
|
h := fnv.New32a()
|
||||||
|
h.Write([]byte(s))
|
||||||
|
return h.Sum32()
|
||||||
|
}
|
||||||
71
audio/audio.go
Normal file
71
audio/audio.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package audio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"time"
|
||||||
|
"../icecast"
|
||||||
|
)
|
||||||
|
|
||||||
|
// type stream_t struct {
|
||||||
|
// filepath string
|
||||||
|
// bitrate int
|
||||||
|
// channels int
|
||||||
|
// encoding_src string
|
||||||
|
// encoding_dst string
|
||||||
|
// }
|
||||||
|
|
||||||
|
func Play(filename string) {
|
||||||
|
|
||||||
|
sig := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sig, os.Interrupt, os.Kill)
|
||||||
|
|
||||||
|
samplerate := 44100
|
||||||
|
bytes_per_sample := 1
|
||||||
|
channels := 1
|
||||||
|
bytes_per_sec := int(samplerate) * channels * bytes_per_sample
|
||||||
|
|
||||||
|
r, e := os.Open(filename)
|
||||||
|
chk(e)
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
_, er := icecast.Connect()
|
||||||
|
chk(er)
|
||||||
|
|
||||||
|
audio := make([]byte, 2 * 1024)
|
||||||
|
|
||||||
|
dt := time.Second * time.Duration(len(audio)) / time.Duration(bytes_per_sec)
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
for {
|
||||||
|
|
||||||
|
n, err := r.Read(audio)
|
||||||
|
if n == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
chk(err)
|
||||||
|
|
||||||
|
icecast.Send(audio)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-sig:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(dt)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(time.Duration(time.Now().Sub(now)))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func chk(err error) {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
88
config/config.go
Normal file
88
config/config.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-ini/ini"
|
||||||
|
)
|
||||||
|
|
||||||
|
type server_t struct {
|
||||||
|
NAME string
|
||||||
|
ADDR string
|
||||||
|
PORT int
|
||||||
|
MOUNT string
|
||||||
|
USR string
|
||||||
|
PWD string
|
||||||
|
STYPE int
|
||||||
|
}
|
||||||
|
|
||||||
|
type ice_t struct {
|
||||||
|
NAME string
|
||||||
|
DESC string
|
||||||
|
GENRE string
|
||||||
|
URL string
|
||||||
|
IRC string
|
||||||
|
AIM string
|
||||||
|
PUB string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Track_t struct {
|
||||||
|
ID uint32
|
||||||
|
AID uint32
|
||||||
|
NAME string
|
||||||
|
MAKER string
|
||||||
|
ALBUM string
|
||||||
|
PATH string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Album_t struct {
|
||||||
|
ID uint32
|
||||||
|
NAME string
|
||||||
|
MAKER string
|
||||||
|
PATH string
|
||||||
|
COVER string
|
||||||
|
TRACKS map[uint32]Track_t
|
||||||
|
}
|
||||||
|
|
||||||
|
type archive_t struct {
|
||||||
|
PATH string
|
||||||
|
ALBUMS map[uint32]Album_t
|
||||||
|
}
|
||||||
|
|
||||||
|
type config_t struct {
|
||||||
|
Track Track_t
|
||||||
|
Server server_t
|
||||||
|
Ice ice_t
|
||||||
|
Archive archive_t
|
||||||
|
}
|
||||||
|
|
||||||
|
var Xcfg config_t
|
||||||
|
|
||||||
|
func Loadconfig(filename string) error {
|
||||||
|
|
||||||
|
ini, err := ini.Load(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
Xcfg.Server.NAME = ini.Section("server").Key("name").Value()
|
||||||
|
Xcfg.Server.ADDR = ini.Section("server").Key("addr").Value()
|
||||||
|
Xcfg.Server.PORT, _ = ini.Section("server").Key("port").Int()
|
||||||
|
Xcfg.Server.MOUNT = ini.Section("server").Key("mount").Value()
|
||||||
|
Xcfg.Server.USR = ini.Section("server").Key("usr").Value()
|
||||||
|
Xcfg.Server.PWD = ini.Section("server").Key("pwd").Value()
|
||||||
|
|
||||||
|
Xcfg.Ice.NAME = ini.Section("ice").Key("name").Value()
|
||||||
|
Xcfg.Ice.DESC = ini.Section("ice").Key("desc").Value()
|
||||||
|
Xcfg.Ice.GENRE = ini.Section("ice").Key("genre").Value()
|
||||||
|
Xcfg.Ice.URL = ini.Section("ice").Key("url").Value()
|
||||||
|
Xcfg.Ice.IRC = ini.Section("ice").Key("irc").Value()
|
||||||
|
Xcfg.Ice.AIM = ini.Section("ice").Key("aim").Value()
|
||||||
|
Xcfg.Ice.PUB = ini.Section("ice").Key("pub").Value()
|
||||||
|
|
||||||
|
Xcfg.Archive.PATH = ini.Section("archive").Key("path").Value()
|
||||||
|
|
||||||
|
|
||||||
|
Xcfg.Archive.ALBUMS = make(map[uint32]Album_t)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
205
icecast/icecast.go
Normal file
205
icecast/icecast.go
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
package icecast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"encoding/base64"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"bufio"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"io/ioutil"
|
||||||
|
"../config"
|
||||||
|
"../socket"
|
||||||
|
)
|
||||||
|
|
||||||
|
var connected bool = false
|
||||||
|
var stream_socket net.Conn
|
||||||
|
var status int = 0
|
||||||
|
var server_msg string
|
||||||
|
|
||||||
|
var pln = fmt.Println
|
||||||
|
var spf = fmt.Sprintf
|
||||||
|
|
||||||
|
|
||||||
|
func Connect() (net.Conn, error){
|
||||||
|
|
||||||
|
if connected {
|
||||||
|
return stream_socket, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var sock net.Conn
|
||||||
|
host := config.Xcfg.Server.ADDR + ":" + strconv.Itoa(config.Xcfg.Server.PORT)
|
||||||
|
sock, err := net.Dial("tcp", host)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
connected = false
|
||||||
|
return sock, err
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
// doing connection BUTT style (i.e. multiple sends)
|
||||||
|
// icecast only for now
|
||||||
|
// try PUT method
|
||||||
|
|
||||||
|
mount := config.Xcfg.Server.MOUNT
|
||||||
|
s := spf("PUT %s HTTP/1.1\r\n", mount)
|
||||||
|
if mount[0] != '/' {
|
||||||
|
s = spf("PUT /%s HTTP/1.1\r\n", mount)
|
||||||
|
}
|
||||||
|
err = socket.Send(sock, []byte(s))
|
||||||
|
|
||||||
|
s = spf("%s:%s", config.Xcfg.Server.USR, config.Xcfg.Server.PWD)
|
||||||
|
sb64 := "Authorization: Basic " + base64.StdEncoding.EncodeToString([]byte(s)) + "\r\n"
|
||||||
|
err = socket.Send(sock, []byte(sb64))
|
||||||
|
|
||||||
|
s = "User-Agent: radiodiodio/v0.0\r\n"
|
||||||
|
err = socket.Send(sock, []byte(s))
|
||||||
|
|
||||||
|
s = "Content-Type: audio/mp3\r\n"
|
||||||
|
err = socket.Send(sock, []byte(s))
|
||||||
|
|
||||||
|
s = spf("ice-name: %s\r\n", config.Xcfg.Ice.NAME)
|
||||||
|
err = socket.Send(sock, []byte(s))
|
||||||
|
|
||||||
|
s = spf("ice-public: %s\r\n", config.Xcfg.Ice.PUB)
|
||||||
|
err = socket.Send(sock, []byte(s))
|
||||||
|
|
||||||
|
s = spf("ice-url: %s\r\n", config.Xcfg.Ice.URL)
|
||||||
|
err = socket.Send(sock, []byte(s))
|
||||||
|
|
||||||
|
s = spf("ice-genre: %s\r\n", config.Xcfg.Ice.GENRE)
|
||||||
|
err = socket.Send(sock, []byte(s))
|
||||||
|
|
||||||
|
s = spf("ice-description: %s\r\n", config.Xcfg.Ice.DESC)
|
||||||
|
err = socket.Send(sock, []byte(s))
|
||||||
|
|
||||||
|
s = "ice-audio-info: ice-bitrate=192000; ice-channels=2; ice-samplerate=44100\r\n"
|
||||||
|
err = socket.Send(sock, []byte(s))
|
||||||
|
|
||||||
|
s = "\r\n"
|
||||||
|
err = socket.Send(sock, []byte(s))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
pln("error: sending PUT to the icecast server")
|
||||||
|
connected = false
|
||||||
|
return sock, err
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
resp, err := socket.Recv(sock)
|
||||||
|
if err != nil {
|
||||||
|
pln("error: receiving response from the icecast server")
|
||||||
|
connected = false
|
||||||
|
return sock, err
|
||||||
|
}
|
||||||
|
|
||||||
|
status, server_msg, err := read_http_response(resp)
|
||||||
|
|
||||||
|
if status != 200 {
|
||||||
|
connected = false
|
||||||
|
return sock, errors.New("Icecast connection failed: " + strconv.Itoa(status) + " - " + server_msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
pln("YAYAY!!")
|
||||||
|
|
||||||
|
connected = true
|
||||||
|
stream_socket = sock
|
||||||
|
|
||||||
|
return sock, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func Disconnect() {
|
||||||
|
stream_socket.Close()
|
||||||
|
connected = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func Send(buff []byte) error {
|
||||||
|
if !connected {
|
||||||
|
return errors.New("Not connected to Icecast server")
|
||||||
|
}
|
||||||
|
err := socket.Send(stream_socket, buff)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func Update() error {
|
||||||
|
|
||||||
|
host := config.Xcfg.Server.ADDR + ":" + strconv.Itoa(config.Xcfg.Server.PORT)
|
||||||
|
track := url.QueryEscape(config.Xcfg.Track.NAME)
|
||||||
|
mount := config.Xcfg.Server.MOUNT
|
||||||
|
if mount[0] != '/' {
|
||||||
|
mount = "/" + mount
|
||||||
|
}
|
||||||
|
|
||||||
|
s := spf("%s:%s", config.Xcfg.Server.USR, config.Xcfg.Server.PWD)
|
||||||
|
sb64 := "Basic " + base64.StdEncoding.EncodeToString([]byte(s))
|
||||||
|
|
||||||
|
header := "GET /admin/metadata?mode=updinfo&mount=" + mount + "&song=" + track + " HTTP/1.0\r\n" +
|
||||||
|
"User-Agent: radiodiodio/v0.0\r\n" +
|
||||||
|
"Authorization: " + sb64 + "\r\n" +
|
||||||
|
"\r\n"
|
||||||
|
|
||||||
|
pln(host)
|
||||||
|
pln(header)
|
||||||
|
|
||||||
|
var sock net.Conn
|
||||||
|
sock, err := net.Dial("tcp", host)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
err = socket.Send(sock, []byte(header))
|
||||||
|
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
defer sock.Close()
|
||||||
|
|
||||||
|
resp, err := socket.Recv(sock)
|
||||||
|
if err != nil {
|
||||||
|
pln("error: receiving response from the icecast server")
|
||||||
|
connected = false
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
status, server_msg, err := read_http_response(resp)
|
||||||
|
|
||||||
|
if status != 200 {
|
||||||
|
connected = false
|
||||||
|
return errors.New("Icecast connection failed: " + strconv.Itoa(status) + " - " + server_msg)
|
||||||
|
} else {
|
||||||
|
pln(server_msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func read_http_response(raw_resp []byte) (int, string, error) {
|
||||||
|
|
||||||
|
reader := bufio.NewReader(strings.NewReader(string(raw_resp)))
|
||||||
|
resp, err := http.ReadResponse(reader, nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
status := resp.StatusCode
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, er := ioutil.ReadAll(resp.Body)
|
||||||
|
if er != nil {
|
||||||
|
return status, "", er
|
||||||
|
}
|
||||||
|
|
||||||
|
return status, string(body), nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
181
playlist/playlist.go
Normal file
181
playlist/playlist.go
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
package playlist
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"math/rand"
|
||||||
|
"encoding/json"
|
||||||
|
"../archive"
|
||||||
|
"../config"
|
||||||
|
)
|
||||||
|
|
||||||
|
var pln = fmt.Println
|
||||||
|
|
||||||
|
type Playlist struct {
|
||||||
|
NAME string
|
||||||
|
CTRACK config.Track_t
|
||||||
|
CALBUM config.Album_t
|
||||||
|
LIST []uint32
|
||||||
|
MAX int
|
||||||
|
}
|
||||||
|
|
||||||
|
type PrettyPlaylist struct {
|
||||||
|
NAME string
|
||||||
|
CTRACK config.Track_t
|
||||||
|
CALBUM config.Album_t
|
||||||
|
LIST []config.Track_t
|
||||||
|
MAX int
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func MakeRandom(name string, max int) (*Playlist, error) {
|
||||||
|
err := archive.Build()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &Playlist{NAME: name, MAX: max}
|
||||||
|
p.LIST = make([]uint32, max)
|
||||||
|
|
||||||
|
for i := 0; i < max; i++ {
|
||||||
|
r, _ := Random()
|
||||||
|
p.LIST[i] = r // might duplicate
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Playlist) Encode() string {
|
||||||
|
|
||||||
|
pp := p.Pretty()
|
||||||
|
res, _ := json.Marshal(pp)
|
||||||
|
return string(res)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func Decode(jsonstr string) (*Playlist, error) {
|
||||||
|
|
||||||
|
pp := &PrettyPlaylist{}
|
||||||
|
|
||||||
|
if err := json.Unmarshal([]byte(jsonstr), pp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p := pp.Unpretty()
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Playlist) Pretty() *PrettyPlaylist {
|
||||||
|
pp := &PrettyPlaylist{NAME: p.NAME, CTRACK: p.CTRACK, CALBUM: p.CALBUM, MAX: p.MAX}
|
||||||
|
pp.LIST = make([]config.Track_t, len(p.LIST))
|
||||||
|
for i := 0; i < len(p.LIST); i++ {
|
||||||
|
pp.LIST[i] = archive.Archive_map[p.LIST[i]]
|
||||||
|
}
|
||||||
|
return pp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pp *PrettyPlaylist) Unpretty() *Playlist {
|
||||||
|
p := &Playlist{NAME: pp.NAME, CTRACK: pp.CTRACK, CALBUM: pp.CALBUM, MAX: pp.MAX}
|
||||||
|
p.LIST = make([]uint32, len(pp.LIST))
|
||||||
|
for i := 0; i < len(pp.LIST); i++ {
|
||||||
|
p.LIST = append(p.LIST, pp.LIST[i].ID)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// type PrettyPlaylist struct {
|
||||||
|
// NAME string
|
||||||
|
// CTRACK config.Track_t
|
||||||
|
// CALBUM config.Album_t
|
||||||
|
// LIST []config.Track_t
|
||||||
|
// MAX int
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
func (pp *PrettyPlaylist) Print() {
|
||||||
|
|
||||||
|
pln("Name: " + pp.NAME)
|
||||||
|
pln("CTRACK: " + pp.CTRACK.NAME)
|
||||||
|
pln("CALBUM: " + pp.CALBUM.NAME)
|
||||||
|
pln("Next:")
|
||||||
|
for i, r := range pp.LIST {
|
||||||
|
pln(" " + strconv.Itoa(i) + " - " + r.NAME)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/33834742/remove-and-adding-elements-to-array-in-go-lang
|
||||||
|
|
||||||
|
func (p *Playlist) Pop() error {
|
||||||
|
if len(p.LIST) == 0 {
|
||||||
|
return errors.New("Playlist is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
t := p.LIST[0]
|
||||||
|
if len(p.LIST) < 2 {
|
||||||
|
p.LIST = make([]uint32, 0)
|
||||||
|
} else {
|
||||||
|
p.LIST = p.LIST[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
p.CTRACK = archive.Archive_map[t]
|
||||||
|
p.CALBUM = config.Xcfg.Archive.ALBUMS[p.CTRACK.AID]
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Playlist) Push(track_id uint32) error {
|
||||||
|
if len(p.LIST) == p.MAX {
|
||||||
|
return errors.New("Playlist is full")
|
||||||
|
}
|
||||||
|
p.LIST = append(p.LIST, track_id)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Playlist) PushFront(track_id uint32) error {
|
||||||
|
if len(p.LIST) == p.MAX {
|
||||||
|
return errors.New("Playlist is full")
|
||||||
|
}
|
||||||
|
p.LIST = append([]uint32{ track_id }, p.LIST...) // don't forget '...''
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Playlist) Insert(track_id uint32, at_index int) error {
|
||||||
|
if at_index > p.MAX {
|
||||||
|
return errors.New("Invalid insert index")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.LIST = append(p.LIST, 0)
|
||||||
|
copy(p.LIST[at_index+1:], p.LIST[at_index:])
|
||||||
|
p.LIST[at_index] = track_id
|
||||||
|
|
||||||
|
if len(p.LIST) > p.MAX {
|
||||||
|
p.LIST = p.LIST[:p.MAX]
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Playlist) Delete(track_id uint32) error {
|
||||||
|
|
||||||
|
k := -1
|
||||||
|
for i := 0; i <= len(p.LIST); i++ {
|
||||||
|
if p.LIST[i] == track_id { k = i }
|
||||||
|
}
|
||||||
|
|
||||||
|
if k < 0 {
|
||||||
|
return errors.New("Invalid track ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.LIST = append(p.LIST[:k-1], p.LIST[k+1:]...)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func Random() (uint32, string) {
|
||||||
|
rand.Seed(time.Now().UTC().UnixNano())
|
||||||
|
index := archive.Archive_map_keys[rand.Intn(len(archive.Archive_map_keys) - 1)]
|
||||||
|
return archive.Archive_map[index].ID, archive.Archive_map[index].NAME
|
||||||
|
}
|
||||||
20
serve.go
Normal file
20
serve.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"./config"
|
||||||
|
"./playlist"
|
||||||
|
"./www"
|
||||||
|
)
|
||||||
|
|
||||||
|
var pln = fmt.Println
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
config.Loadconfig("config.ini")
|
||||||
|
p, _ := playlist.MakeRandom("YOYO", 10)
|
||||||
|
p.Pop()
|
||||||
|
www.Init(p)
|
||||||
|
log.Fatal(http.ListenAndServe(":8718", nil))
|
||||||
|
}
|
||||||
26
socket/socket.go
Normal file
26
socket/socket.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package socket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Send(sock net.Conn, buf []byte) error {
|
||||||
|
n, err := sock.Write(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n != len(buf) {
|
||||||
|
return errors.New("error: socket send")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Recv(sock net.Conn) ([]byte, error) {
|
||||||
|
var buf []byte = make([]byte, 1024)
|
||||||
|
n, err := sock.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buf[0:n], err
|
||||||
|
}
|
||||||
69
www/tmpl/playlist.html
Normal file
69
www/tmpl/playlist.html
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
|
||||||
|
<style type="text/css">
|
||||||
|
#sortable { list-style-type: none; margin: 10; padding: 10; width: 60%; }
|
||||||
|
#sortable li { margin: 0 3px 3px 3px; padding: 0.4em; padding-left: 1.5em; height: 18px; }
|
||||||
|
#sortable li span { position: absolute; margin-left: -1.3em; }
|
||||||
|
</style>
|
||||||
|
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
|
||||||
|
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
|
||||||
|
<script>
|
||||||
|
$( function() {
|
||||||
|
$( "#sortable" ).sortable({
|
||||||
|
update: function (e, u) {
|
||||||
|
console.log("update")
|
||||||
|
list = Array.from(document.querySelectorAll('#sortable>li'));
|
||||||
|
var i = list.indexOf(u.item[0])
|
||||||
|
console.log(u.item.attr("id") + " is now " + i)
|
||||||
|
on_update_operation(u.item.attr("id"), i)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$( "#sortable" ).disableSelection();
|
||||||
|
|
||||||
|
// connect to ppop
|
||||||
|
sock = new WebSocket("ws://localhost:8718/ppop");
|
||||||
|
sock.onopen = function() {
|
||||||
|
console.log("ppop open")
|
||||||
|
}
|
||||||
|
sock.onclose = function() {
|
||||||
|
console.log("ppop closed")
|
||||||
|
sock = new WebSocket("ws://localhost:8718/ppop");
|
||||||
|
}
|
||||||
|
|
||||||
|
sock.onmessage = function(msg) {
|
||||||
|
console.log(msg.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setInterval(function() {
|
||||||
|
// sock.send(JSON.stringify({"op": "tick", "id": "0", "index": 0}));
|
||||||
|
// },3000);
|
||||||
|
|
||||||
|
} );
|
||||||
|
var sock = null;
|
||||||
|
function on_update_operation(id, index) {
|
||||||
|
if(sock) {
|
||||||
|
console.log("sending")
|
||||||
|
sock.send(JSON.stringify({"op": "update", "id": id, "index": index}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<title>Radiodiodio</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Playlist: {{.NAME}}</h1>
|
||||||
|
|
||||||
|
<h2>Current Track: {{.CTRACK.NAME}} </h2>
|
||||||
|
<h2>Current Album : {{.CALBUM.NAME}} - {{.CALBUM.MAKER}}</h2>
|
||||||
|
|
||||||
|
<ul id="sortable">
|
||||||
|
{{range $.LIST}}
|
||||||
|
<li class="ui-state-default" id="{{.ID}}"><span>{{.MAKER}} - {{.ALBUM}} - {{.NAME}}</span></li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
81
www/www.go
Normal file
81
www/www.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package www
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
// "golang.org/x/net/websocket"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"../playlist"
|
||||||
|
)
|
||||||
|
|
||||||
|
// todo: hub - https://stackoverflow.com/questions/31532652/go-websocket-send-all-clients-a-message
|
||||||
|
|
||||||
|
var pp *playlist.PrettyPlaylist
|
||||||
|
|
||||||
|
var pln = log.Println
|
||||||
|
|
||||||
|
type op_t struct {
|
||||||
|
OP string `json:"op"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
INDEX int `json:"index"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var upgrader = websocket.Upgrader{
|
||||||
|
ReadBufferSize: 512,
|
||||||
|
WriteBufferSize: 512,
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(p *playlist.Playlist) {
|
||||||
|
pp = p.Pretty()
|
||||||
|
http.HandleFunc("/pp", pp_handler)
|
||||||
|
http.HandleFunc("/ppop", pp_operations)
|
||||||
|
}
|
||||||
|
|
||||||
|
func pp_handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
t, err := template.ParseFiles("./www/tmpl/playlist.html")
|
||||||
|
if err != nil {
|
||||||
|
pln(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pp.Print()
|
||||||
|
t.Execute(w, pp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func pp_operations(w http.ResponseWriter, r *http.Request) {
|
||||||
|
pln("x")
|
||||||
|
c, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
pln(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go readop(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readop(c *websocket.Conn) {
|
||||||
|
for {
|
||||||
|
opdata := &op_t{}
|
||||||
|
if err := c.ReadJSON(&opdata); err != nil {
|
||||||
|
pln(err)
|
||||||
|
return //connection lost?
|
||||||
|
}
|
||||||
|
pln(opdata.OP)
|
||||||
|
pln(opdata.ID)
|
||||||
|
pln(opdata.INDEX)
|
||||||
|
if err := c.WriteJSON(opdata); err != nil {
|
||||||
|
pln(err)
|
||||||
|
return //connection lost?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// func pp_operations(ws *websocket.Conn) {
|
||||||
|
// opdata := &op_t{}
|
||||||
|
// if err := websocket.JSON.Receive(ws, &opdata); err != nil {
|
||||||
|
// pln(err)
|
||||||
|
// }
|
||||||
|
// // websocket.JSON.Send(ws, "ok")
|
||||||
|
// pln(opdata.OP)
|
||||||
|
// pln(opdata.ID)
|
||||||
|
// pln(opdata.INDEX)
|
||||||
|
// }
|
||||||
Loading…
x
Reference in New Issue
Block a user