10 Commits

Author SHA1 Message Date
7e6cc8cd1b Merge branch 'master' of https://git.project-name-here.de/Project-Name-Here/soundr 2023-03-23 21:52:28 +01:00
0b40953cf5 - fixes loop not working 2023-03-23 21:52:25 +01:00
2eb3b0a089 „README.MD“ ändern 2022-11-21 21:21:55 +01:00
3482b6952a fixed stacktrack, crashing issue 2022-10-25 18:16:23 +02:00
92a6b4b1d3 fixed small thing 2022-10-13 18:01:06 +02:00
22edd2ea9c added additional info 2022-10-13 17:58:09 +02:00
39a5d0b0cd - small fixes 2022-10-13 17:22:01 +02:00
8b198040bd - add a new remaining endpoint 2022-10-13 16:04:51 +02:00
1af34dfcf3 - add predictable / vanity ids
- add / route
2022-10-13 12:47:44 +02:00
f3e07d3f4d - add error handeling
- add loop parameter
- improve documentation
2022-05-26 17:46:03 +02:00
5 changed files with 134 additions and 16 deletions

View File

@ -23,6 +23,8 @@ This is the basic json configuration file layout:
} }
``` ```
> All sounds should be in the same bitrate. It will assume the bitrate of the first loaded sample.
# Usage # Usage
Drop your sounds into the /sounds. You can play them by sending a GET request to the /v1/play endpoint. 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. 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.

View File

@ -14,6 +14,11 @@ paths:
description: A base64 encoded version of the file name description: A base64 encoded version of the file name
schema: schema:
type: string type: string
- in: query
name: loop
description: Defaults to false; if true, will loop the sound until stopped
schema:
type: boolean
responses: responses:
'200': '200':
description: OK description: OK

View File

@ -16,6 +16,8 @@ import (
"github.com/h2non/filetype" "github.com/h2non/filetype"
) )
var firstLoad = true
func BufferSound(file string) bool { func BufferSound(file string) bool {
_, ok := streamMap[file] _, ok := streamMap[file]
if !ok { if !ok {
@ -26,9 +28,18 @@ func BufferSound(file string) bool {
} }
fmt.Println("Opened file") fmt.Println("Opened file")
buf, _ := ioutil.ReadFile("sounds/" + string(file)) buf, err := ioutil.ReadFile("sounds/" + string(file))
if err != nil {
log.Fatal("Fatal error while opening: " + err.Error())
return false
}
kind, _ := filetype.Match(buf) kind, err := filetype.Match(buf)
if err != nil {
log.Fatal("Fatal error while detecting file type: " + err.Error())
return false
}
fmt.Println("File type: " + kind.MIME.Subtype) fmt.Println("File type: " + kind.MIME.Subtype)
var streamer beep.StreamSeekCloser var streamer beep.StreamSeekCloser
@ -45,8 +56,10 @@ func BufferSound(file string) bool {
fmt.Println("!!!!! Unsupported file type for " + file) fmt.Println("!!!!! Unsupported file type for " + file)
return false return false
} }
if firstLoad {
speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10)) speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
firstLoad = false
}
fmt.Println("Decoded file") fmt.Println("Decoded file")
buffer := beep.NewBuffer(format) buffer := beep.NewBuffer(format)
@ -66,37 +79,46 @@ func BufferSound(file string) bool {
} }
} }
func PlaySound(file string, index int) int { func PlaySound(file string, index int, loop bool) int {
playbacks[index] = playback{ playbacks[index] = playback{
File: file, File: file,
IsLoaded: false, IsLoaded: false,
Streamer: nil, Streamer: nil,
Control: nil, Control: nil,
Loop: loop,
Format: streamMap[file].Format,
} }
fmt.Println("Playing sound: " + file) fmt.Println("Playing sound: " + file)
var buffer *beep.Buffer var buffer *beep.Buffer
BufferSound(file) BufferSound(file)
buffer = streamMap[file].Buffer buffer = streamMap[file].Buffer
streamer := streamMap[file].Streamer // streamer := streamMap[file].Streamer
fmt.Println("Trying to play sound") fmt.Println("Trying to play sound")
amountOfLoops := 1
if loop {
amountOfLoops = -1
fmt.Println("Looping sound: " + file)
}
shot := buffer.Streamer(0, buffer.Len()) shot := buffer.Streamer(0, buffer.Len())
done := make(chan bool) done := make(chan bool)
ctrl := &beep.Ctrl{Streamer: beep.Seq(shot, beep.Callback(func() { ctrl := &beep.Ctrl{Streamer: beep.Loop(amountOfLoops, shot), Paused: false}
done <- true
})), Paused: false}
playbacks[index] = playback{ playbacks[index] = playback{
File: file, File: file,
IsLoaded: true, IsLoaded: true,
Streamer: streamer, Streamer: shot,
Control: ctrl, Control: ctrl,
Loop: loop,
Format: streamMap[file].Format,
Done: done,
} }
speaker.Play(ctrl) speaker.Play(ctrl)
<-done <-done
fmt.Println("Finished playing sound: " + file) fmt.Println("Finished playing sound: " + file)
delete(playbacks, index) delete(playbacks, index)
return 1 return 1
} }

View File

@ -14,8 +14,11 @@ import (
type playback struct { type playback struct {
File string File string
IsLoaded bool IsLoaded bool
Streamer beep.Streamer Streamer beep.StreamSeeker
Control *beep.Ctrl Control *beep.Ctrl
Loop bool
Format beep.Format
Done chan bool
} }
type playbackWebReturn struct { type playbackWebReturn struct {
@ -89,6 +92,8 @@ func main() {
http.HandleFunc("/v1/stopAll", handleStopAll) http.HandleFunc("/v1/stopAll", handleStopAll)
http.HandleFunc("/v1/current", handleCurrent) http.HandleFunc("/v1/current", handleCurrent)
http.HandleFunc("/v1/list", handleListing) http.HandleFunc("/v1/list", handleListing)
http.HandleFunc("/v1/remaining", handleRemaining)
http.HandleFunc("/", handleRoot)
fmt.Println("Listening on port " + fmt.Sprint(configuration.Port)) fmt.Println("Listening on port " + fmt.Sprint(configuration.Port))
log.Fatal(http.ListenAndServe(":"+fmt.Sprint(configuration.Port), nil)) log.Fatal(http.ListenAndServe(":"+fmt.Sprint(configuration.Port), nil))

View File

@ -10,6 +10,7 @@ import (
"net/http" "net/http"
"os" "os"
"strconv" "strconv"
"time"
"github.com/faiface/beep/speaker" "github.com/faiface/beep/speaker"
"github.com/h2non/filetype" "github.com/h2non/filetype"
@ -25,8 +26,16 @@ func handlePlay(w http.ResponseWriter, r *http.Request) {
var cnt = r.URL.Query().Get("file") // Retrieve the file name from the query string 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 bytArr, err := base64.StdEncoding.DecodeString(cnt) // Decode the base64 string
if err != nil { if err != nil {
log.Fatal(err) fmt.Fprintf(w, "{\"status\":\"fail\", \"reason\":\""+err.Error()+"\"}")
return
} }
loop := r.URL.Query().Get("loop") // Retrieve the loop value from the query string
loopBool := false
if loop == "true" {
loopBool = true
}
wantedId := r.URL.Query().Get("id") // Retrieve the id value from the query string, it's optional
t, err := os.Stat("./sounds/" + string(bytArr[:])) // Check if the file exists t, err := os.Stat("./sounds/" + string(bytArr[:])) // Check if the file exists
if errors.Is(err, os.ErrNotExist) { if errors.Is(err, os.ErrNotExist) {
@ -55,9 +64,23 @@ func handlePlay(w http.ResponseWriter, r *http.Request) {
} }
var currIndex = len(playbacks) // Create a new index for the playback var currIndex = len(playbacks) // Create a new index for the playback
if len(wantedId) > 0 { // If the id is set, check if it is already in use
id, err := strconv.Atoi(wantedId)
if err != nil {
fmt.Fprintf(w, "{\"status\":\"fail\", \"reason\":\"id is not a number\"}")
return
}
if _, ok := playbacks[id]; ok {
fmt.Fprintf(w, "{\"status\":\"fail\", \"reason\":\"id is already in use\"}")
return
}
currIndex = id
}
fmt.Fprintf(w, "{\"status\":\"ok\", \"id\":%d}", currIndex) // Return a JSON object to the user fmt.Fprintf(w, "{\"status\":\"ok\", \"id\":%d}", currIndex) // Return a JSON object to the user
go PlaySound(string(bytArr[:]), currIndex) // Play the sound go PlaySound(string(bytArr[:]), currIndex, loopBool) // Play the sound
} }
@ -74,7 +97,8 @@ func handleBufferAll(w http.ResponseWriter, r *http.Request) {
var temp []string var temp []string
files, err := ioutil.ReadDir("./sounds/") // Read the directory files, err := ioutil.ReadDir("./sounds/") // Read the directory
if err != nil { if err != nil {
log.Fatal(err) fmt.Fprintf(w, "{\"status\":\"fail\", \"reason\":\""+err.Error()+"\"}")
return
} }
// Loop through the files and add the file name to the temp array // Loop through the files and add the file name to the temp array
// Also triggers the buffer process for the file // Also triggers the buffer process for the file
@ -93,6 +117,7 @@ func handleBufferAll(w http.ResponseWriter, r *http.Request) {
} }
temp = append(temp, f.Name()) temp = append(temp, f.Name())
go BufferSound(f.Name()) go BufferSound(f.Name())
time.Sleep(200 * time.Millisecond) // Wait a bit to not overload the system
} }
// Return the amount of files buffered // Return the amount of files buffered
fmt.Fprintf(w, "{\"status\":\"ok\", \"amount\":%d}", len(temp)) fmt.Fprintf(w, "{\"status\":\"ok\", \"amount\":%d}", len(temp))
@ -109,7 +134,8 @@ func handleBuffer(w http.ResponseWriter, r *http.Request) {
var cnt = r.URL.Query().Get("file") // Retrieve the file name from the query string 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 bytArr, err := base64.StdEncoding.DecodeString(cnt) // Decode the base64 string
if err != nil { if err != nil {
log.Fatal(err) fmt.Fprintf(w, "{\"status\":\"fail\", \"reason\":\""+err.Error()+"\"}")
return
} }
t, err := os.Stat("./sounds/" + string(bytArr[:])) // Check if the file exists t, err := os.Stat("./sounds/" + string(bytArr[:])) // Check if the file exists
@ -199,6 +225,23 @@ func handleCurrent(w http.ResponseWriter, r *http.Request) {
} }
w.Header().Set("Content-Type", "application/json") // Set the content type to json w.Header().Set("Content-Type", "application/json") // Set the content type to json
for i := 0; i < len(playbacks); i++ {
plyB := playbacks[i]
seeker := plyB.Streamer
format := plyB.Format
if seeker != nil {
fmt.Println(format.SampleRate)
// fmt.Println(plyB.Seeker.)
position := plyB.Format.SampleRate.D(seeker.Position())
length := plyB.Format.SampleRate.D(seeker.Len())
remaining := length - position
if remaining == 0 {
plyB.Done <- true
}
}
}
var tempResultSet map[int]playbackWebReturn = make(map[int]playbackWebReturn) // Create a new map to store the results 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 // Iterate through the playbacks map and add important information to the tempResultSet map
for index, element := range playbacks { for index, element := range playbacks {
@ -259,3 +302,44 @@ func handleListing(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, string(j)) fmt.Fprintf(w, string(j))
} }
} }
func handleRemaining(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\"}")
return
}
fmt.Println(cnt)
plyB := playbacks[cnt]
// fmt.Println(beep.SampleRate.D(plyB.Streamer.Stream().Len()))
seeker := plyB.Streamer
format := plyB.Format
n := plyB.Format.SampleRate // Streamer.Stream() // .At(beep.SampleRate.D(plyB.Streamer.Stream().Len()))
if seeker != nil {
fmt.Println(format.SampleRate)
// fmt.Println(plyB.Seeker.)
position := plyB.Format.SampleRate.D(seeker.Position())
length := plyB.Format.SampleRate.D(seeker.Len())
remaining := length - position
if remaining == 0 {
plyB.Done <- true
}
fmt.Println(position)
fmt.Fprintf(w, "{\"status\":\"ok\", \"id\":%d, \"SampleRate\":%d, \"Length\":%d, \"Position\":%d, \"Remaining\": %d, \"LengthSec\":\"%v\", \"PosSec\":\"%v\", \"RemaningSec\":\"%v\"}", cnt, n, length, position, remaining, length, position, remaining)
} else {
fmt.Println("Seeker is nil")
fmt.Fprintf(w, "{\"status\":\"ok\", \"SampleRate\":%d}", n)
}
}
func handleRoot(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Soundr is running.")
}