Explorar o código

Working prototype b1

TC pushbot 5 %!s(int64=4) %!d(string=hai) anos
pai
achega
70673db20a
Modificáronse 8 ficheiros con 436 adicións e 37 borrados
  1. 5 1
      go.mod
  2. 27 0
      go.sum
  3. 229 18
      handlers.go
  4. 44 0
      helpers.go
  5. 0 18
      mac.go
  6. 28 0
      main.go
  7. 103 0
      playback.go
  8. BIN=BIN
      tmp/1620547381.wav

+ 5 - 1
go.mod

@@ -2,4 +2,8 @@ module imuslab.com/hds/audio
 
 go 1.14
 
-require github.com/grandcat/zeroconf v1.0.0
+require (
+	github.com/faiface/beep v1.0.2
+	github.com/gabriel-vasile/mimetype v1.2.0
+	github.com/grandcat/zeroconf v1.0.0
+)

+ 27 - 0
go.sum

@@ -1,14 +1,39 @@
 github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
 github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
+github.com/faiface/beep v1.0.2 h1:UB5DiRNmA4erfUYnHbgU4UB6DlBOrsdEFRtcc8sCkdQ=
+github.com/faiface/beep v1.0.2/go.mod h1:1yLb5yRdHMsovYYWVqYLioXkVuziCSITW1oarTeduQM=
+github.com/gabriel-vasile/mimetype v1.2.0 h1:A6z5J8OhjiWFV91sQ3dMI8apYu/tvP9keDaMM3Xu6p4=
+github.com/gabriel-vasile/mimetype v1.2.0/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To=
+github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
+github.com/gdamore/tcell v1.1.1/go.mod h1:K1udHkiR3cOtlpKG5tZPD5XxrF7v2y7lDq7Whcj+xkQ=
+github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gopherjs/gopherwasm v0.1.1/go.mod h1:kx4n9a+MzHH0BJJhvlsQ65hqLFXDO/m256AsaDPQ+/4=
+github.com/gopherjs/gopherwasm v1.0.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI=
 github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=
 github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs=
+github.com/hajimehoshi/go-mp3 v0.1.1 h1:Y33fAdTma70fkrxnc9u50Uq0lV6eZ+bkAlssdMmCwUc=
+github.com/hajimehoshi/go-mp3 v0.1.1/go.mod h1:4i+c5pDNKDrxl1iu9iG90/+fhP37lio6gNhjCx9WBJw=
+github.com/hajimehoshi/oto v0.1.1/go.mod h1:hUiLWeBQnbDu4pZsAhOnGqMI1ZGibS6e2qhQdfpwz04=
+github.com/hajimehoshi/oto v0.3.1 h1:cpf/uIv4Q0oc5uf9loQn7PIehv+mZerh+0KKma6gzMk=
+github.com/hajimehoshi/oto v0.3.1/go.mod h1:e9eTLBB9iZto045HLbzfHJIc+jP3xaKrjZTghvb6fdM=
+github.com/jfreymuth/oggvorbis v1.0.0/go.mod h1:abe6F9QRjuU9l+2jek3gj46lu40N4qlYxh2grqkLEDM=
+github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0=
+github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
+github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mewkiz/flac v1.0.5 h1:dHGW/2kf+/KZ2GGqSVayNEhL9pluKn/rr/h/QqD9Ogc=
+github.com/mewkiz/flac v1.0.5/go.mod h1:EHZNU32dMF6alpurYyKHDLYpW1lYpBZ5WrXi/VuNIGs=
 github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM=
 github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/exp v0.0.0-20180710024300-14dda7b62fcd/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
+golang.org/x/mobile v0.0.0-20180806140643-507816974b79/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -17,6 +42,7 @@ golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCT
 golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
@@ -24,3 +50,4 @@ golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw=

+ 229 - 18
handlers.go

@@ -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
+	}
+}

+ 44 - 0
helpers.go

@@ -0,0 +1,44 @@
+package main
+
+import (
+	"io"
+	"net"
+	"net/http"
+	"os"
+)
+
+func getMacAddr() ([]string, error) {
+	ifas, err := net.Interfaces()
+	if err != nil {
+		return nil, err
+	}
+	var as []string
+	for _, ifa := range ifas {
+		a := ifa.HardwareAddr.String()
+		if a != "" {
+			as = append(as, a)
+		}
+	}
+	return as, nil
+}
+
+func DownloadFile(filepath string, url string) error {
+
+	// Get the data
+	resp, err := http.Get(url)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+
+	// Create the file
+	out, err := os.Create(filepath)
+	if err != nil {
+		return err
+	}
+	defer out.Close()
+
+	// Write the body to file
+	_, err = io.Copy(out, resp.Body)
+	return err
+}

+ 0 - 18
mac.go

@@ -1,18 +0,0 @@
-package main
-
-import "net"
-
-func getMacAddr() ([]string, error) {
-	ifas, err := net.Interfaces()
-	if err != nil {
-		return nil, err
-	}
-	var as []string
-	for _, ifa := range ifas {
-		a := ifa.HardwareAddr.String()
-		if a != "" {
-			as = append(as, a)
-		}
-	}
-	return as, nil
-}

+ 28 - 0
main.go

@@ -11,8 +11,11 @@ import (
 	"flag"
 	"log"
 	"net/http"
+	"os"
+	"os/signal"
 	"strconv"
 	"strings"
+	"syscall"
 	"time"
 
 	"imuslab.com/hds/audio/mod/mdns"
@@ -25,6 +28,19 @@ var (
 	deviceUUID   = ""
 )
 
+func SetupCloseHandler() {
+	c := make(chan os.Signal, 2)
+	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
+	go func() {
+		<-c
+		//Clear up the tmp folder
+		os.RemoveAll("./tmp")
+
+		//Exit
+		os.Exit(0)
+	}()
+}
+
 func main() {
 	//Parse flag
 	flag.Parse()
@@ -38,6 +54,8 @@ func main() {
 		deviceUUID = strconv.Itoa(int(time.Now().Unix()))
 	}
 
+	SetupCloseHandler()
+
 	//Start MDNS broadcast
 	hostController, err := mdns.NewMDNS(mdns.NetworkHost{
 		HostName:     "hdsAudio_" + deviceUUID,
@@ -59,6 +77,7 @@ func main() {
 	currentStatus := Status{
 		Playing: false,
 		Status:  "ready",
+		Gain:    0,
 	}
 
 	deviceStatus = &currentStatus
@@ -68,6 +87,15 @@ func main() {
 	http.HandleFunc("/status", handleStatus)
 	http.HandleFunc("/eps", handleEndpoints)
 
+	//handlers for playback endpoints
+	http.HandleFunc("/play", handlePlayToggle)
+	http.HandleFunc("/stop", handleStop)
+	http.HandleFunc("/load", handleLoad)
+	http.HandleFunc("/loop", handleLoop)
+	http.HandleFunc("/gain", handleVol)
+	http.HandleFunc("/mute", handleMute)
+	http.HandleFunc("/name", handleSetName)
+
 	//Start web server
 	log.Println("Web API Started on port ", *port)
 	err = http.ListenAndServe(":"+strconv.Itoa(*port), nil)

+ 103 - 0
playback.go

@@ -0,0 +1,103 @@
+package main
+
+import (
+	"errors"
+	"log"
+	"os"
+	"path/filepath"
+	"time"
+
+	"github.com/faiface/beep"
+	"github.com/faiface/beep/effects"
+	"github.com/faiface/beep/flac"
+	"github.com/faiface/beep/mp3"
+	"github.com/faiface/beep/speaker"
+	"github.com/faiface/beep/wav"
+)
+
+var globalStopFlag bool = false
+var playingSongData SongData
+
+func playFile(filename string) error {
+	if !fileExists(filename) {
+		return errors.New("File not exists")
+	}
+	f, err := os.Open(filename)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	ext := filepath.Ext(filename)
+	var streamer beep.StreamSeekCloser
+	var format beep.Format
+	if ext == ".mp3" {
+		streamer, format, err = mp3.Decode(f)
+	} else if ext == ".flac" {
+		streamer, format, err = flac.Decode(f)
+	} else if ext == ".wav" {
+		streamer, format, err = wav.Decode(f)
+	} else {
+		return errors.New("Not supported file format.")
+	}
+
+	if err != nil {
+		return err
+	}
+
+	defer streamer.Close()
+
+	//Create a new speaker object
+	speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
+
+	ctrl := &beep.Ctrl{
+		Streamer: beep.Loop(1, streamer),
+		Paused:   false,
+	}
+
+	volume := &effects.Volume{
+		Streamer: ctrl,
+		Base:     2,
+		Volume:   0,
+		Silent:   false,
+	}
+
+	//Start the playback counter for loop controls
+	playbackCounter := 0
+
+	for deviceStatus.Loop == true || playbackCounter == 0 {
+
+		done := make(chan bool)
+		speaker.Play(beep.Seq(volume, beep.Callback(func() {
+			done <- true
+		})))
+
+		breakOut := false
+		go func() {
+			for {
+				speaker.Lock()
+				ctrl.Paused = !deviceStatus.Playing
+				volume.Volume = deviceStatus.Gain
+				volume.Silent = deviceStatus.Mute
+				speaker.Unlock()
+
+				//If the playback finished or the global stopflag is set to true
+				if breakOut == true || globalStopFlag == true {
+					globalStopFlag = false
+					speaker.Clear()
+					break
+				}
+			}
+		}()
+
+		//Playback finish. Breakout of the loop
+		<-done
+		breakOut = true
+
+		//Play again if loop is enabled.
+		playbackCounter++
+	}
+
+	log.Println("Playback finished")
+	deviceStatus.Status = "ready"
+	return nil
+}

BIN=BIN
tmp/1620547381.wav