- add documentation
- add more info to README.MD - splited into multiple files
This commit is contained in:
		
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,2 +1,3 @@
 | 
			
		||||
*.exe
 | 
			
		||||
sounds/**
 | 
			
		||||
sounds/**
 | 
			
		||||
conf.json
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										28
									
								
								README.MD
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								README.MD
									
									
									
									
									
								
							@@ -1,2 +1,28 @@
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
# Soundr
 | 
			
		||||
An opensource audio server meant for professional applications
 | 
			
		||||
Soundr is a simple, open-source, cross-platform audio playing server written in go.
 | 
			
		||||
It aims to be simple to setup and work in many envoriments. It is also designed to be
 | 
			
		||||
easy to use and maintain. 
 | 
			
		||||
Soundr is able to play multiple audio files at the same time. It is able to intigrate well as it uses a REST endpoint.
 | 
			
		||||
Swagger Documentation for that endpoint is available in `apiDocs.yml`.
 | 
			
		||||
The software it self is written in go and uses the BEEP library. It is made to be shipped as a single executable.
 | 
			
		||||
Another target was a minimal dependency tree.
 | 
			
		||||
Initally it was written to be used with [Bitfocus Companion](https://bitfocus.io/companion) in a more professional envoriment. (A client for Companion is currently WiP)
 | 
			
		||||
 | 
			
		||||
# Installation
 | 
			
		||||
Installation is as simple as it gets as it is a single executable.
 | 
			
		||||
Download one of the releases, drop your sounds into the /sounds folder and run the executable.
 | 
			
		||||
 | 
			
		||||
# Configuration
 | 
			
		||||
TODO, no config yet. Soon ports and other settings will be added.
 | 
			
		||||
 | 
			
		||||
# Usage
 | 
			
		||||
Drop your sounds into the /sounds. You can play them by sending a GET request to the /v1/play endpoint.
 | 
			
		||||
You need to know the base64 encoded file name of the sound you want to play. You can get started by querying /v1/list. It will return a list of all sounds with their respective base64 encoded file name.
 | 
			
		||||
Use that base64 as the `file` parameter in the request.
 | 
			
		||||
 | 
			
		||||
**Note**: The sounds must be in the format `*.mp3` (more will be supported soon :tm:).
 | 
			
		||||
 | 
			
		||||
# ToDo
 | 
			
		||||
- [ ] Add support for other audio formats
 | 
			
		||||
							
								
								
									
										144
									
								
								apiDocs.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								apiDocs.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,144 @@
 | 
			
		||||
openapi: '3.0.2'
 | 
			
		||||
info:
 | 
			
		||||
  title: Soundr
 | 
			
		||||
  version: '1.0'
 | 
			
		||||
servers:
 | 
			
		||||
  - url: http://localhost:8082/v1/
 | 
			
		||||
paths:
 | 
			
		||||
  /play:
 | 
			
		||||
    get:
 | 
			
		||||
      summary: Plays a sound by it's base64'd name. Will load it to buffer first if not already loaded.
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: file
 | 
			
		||||
          description: A base64 encoded version of the file name
 | 
			
		||||
          schema:
 | 
			
		||||
            type: string
 | 
			
		||||
      responses:
 | 
			
		||||
        '200':
 | 
			
		||||
          description: OK
 | 
			
		||||
          content:
 | 
			
		||||
            application/json:
 | 
			
		||||
              schema:
 | 
			
		||||
                type: object
 | 
			
		||||
                properties:
 | 
			
		||||
                  id:
 | 
			
		||||
                    type: number
 | 
			
		||||
                    description: The ID of the playing sound
 | 
			
		||||
        '400':
 | 
			
		||||
          description: Bad Request
 | 
			
		||||
          content:
 | 
			
		||||
            application/json:
 | 
			
		||||
              schema:
 | 
			
		||||
                type: object
 | 
			
		||||
                properties:
 | 
			
		||||
                  reason:
 | 
			
		||||
                    type: string
 | 
			
		||||
                    description: The error message, in this case probably "file not found"
 | 
			
		||||
  /buffer:
 | 
			
		||||
    get:
 | 
			
		||||
      summary: Loads a sound into the buffer.
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: file
 | 
			
		||||
          description: A base64 encoded version of the file name
 | 
			
		||||
          schema:
 | 
			
		||||
            type: string
 | 
			
		||||
      responses:
 | 
			
		||||
        '200':
 | 
			
		||||
          description: OK
 | 
			
		||||
          
 | 
			
		||||
        '400':
 | 
			
		||||
          description: Bad Request
 | 
			
		||||
          content:
 | 
			
		||||
            application/json:
 | 
			
		||||
              schema:
 | 
			
		||||
                type: object
 | 
			
		||||
                properties:
 | 
			
		||||
                  reason:
 | 
			
		||||
                    type: string
 | 
			
		||||
                    description: The error message, in this case probably "file not found"
 | 
			
		||||
  /bufferAll:
 | 
			
		||||
    get:
 | 
			
		||||
      summary: Loads all sounds into the buffer.
 | 
			
		||||
      responses:
 | 
			
		||||
        '200':
 | 
			
		||||
          description: OK
 | 
			
		||||
  /stop:
 | 
			
		||||
    get:
 | 
			
		||||
      summary: Stops a given sound by it's ID.
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: id
 | 
			
		||||
          description: The ID of the sound to stop
 | 
			
		||||
          schema:
 | 
			
		||||
            type: number
 | 
			
		||||
      responses:
 | 
			
		||||
        '200':
 | 
			
		||||
          description: OK
 | 
			
		||||
        '400':
 | 
			
		||||
          description: Bad Request
 | 
			
		||||
          content:
 | 
			
		||||
            application/json:
 | 
			
		||||
              schema:
 | 
			
		||||
                type: object
 | 
			
		||||
                properties:
 | 
			
		||||
                  reason:
 | 
			
		||||
                    type: string
 | 
			
		||||
                    description: The error message
 | 
			
		||||
  /stopAll:
 | 
			
		||||
    get:
 | 
			
		||||
      summary: Stops all sounds.
 | 
			
		||||
      responses:
 | 
			
		||||
        '200':
 | 
			
		||||
          description: OK
 | 
			
		||||
  /current:
 | 
			
		||||
    get:
 | 
			
		||||
      summary: Gets the current playing sound(s).
 | 
			
		||||
      responses:
 | 
			
		||||
        '200':
 | 
			
		||||
          description: OK
 | 
			
		||||
          content:
 | 
			
		||||
            application/json:
 | 
			
		||||
              schema:
 | 
			
		||||
                type: object
 | 
			
		||||
                properties:
 | 
			
		||||
                  sounds:
 | 
			
		||||
                    type: array
 | 
			
		||||
                    items:
 | 
			
		||||
                      type: object
 | 
			
		||||
                      properties:
 | 
			
		||||
                        id:
 | 
			
		||||
                          type: number
 | 
			
		||||
                          description: The ID of the sound
 | 
			
		||||
                        name:
 | 
			
		||||
                          type: string
 | 
			
		||||
                          description: The name of the sound
 | 
			
		||||
                        loaded:
 | 
			
		||||
                          type: boolean
 | 
			
		||||
                          description: Whether the sound is loaded into the buffer
 | 
			
		||||
  /list:
 | 
			
		||||
   get: # TODO REWORK!!!!!
 | 
			
		||||
    summary: Lists all sounds in the buffer.
 | 
			
		||||
    responses:
 | 
			
		||||
      '200':
 | 
			
		||||
        description: OK
 | 
			
		||||
        content:
 | 
			
		||||
          application/json:
 | 
			
		||||
            schema:
 | 
			
		||||
              type: object
 | 
			
		||||
              properties:
 | 
			
		||||
                sounds:
 | 
			
		||||
                  type: array
 | 
			
		||||
                  items:
 | 
			
		||||
                    type: object
 | 
			
		||||
                    properties:
 | 
			
		||||
                      name:
 | 
			
		||||
                        type: string
 | 
			
		||||
                        description: The name of the sound
 | 
			
		||||
                      base64:
 | 
			
		||||
                        type: string
 | 
			
		||||
                        description: The base64 version of the name
 | 
			
		||||
                      url:
 | 
			
		||||
                        type: string
 | 
			
		||||
                        description: The URL to the sound
 | 
			
		||||
							
								
								
									
										78
									
								
								handlerFunctions.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								handlerFunctions.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"os"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/faiface/beep"
 | 
			
		||||
	"github.com/faiface/beep/mp3"
 | 
			
		||||
	"github.com/faiface/beep/speaker"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func BufferSound(file string) bool {
 | 
			
		||||
	_, ok := streamMap[file]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		fmt.Println("Not in memory, loading")
 | 
			
		||||
		f, err := os.Open("./sounds/" + file)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fmt.Println("Opened file")
 | 
			
		||||
		streamer, format, _ := mp3.Decode(f)
 | 
			
		||||
		speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
 | 
			
		||||
 | 
			
		||||
		fmt.Println("Decoded file")
 | 
			
		||||
		buffer := beep.NewBuffer(format)
 | 
			
		||||
		buffer.Append(streamer)
 | 
			
		||||
		streamer.Close()
 | 
			
		||||
		fmt.Println("Bufferd file")
 | 
			
		||||
 | 
			
		||||
		// Save to streamMap
 | 
			
		||||
		streamMap[file] = streamBuf{
 | 
			
		||||
			Streamer: streamer,
 | 
			
		||||
			Format:   format,
 | 
			
		||||
			Buffer:   buffer,
 | 
			
		||||
		}
 | 
			
		||||
		return (true)
 | 
			
		||||
	} else {
 | 
			
		||||
		return (false)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func PlaySound(file string, index int) int {
 | 
			
		||||
	playbacks[index] = playback{
 | 
			
		||||
		File:     file,
 | 
			
		||||
		IsLoaded: false,
 | 
			
		||||
		Streamer: nil,
 | 
			
		||||
		Control:  nil,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Playing sound: " + file)
 | 
			
		||||
	var buffer *beep.Buffer
 | 
			
		||||
	BufferSound(file)
 | 
			
		||||
	buffer = streamMap[file].Buffer
 | 
			
		||||
	streamer := streamMap[file].Streamer
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Trying to play sound")
 | 
			
		||||
	shot := buffer.Streamer(0, buffer.Len())
 | 
			
		||||
 | 
			
		||||
	done := make(chan bool)
 | 
			
		||||
	ctrl := &beep.Ctrl{Streamer: beep.Seq(shot, beep.Callback(func() {
 | 
			
		||||
		done <- true
 | 
			
		||||
	})), Paused: false}
 | 
			
		||||
 | 
			
		||||
	playbacks[index] = playback{
 | 
			
		||||
		File:     file,
 | 
			
		||||
		IsLoaded: true,
 | 
			
		||||
		Streamer: streamer,
 | 
			
		||||
		Control:  ctrl,
 | 
			
		||||
	}
 | 
			
		||||
	speaker.Play(ctrl)
 | 
			
		||||
	<-done
 | 
			
		||||
	fmt.Println("Finished playing sound: " + file)
 | 
			
		||||
	delete(playbacks, index)
 | 
			
		||||
	return 1
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										181
									
								
								resources/logo.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								resources/logo.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
		 After Width: | Height: | Size: 29 KiB  | 
							
								
								
									
										236
									
								
								soundr.go
									
									
									
									
									
								
							
							
						
						
									
										236
									
								
								soundr.go
									
									
									
									
									
								
							@@ -1,22 +1,14 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"html"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/faiface/beep"
 | 
			
		||||
	"github.com/faiface/beep/mp3"
 | 
			
		||||
	"github.com/faiface/beep/speaker"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type playback struct {
 | 
			
		||||
@@ -38,198 +30,66 @@ type streamBuf struct {
 | 
			
		||||
	Buffer   *beep.Buffer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Configuration struct {
 | 
			
		||||
	Port int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var playbacks map[int]playback
 | 
			
		||||
var mapMutex = sync.Mutex{}
 | 
			
		||||
 | 
			
		||||
var streamMap map[string]streamBuf
 | 
			
		||||
 | 
			
		||||
func BufferSound(file string) bool {
 | 
			
		||||
	_, ok := streamMap[file]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		fmt.Println("Not in memory, loading")
 | 
			
		||||
		f, err := os.Open("./sounds/" + file)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fmt.Println("Opened file")
 | 
			
		||||
		streamer, format, err := mp3.Decode(f)
 | 
			
		||||
		speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
 | 
			
		||||
 | 
			
		||||
		fmt.Println("Decoded file")
 | 
			
		||||
		buffer := beep.NewBuffer(format)
 | 
			
		||||
		buffer.Append(streamer)
 | 
			
		||||
		streamer.Close()
 | 
			
		||||
		fmt.Println("Bufferd file")
 | 
			
		||||
 | 
			
		||||
		// Save to streamMap
 | 
			
		||||
		streamMap[file] = streamBuf{
 | 
			
		||||
			Streamer: streamer,
 | 
			
		||||
			Format:   format,
 | 
			
		||||
			Buffer:   buffer,
 | 
			
		||||
		}
 | 
			
		||||
		return (true)
 | 
			
		||||
	} else {
 | 
			
		||||
		return (false)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func PlaySound(file string, index int) int {
 | 
			
		||||
	playbacks[index] = playback{
 | 
			
		||||
		File:     file,
 | 
			
		||||
		IsLoaded: false,
 | 
			
		||||
		Streamer: nil,
 | 
			
		||||
		Control:  nil,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Playing sound: " + file)
 | 
			
		||||
	var buffer *beep.Buffer
 | 
			
		||||
	BufferSound(file)
 | 
			
		||||
	buffer = streamMap[file].Buffer
 | 
			
		||||
	streamer := streamMap[file].Streamer
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Trying to play sound")
 | 
			
		||||
	shot := buffer.Streamer(0, buffer.Len())
 | 
			
		||||
 | 
			
		||||
	done := make(chan bool)
 | 
			
		||||
	ctrl := &beep.Ctrl{Streamer: beep.Seq(shot, beep.Callback(func() {
 | 
			
		||||
		done <- true
 | 
			
		||||
	})), Paused: false}
 | 
			
		||||
 | 
			
		||||
	playbacks[index] = playback{
 | 
			
		||||
		File:     file,
 | 
			
		||||
		IsLoaded: true,
 | 
			
		||||
		Streamer: streamer,
 | 
			
		||||
		Control:  ctrl,
 | 
			
		||||
	}
 | 
			
		||||
	speaker.Play(ctrl)
 | 
			
		||||
	<-done
 | 
			
		||||
	fmt.Println("Finished playing sound: " + file)
 | 
			
		||||
	delete(playbacks, index)
 | 
			
		||||
	return 1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Welcome to Soundr!")
 | 
			
		||||
 | 
			
		||||
	playbacks = make(map[int]playback)
 | 
			
		||||
	streamMap = make(map[string]streamBuf)
 | 
			
		||||
 | 
			
		||||
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Query().Get("name")))
 | 
			
		||||
	})
 | 
			
		||||
	// Create /sounds if not exists
 | 
			
		||||
	if _, err := os.Stat("./sounds"); os.IsNotExist(err) {
 | 
			
		||||
		fmt.Println("Created /sounds folder")
 | 
			
		||||
		os.Mkdir("./sounds", 0777)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	http.HandleFunc("/v1/play", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		w.Header().Set("Content-Type", "application/json")
 | 
			
		||||
		var cnt = r.URL.Query().Get("file")
 | 
			
		||||
		bytArr, err := base64.StdEncoding.DecodeString(cnt)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatal(err)
 | 
			
		||||
	// Handle config
 | 
			
		||||
	fmt.Println("Opening conf.json")
 | 
			
		||||
	file, fOpenError := os.Open("conf.json") // Try to open the file
 | 
			
		||||
 | 
			
		||||
	if errors.Is(fOpenError, os.ErrNotExist) { // If it does not exist, create it
 | 
			
		||||
		fmt.Println("Creating conf.json")
 | 
			
		||||
		file, fOpenError = os.Create("conf.json")
 | 
			
		||||
		if fOpenError != nil {
 | 
			
		||||
			log.Fatal(fOpenError)
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Println(string(bytArr[:]))
 | 
			
		||||
		t, err := os.Stat("./sounds/" + string(bytArr[:]))
 | 
			
		||||
		t = t
 | 
			
		||||
		if !errors.Is(err, os.ErrNotExist) {
 | 
			
		||||
			var currIndex = len(playbacks)
 | 
			
		||||
			fmt.Fprintf(w, "{\"status\":\"ok\", \"id\":%d}", currIndex)
 | 
			
		||||
		defer file.Close()
 | 
			
		||||
		fmt.Println("Writing to conf.json")
 | 
			
		||||
		// Write the default config to the file
 | 
			
		||||
		json.NewEncoder(file).Encode(Configuration{
 | 
			
		||||
			Port: 8080,
 | 
			
		||||
		})
 | 
			
		||||
		fmt.Println("Wrote to conf.json")
 | 
			
		||||
	}
 | 
			
		||||
	defer file.Close()
 | 
			
		||||
	// Decode the config
 | 
			
		||||
	decoder := json.NewDecoder(file)
 | 
			
		||||
	configuration := Configuration{}
 | 
			
		||||
	err := decoder.Decode(&configuration)
 | 
			
		||||
 | 
			
		||||
			go PlaySound(string(bytArr[:]), currIndex)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("error:", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		} else {
 | 
			
		||||
			fmt.Fprintf(w, "{\"status\":\"fail\", \"reason\":\"file not found\"}")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	http.HandleFunc("/v1/buffer", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		w.Header().Set("Content-Type", "application/json")
 | 
			
		||||
		var cnt = r.URL.Query().Get("file")
 | 
			
		||||
		bytArr, err := base64.StdEncoding.DecodeString(cnt)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
		t, err := os.Stat("./sounds/" + string(bytArr[:]))
 | 
			
		||||
		t = t
 | 
			
		||||
		if !errors.Is(err, os.ErrNotExist) {
 | 
			
		||||
			fmt.Fprintf(w, "{\"status\":\"ok\"}")
 | 
			
		||||
			go BufferSound(string(bytArr[:]))
 | 
			
		||||
		} else {
 | 
			
		||||
			fmt.Fprintf(w, "{\"status\":\"fail\", \"reason\":\"file not found\"}")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	http.HandleFunc("/v1/stopAll", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		w.Header().Set("Content-Type", "application/json")
 | 
			
		||||
		playbacks = make(map[int]playback)
 | 
			
		||||
		fmt.Fprintf(w, "{\"status\":\"ok\"}")
 | 
			
		||||
		speaker.Clear()
 | 
			
		||||
		//fmt.Fprintf(w, "{\"status\":\"ok\", \"id\":%d}", currIndex)
 | 
			
		||||
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	http.HandleFunc("/v1/stop", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		w.Header().Set("Content-Type", "application/json")
 | 
			
		||||
		var cnt, err = strconv.Atoi(r.URL.Query().Get("id"))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Fprintf(w, "{\"status\":\"fail\", \"reason\":\"invalid id\"}")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		value, ok := playbacks[cnt]
 | 
			
		||||
		if !ok {
 | 
			
		||||
			fmt.Fprintf(w, "{\"status\":\"fail\", \"reason\":\"audio not playing\"}")
 | 
			
		||||
		} else {
 | 
			
		||||
			fmt.Fprintf(w, "{\"status\":\"ok\", \"id\":%d}", value)
 | 
			
		||||
			value.Control.Paused = true
 | 
			
		||||
			value.Control.Streamer = nil
 | 
			
		||||
			delete(playbacks, cnt)
 | 
			
		||||
		}
 | 
			
		||||
		//fmt.Fprintf(w, "{\"status\":\"ok\", \"id\":%d}", currIndex)
 | 
			
		||||
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	http.HandleFunc("/v1/current", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		w.Header().Set("Content-Type", "application/json")
 | 
			
		||||
 | 
			
		||||
		var tempResultSet map[int]playbackWebReturn
 | 
			
		||||
		tempResultSet = make(map[int]playbackWebReturn)
 | 
			
		||||
 | 
			
		||||
		for index, element := range playbacks {
 | 
			
		||||
			tempResultSet[index] = playbackWebReturn{File: element.File, IsLoaded: element.IsLoaded, Id: index}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		j, err := json.Marshal(tempResultSet)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Printf("Error: %s", err.Error())
 | 
			
		||||
		} else {
 | 
			
		||||
			fmt.Println(string(j))
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Fprintf(w, string(j))
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	http.HandleFunc("/v1/list", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		w.Header().Set("Content-Type", "application/json")
 | 
			
		||||
		var temp [][3]string
 | 
			
		||||
		files, err := ioutil.ReadDir("./sounds/")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, f := range files {
 | 
			
		||||
			var soundObj [3]string
 | 
			
		||||
			soundObj[0] = f.Name()
 | 
			
		||||
			soundObj[1] = base64.StdEncoding.EncodeToString([]byte(f.Name()))
 | 
			
		||||
			soundObj[2] = r.URL.Host + "/v1/play?file=" + soundObj[1]
 | 
			
		||||
			temp = append(temp, soundObj)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		j, err := json.Marshal(temp)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Printf("Error: %s", err.Error())
 | 
			
		||||
		} else {
 | 
			
		||||
			fmt.Println(string(j))
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Fprintf(w, string(j))
 | 
			
		||||
	})
 | 
			
		||||
	log.Fatal(http.ListenAndServe(":8081", nil))
 | 
			
		||||
	// Web server stuff
 | 
			
		||||
	// Play route, takes file as parameter, file is base64 encoded
 | 
			
		||||
	http.HandleFunc("/v1/play", handlePlay)
 | 
			
		||||
	// Buffer route, buffers file
 | 
			
		||||
	http.HandleFunc("/v1/buffer", handleBuffer)
 | 
			
		||||
	http.HandleFunc("/v1/bufferAll", handleBufferAll)
 | 
			
		||||
	http.HandleFunc("/v1/stop", handleStop)
 | 
			
		||||
	http.HandleFunc("/v1/stopAll", handleStopAll)
 | 
			
		||||
	http.HandleFunc("/v1/current", handleCurrent)
 | 
			
		||||
	http.HandleFunc("/v1/list", handleListing)
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Listening on port " + fmt.Sprint(configuration.Port))
 | 
			
		||||
	log.Fatal(http.ListenAndServe(":"+fmt.Sprint(configuration.Port), nil))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										208
									
								
								webRoutes.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								webRoutes.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,208 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"github.com/faiface/beep/speaker"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func handlePlay(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	// Rejct everything else then GET requests
 | 
			
		||||
	if r.Method != "GET" {
 | 
			
		||||
		http.Error(w, "Method is not supported.", http.StatusNotFound)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	w.Header().Set("Content-Type", "application/json")  // Set the content type to json
 | 
			
		||||
	var cnt = r.URL.Query().Get("file")                 // Retrieve the file name from the query string
 | 
			
		||||
	bytArr, err := base64.StdEncoding.DecodeString(cnt) // Decode the base64 string
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t, err := os.Stat("./sounds/" + string(bytArr[:])) // Check if the file exists
 | 
			
		||||
	if errors.Is(err, os.ErrNotExist) {
 | 
			
		||||
		w.WriteHeader(400)
 | 
			
		||||
		fmt.Fprintf(w, "{\"status\":\"fail\", \"reason\":\"file not found\"}")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if t.IsDir() { // Make sure it is not a folder we are trying to play
 | 
			
		||||
		w.WriteHeader(400)
 | 
			
		||||
		fmt.Fprintf(w, "{\"status\":\"fail\", \"reason\":\"target is folder\"}")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var currIndex = len(playbacks)                              // Create a new index for the playback
 | 
			
		||||
	fmt.Fprintf(w, "{\"status\":\"ok\", \"id\":%d}", currIndex) // Return a JSON object to the user
 | 
			
		||||
 | 
			
		||||
	go PlaySound(string(bytArr[:]), currIndex) // Play the sound
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Handle Buffering
 | 
			
		||||
 | 
			
		||||
func handleBufferAll(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	// Rejct everything else then GET requests
 | 
			
		||||
	if r.Method != "GET" {
 | 
			
		||||
		http.Error(w, "Method is not supported.", http.StatusNotFound)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	w.Header().Set("Content-Type", "application/json") // Set the content type to json
 | 
			
		||||
 | 
			
		||||
	var temp []string
 | 
			
		||||
	files, err := ioutil.ReadDir("./sounds/") // Read the directory
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	// Loop through the files and add the file name to the temp array
 | 
			
		||||
	// Also triggers the buffer process for the file
 | 
			
		||||
	for _, f := range files {
 | 
			
		||||
		temp = append(temp, f.Name())
 | 
			
		||||
		go BufferSound(f.Name())
 | 
			
		||||
	}
 | 
			
		||||
	// Return the amount of files buffered
 | 
			
		||||
	fmt.Fprintf(w, "{\"status\":\"ok\", \"amount\":%d}", len(temp))
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func handleBuffer(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	// Rejct everything else then GET requests
 | 
			
		||||
	if r.Method != "GET" {
 | 
			
		||||
		http.Error(w, "Method is not supported.", http.StatusNotFound)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	w.Header().Set("Content-Type", "application/json")  // Set the content type to json
 | 
			
		||||
	var cnt = r.URL.Query().Get("file")                 // Retrieve the file name from the query string
 | 
			
		||||
	bytArr, err := base64.StdEncoding.DecodeString(cnt) // Decode the base64 string
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t, err := os.Stat("./sounds/" + string(bytArr[:])) // Check if the file exists
 | 
			
		||||
	if errors.Is(err, os.ErrNotExist) {
 | 
			
		||||
		w.WriteHeader(400)
 | 
			
		||||
		fmt.Fprintf(w, "{\"status\":\"fail\", \"reason\":\"file not found\"}")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if t.IsDir() { // Make sure it is not a folder we are trying to play
 | 
			
		||||
		w.WriteHeader(400)
 | 
			
		||||
		fmt.Fprintf(w, "{\"status\":\"fail\", \"reason\":\"target is folder\"}")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Fprintf(w, "{\"status\":\"ok\"}")
 | 
			
		||||
	go BufferSound(string(bytArr[:]))
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Handeling Stop
 | 
			
		||||
 | 
			
		||||
func handleStop(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	// Rejct everything else then GET requests
 | 
			
		||||
	if r.Method != "GET" {
 | 
			
		||||
		http.Error(w, "Method is not supported.", http.StatusNotFound)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	w.Header().Set("Content-Type", "application/json")   // Set the content type to json
 | 
			
		||||
	var cnt, err = strconv.Atoi(r.URL.Query().Get("id")) // Retrieve the id, first convert it to an int
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Fprintf(w, "{\"status\":\"fail\", \"reason\":\"invalid id\"}")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	value, ok := playbacks[cnt] // Get value from playbacks map
 | 
			
		||||
	if !ok {
 | 
			
		||||
		w.WriteHeader(400)
 | 
			
		||||
		fmt.Fprintf(w, "{\"status\":\"fail\", \"reason\":\"audio not playing\"}")
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Fprintf(w, "{\"status\":\"ok\"}")
 | 
			
		||||
		// Stop by pausing first then, set the streamer to nil. Finally delete it from the map
 | 
			
		||||
		value.Control.Paused = true
 | 
			
		||||
		value.Control.Streamer = nil
 | 
			
		||||
		delete(playbacks, cnt)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func handleStopAll(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	// Rejct everything else then GET requests
 | 
			
		||||
	if r.Method != "GET" {
 | 
			
		||||
		http.Error(w, "Method is not supported.", http.StatusNotFound)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	w.Header().Set("Content-Type", "application/json") // Set the content type to json
 | 
			
		||||
 | 
			
		||||
	// Pause and stop all playbacks
 | 
			
		||||
	for _, v := range playbacks {
 | 
			
		||||
		v.Control.Paused = true
 | 
			
		||||
		v.Control.Streamer = nil
 | 
			
		||||
	}
 | 
			
		||||
	speaker.Clear() // Clear the speaker and make it shut up
 | 
			
		||||
 | 
			
		||||
	// Reset the map
 | 
			
		||||
	playbacks = make(map[int]playback)
 | 
			
		||||
 | 
			
		||||
	fmt.Fprintf(w, "{\"status\":\"ok\"}")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func handleCurrent(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	// Rejct everything else then GET requests
 | 
			
		||||
	if r.Method != "GET" {
 | 
			
		||||
		http.Error(w, "Method is not supported.", http.StatusNotFound)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	w.Header().Set("Content-Type", "application/json") // Set the content type to json
 | 
			
		||||
 | 
			
		||||
	var tempResultSet map[int]playbackWebReturn = make(map[int]playbackWebReturn) // Create a new map to store the results
 | 
			
		||||
	// Iterate through the playbacks map and add important information to the tempResultSet map
 | 
			
		||||
	for index, element := range playbacks {
 | 
			
		||||
		tempResultSet[index] = playbackWebReturn{File: element.File, IsLoaded: element.IsLoaded, Id: index}
 | 
			
		||||
	}
 | 
			
		||||
	// Convert the map to a JSON object and return it to the user
 | 
			
		||||
	j, err := json.Marshal(tempResultSet)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Printf("Error: %s", err.Error())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println(string(j))
 | 
			
		||||
		fmt.Fprintf(w, string(j))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func handleListing(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	// Rejct everything else then GET requests
 | 
			
		||||
	if r.Method != "GET" {
 | 
			
		||||
		http.Error(w, "Method is not supported.", http.StatusNotFound)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	w.Header().Set("Content-Type", "application/json") // Set the content type to json
 | 
			
		||||
 | 
			
		||||
	var temp [][3]string
 | 
			
		||||
	files, err := ioutil.ReadDir("./sounds/") // Find all files in the sounds directory
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Add the file data to the temp array
 | 
			
		||||
	for _, f := range files {
 | 
			
		||||
		var soundObj [3]string
 | 
			
		||||
		soundObj[0] = f.Name()
 | 
			
		||||
		soundObj[1] = base64.StdEncoding.EncodeToString([]byte(f.Name()))
 | 
			
		||||
		soundObj[2] = r.URL.Host + "/v1/play?file=" + soundObj[1]
 | 
			
		||||
		temp = append(temp, soundObj)
 | 
			
		||||
	}
 | 
			
		||||
	// Convert the array to a JSON object and return it to the user
 | 
			
		||||
	j, err := json.Marshal(temp)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Printf("Error: %s", err.Error())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println(string(j))
 | 
			
		||||
		fmt.Fprintf(w, string(j))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user