|
@@ -2,16 +2,26 @@ package main
|
|
|
|
|
|
import (
|
|
|
"encoding/json"
|
|
|
+ "log"
|
|
|
"net/http"
|
|
|
+ "os"
|
|
|
+ "os/exec"
|
|
|
+ "path/filepath"
|
|
|
+ "strconv"
|
|
|
+ "strings"
|
|
|
+ "time"
|
|
|
+
|
|
|
+ "github.com/gabriel-vasile/mimetype"
|
|
|
)
|
|
|
|
|
|
type Status struct {
|
|
|
- Playing bool //If the device is playing
|
|
|
- PlayingFileName string //The playing filename
|
|
|
- PlayingFileURL string //The playing URL
|
|
|
- PlayingPosition int //The number of seconds that is currently playing
|
|
|
- Volume int //The volume currently is playing at, in percentage
|
|
|
- Status string //Status of the device, support {downloading, converting, playing, paused, ready}
|
|
|
+ Playing bool //If the device is playing
|
|
|
+ Loop bool //If playback as loop
|
|
|
+ PlayingFileName string //The playing filename
|
|
|
+ PlayingURL string //The playing URL
|
|
|
+ Gain float64 //The volume currently is playing at, in percentage
|
|
|
+ Mute bool //If the playback should be muted
|
|
|
+ Status string //Status of the device, support {downloading, converting, playing, paused, ready}
|
|
|
}
|
|
|
|
|
|
type Endpoint struct {
|
|
@@ -27,6 +37,12 @@ type Endpoint struct {
|
|
|
Regex string
|
|
|
}
|
|
|
|
|
|
+type SongData struct {
|
|
|
+ Filename string
|
|
|
+ Filesize int64
|
|
|
+ Ext string
|
|
|
+}
|
|
|
+
|
|
|
func handleIndex(w http.ResponseWriter, r *http.Request) {
|
|
|
//Serve the index
|
|
|
sendOK(w)
|
|
@@ -55,31 +71,226 @@ func handleEndpoints(w http.ResponseWriter, r *http.Request) {
|
|
|
})
|
|
|
|
|
|
endpoints = append(endpoints, Endpoint{
|
|
|
- Name: "Load",
|
|
|
+ Name: "PlayingURL",
|
|
|
RelPath: "load",
|
|
|
Desc: "Load a network resources to play",
|
|
|
Type: "string",
|
|
|
})
|
|
|
|
|
|
endpoints = append(endpoints, Endpoint{
|
|
|
- Name: "Volume",
|
|
|
- RelPath: "vol",
|
|
|
- Desc: "Set the volume of the device playback in percentage",
|
|
|
- Type: "integer",
|
|
|
- Max: 100,
|
|
|
- Min: 0,
|
|
|
- Steps: 1,
|
|
|
+ Name: "Loop",
|
|
|
+ RelPath: "loop",
|
|
|
+ Desc: "Playback in normal or loop mode",
|
|
|
+ Type: "bool",
|
|
|
+ })
|
|
|
+
|
|
|
+ endpoints = append(endpoints, Endpoint{
|
|
|
+ Name: "Mute",
|
|
|
+ RelPath: "mute",
|
|
|
+ Desc: "Mute the playback",
|
|
|
+ Type: "bool",
|
|
|
+ })
|
|
|
+
|
|
|
+ endpoints = append(endpoints, Endpoint{
|
|
|
+ Name: "FileData",
|
|
|
+ RelPath: "name",
|
|
|
+ Desc: "Set the file data of the playing song",
|
|
|
+ Type: "string",
|
|
|
})
|
|
|
|
|
|
endpoints = append(endpoints, Endpoint{
|
|
|
- Name: "Jump",
|
|
|
- RelPath: "jump",
|
|
|
- Desc: "Jump to a given location on the track",
|
|
|
+ Name: "Gain",
|
|
|
+ RelPath: "gain",
|
|
|
+ Desc: "Set the gain of the device playback, 0 means default",
|
|
|
Type: "integer",
|
|
|
- Steps: 1,
|
|
|
+ Max: 2,
|
|
|
+ Min: -6,
|
|
|
+ Steps: 0.1,
|
|
|
})
|
|
|
|
|
|
js, _ := json.Marshal(endpoints)
|
|
|
sendJSONResponse(w, string(js))
|
|
|
|
|
|
}
|
|
|
+
|
|
|
+func handlePlayToggle(w http.ResponseWriter, r *http.Request) {
|
|
|
+ deviceStatus.Playing = !deviceStatus.Playing
|
|
|
+ if deviceStatus.Playing == true {
|
|
|
+ deviceStatus.Status = "playing"
|
|
|
+ } else {
|
|
|
+ deviceStatus.Status = "paused"
|
|
|
+ }
|
|
|
+ sendOK(w)
|
|
|
+}
|
|
|
+
|
|
|
+func handleStop(w http.ResponseWriter, r *http.Request) {
|
|
|
+ globalStopFlag = true
|
|
|
+}
|
|
|
+
|
|
|
+func handleMute(w http.ResponseWriter, r *http.Request) {
|
|
|
+ value, err := mv(r, "value", true)
|
|
|
+ if err != nil {
|
|
|
+ sendErrorResponse(w, "Invalid value given")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if value == "true" {
|
|
|
+ deviceStatus.Mute = true
|
|
|
+ } else {
|
|
|
+ deviceStatus.Mute = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func handleLoad(w http.ResponseWriter, r *http.Request) {
|
|
|
+ _, err := mv(r, "value", false)
|
|
|
+ if err != nil {
|
|
|
+ //Nothing is passed in
|
|
|
+ sendErrorResponse(w, "Invalid value given")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ //There is something here. But for full request purposes, use raw input and split it
|
|
|
+ urlChunks := strings.Split(r.RequestURI, "?value=")
|
|
|
+ finalResourcesEndpoing := strings.Join(urlChunks[1:], "?value=")
|
|
|
+
|
|
|
+ //Download the file
|
|
|
+ deviceStatus.Status = "downloading"
|
|
|
+ tmpFilename := strconv.Itoa(int(time.Now().Unix()))
|
|
|
+ downloadedFile := filepath.Join("./tmp", tmpFilename)
|
|
|
+
|
|
|
+ os.Mkdir("./tmp", 0775)
|
|
|
+ log.Println("Downlaod audio file from: ", finalResourcesEndpoing)
|
|
|
+ err = DownloadFile(downloadedFile, finalResourcesEndpoing)
|
|
|
+ if err != nil {
|
|
|
+ log.Println(err.Error())
|
|
|
+ sendErrorResponse(w, err.Error())
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ //Check if this is a valid audio file
|
|
|
+ mime, err := mimetype.DetectFile(downloadedFile)
|
|
|
+ if err != nil {
|
|
|
+ log.Println(err.Error())
|
|
|
+ sendErrorResponse(w, err.Error())
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if strings.Contains(mime.String(), "audio/") {
|
|
|
+ log.Println("Download file is " + mime.String() + ". Starting Convertsion...")
|
|
|
+ } else {
|
|
|
+ log.Println("Not supported media type")
|
|
|
+ sendErrorResponse(w, "Not supported media type")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ //Set the correct extension for the file
|
|
|
+ var fileMime = strings.Split(mime.String(), "/")[1]
|
|
|
+ var needConv = true
|
|
|
+ var finalFilename = ""
|
|
|
+ if fileMime == "mpeg" {
|
|
|
+ //mp3 file
|
|
|
+ os.Rename(downloadedFile, downloadedFile+".mp3")
|
|
|
+ finalFilename = downloadedFile + ".mp3"
|
|
|
+ } else if fileMime == "flac" {
|
|
|
+ //flac
|
|
|
+ os.Rename(downloadedFile, downloadedFile+".flac")
|
|
|
+ finalFilename = downloadedFile + ".flac"
|
|
|
+ } else if fileMime == "wav" {
|
|
|
+ //wav file
|
|
|
+ os.Rename(downloadedFile, downloadedFile+".wav")
|
|
|
+ finalFilename = downloadedFile + ".wav"
|
|
|
+ needConv = false
|
|
|
+ } else {
|
|
|
+ //Unknown. Just append the mime to it
|
|
|
+ os.Rename(downloadedFile, downloadedFile+"."+fileMime)
|
|
|
+ finalFilename = downloadedFile + "." + fileMime
|
|
|
+ }
|
|
|
+
|
|
|
+ //Start the conversion process (As we need to force the device only playback wav for compeitbility purposes)
|
|
|
+ playReadyFile := filepath.Join("./tmp", strings.TrimSuffix(filepath.Base(downloadedFile), filepath.Ext(filepath.Base(downloadedFile)))+".wav")
|
|
|
+
|
|
|
+ if needConv {
|
|
|
+ log.Println("Conversion started")
|
|
|
+ deviceStatus.Status = "converting"
|
|
|
+
|
|
|
+ cmd := exec.Command("ffmpeg", "-i", finalFilename, playReadyFile)
|
|
|
+ out, err := cmd.CombinedOutput()
|
|
|
+ if err != nil {
|
|
|
+ log.Println("Conversion failed: ", string(out))
|
|
|
+ sendErrorResponse(w, err.Error())
|
|
|
+ return
|
|
|
+ }
|
|
|
+ log.Println(string(out))
|
|
|
+ }
|
|
|
+
|
|
|
+ //Start playing the file
|
|
|
+ log.Println("Conversion finished. Playing audio file.")
|
|
|
+ deviceStatus.Status = "playing" //Set status text to playing
|
|
|
+ deviceStatus.Playing = true //Unpause the playback
|
|
|
+ deviceStatus.PlayingURL = finalResourcesEndpoing //Set the playing URL to target url
|
|
|
+
|
|
|
+ playFile(playReadyFile)
|
|
|
+}
|
|
|
+
|
|
|
+func handleVol(w http.ResponseWriter, r *http.Request) {
|
|
|
+ val, err := mv(r, "value", false)
|
|
|
+ if err != nil {
|
|
|
+ sendErrorResponse(w, "Invalid gain set")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ //Parse the string to float
|
|
|
+ if s, err := strconv.ParseFloat(val, 64); err == nil {
|
|
|
+ if s > 2 || s < -6 {
|
|
|
+ sendErrorResponse(w, "Invalid ranged gain value")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ //Update the new volume gain
|
|
|
+ deviceStatus.Gain = s
|
|
|
+ } else {
|
|
|
+ sendErrorResponse(w, "Unable to parse new value")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ sendOK(w)
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+//Handle set name of the playing file. No logical purpose, just for display only
|
|
|
+//Require struct matching. See the SongInfo struct
|
|
|
+//Remember to pass it in as encodedURL string
|
|
|
+func handleSetName(w http.ResponseWriter, r *http.Request) {
|
|
|
+ //Get the song data
|
|
|
+ value, err := mv(r, "value", true)
|
|
|
+ if err != nil {
|
|
|
+ sendErrorResponse(w, "value invalid")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ //Parse the song data
|
|
|
+ thisSongdata := SongData{}
|
|
|
+ err = json.Unmarshal([]byte(value), &thisSongdata)
|
|
|
+ if err != nil {
|
|
|
+ sendErrorResponse(w, "Failed to parse song data.")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ deviceStatus.PlayingFileName = thisSongdata.Filename
|
|
|
+ playingSongData = thisSongdata
|
|
|
+
|
|
|
+ sendOK(w)
|
|
|
+}
|
|
|
+
|
|
|
+func handleLoop(w http.ResponseWriter, r *http.Request) {
|
|
|
+ value, err := mv(r, "value", true)
|
|
|
+ if err != nil {
|
|
|
+ sendErrorResponse(w, "value invalid")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if value == "true" {
|
|
|
+ deviceStatus.Loop = true
|
|
|
+ } else {
|
|
|
+ deviceStatus.Loop = false
|
|
|
+ }
|
|
|
+}
|