haha
This commit is contained in:
commit
816c2b593c
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)
|
||||
}
|
||||
}
|
||||
22
config.ini
Normal file
22
config.ini
Normal file
@ -0,0 +1,22 @@
|
||||
[server]
|
||||
|
||||
name = iceice-server
|
||||
addr = 192.168.1.80
|
||||
port = 8088
|
||||
mount = wwww
|
||||
usr = source
|
||||
pwd = R1t4R1t4
|
||||
|
||||
[ice]
|
||||
|
||||
name = noise
|
||||
desc = description of noize
|
||||
genre = lmnop
|
||||
url = https://subject041293.xyz
|
||||
irc = nope
|
||||
aim = nope
|
||||
pub = 0
|
||||
|
||||
[archive]
|
||||
|
||||
path = /Volumes/QQQ/wellwellwell/mp3
|
||||
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