An Interest In:
Web News this Week
- April 2, 2024
- April 1, 2024
- March 31, 2024
- March 30, 2024
- March 29, 2024
- March 28, 2024
- March 27, 2024
Build A K-pop Radio in Go!
The full code can be found here:
https://github.com/raymond-design/kpop-cli
Intro:
We'll be learning how to use the Gorilla websockets websocket client and faiface/beep to stream K-pop music
Setup the project:
Let's first init our Go project by running:go mod init [project name]
We'll need to download two libraries:
Beep:go get github.com/faiface/beep
Gorilla Websocket:go get github.com/gorilla/websocket
Start coding!
It will be helpful if we first setup our project structure.
First create a main.go
file at in our project directory. This will be our entrypoint.
Then create 4 more directories:connect
play
types
ui
The project structure should look something like this (I also recommend creating a .gitignore
if you plan on pushing to git:
User Interface
Let's first create a file inside the ui
folder. We name the file ui.go
.
This file will define a function that prints song info the terminal! First let's import the "fmt"
package:
package uiimport ( "fmt")
Now let's create a function named WriteToFunction
. Make sure to capitalize the first letter (since we'll use it elsewhere):
func WriteToScreen(name string, author string, album string) { fmt.Print("\033[H\033[2J") fmt.Println("Now Playing:") fmt.Println("Title: " + name) fmt.Println("Artist: " + author) if album != "" { fmt.Println("Album: " + album) }}
ui.go
looks like this:
Define Types
A helpful pattern in Go is to define related struct types in one place. Let's create a types.go
file in the types directory.
The song info will be in json
format. First import that:
package typesimport "encoding/json"
Next, we need to describe some types for WebSockets connection:
type SocketRes struct { Op int64 `json:"op"` D json.RawMessage}type SendData struct { Op int64 `json:"op"`}type HeartbeatData struct { Message string `json:"message"` Heartbeat int64 `json:"heartbeat"`}
Next, we will define some structs related to the songs themselves(Song, Album, etc.):
type PlayingData struct { Song Song `json:"song"` Requester interface{} `json:"requester"` Event interface{} `json:"event"` StartTime string `json:"startTime"` LastPlayed []Song `json:"lastPlayed"` Listeners int64 `json:"listeners"`}type Song struct { ID int64 `json:"id"` Title string `json:"title"` Sources []interface{} `json:"sources"` Artists []Album `json:"artists"` Albums []Album `json:"albums"` Duration int64 `json:"duration"`}type Album struct { ID int64 `json:"id"` Name string `json:"name"` NameRomaji *string `json:"nameRomaji"` Image *string `json:"image"`}
Now that we finished definitions, we can create the client to stream audio!
Create the WebSocket Client
Head over to the connect
directory and create a connect.go
file.
In this package, we'll need to import Gorilla websocket and the two packages we've already created:
package connectimport ( "encoding/json" "log" "time" "github.com/raymond-design/kpop-cli/types" "github.com/raymond-design/kpop-cli/ui" "github.com/gorilla/websocket")
We also need to define 3 package-level variables:
var conn *websocket.Connvar done = falsevar ticker *time.Ticker
Let's a create a function to initialize the connection:
func Start(url string) { }
(Later on, url string
will be the WebSocket server url that we want to stream from)
Now paste the following:
conn_l, _, err := websocket.DefaultDialer.Dial(url, nil)if err != nil { log.Fatal("Couldn't connect to websocket")}conn = conn_l
If the conn doesn't work, there could be an error with the URL!
Now, let's run an anonymous function Goroutine to maintain the WebSocket connection:
go func() { for { if done { conn.Close() break } _, msg, err := conn.ReadMessage() if err != nil { log.Fatal("Couldn't read websocket message") } handleMessage(msg) }}()
We will keep on maintaining the connection until program break or a read error. The function should look something like this:
Now we need to implement that handleMessage
function!
func handleMessage(in []byte) { var msg types.SocketRes json.Unmarshal(in, &msg) switch msg.Op { case 0: var data types.HeartbeatData json.Unmarshal(msg.D, &data) setHeartbeat(data.Heartbeat) case 1: var data types.PlayingData json.Unmarshal(msg.D, &data) album := "None" if len(data.Song.Albums) > 0 { album = data.Song.Albums[0].Name } ui.WriteToScreen(data.Song.Title, data.Song.Artists[0].Name, album) }}
In the start function, we continually call this function which will grab the current song data and print it.
To make the code cleaner, the actual set heartbeat logic will be in two other functions:
func sendHeartBeat() { data := types.SendData{ Op: 9, } conn.WriteJSON(data)}func setHeartbeat(repeat int64) { sendHeartBeat() ticker = time.NewTicker(time.Duration(repeat) * time.Millisecond) go func() { <-ticker.C sendHeartBeat() }()}
If you want to read more about WebSockets connections, here's a helpful article:
https://www.programmerall.com/article/821816187/
Finally, we just need a stopping function that will break out of that for loop:
func Stop() { ticker.Stop() done = true}
Now that we have these WebSockets connection functions, we can bring sound into the app!
Including Sound!
To bring in sound, we will be importing faiface/beep
:
package playimport ( "log" "net/http" "time" "github.com/faiface/beep" "github.com/faiface/beep/mp3" "github.com/faiface/beep/speaker")
We will also create a global var from this beep package:
var stream beep.StreamSeekCloser
We will need two functions. One to play and one to stop.
The play function is quite simple. We will check the validity of the http url and then use the beep/mp3
to starting streaming audio contents!
func Play(url string) { resp, err := http.Get(url) if err != nil { log.Fatal("http error") } l_streamer, format, err := mp3.Decode(resp.Body) stream = l_streamer if err != nil { log.Fatal("decoding error") } speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10)) speaker.Play(stream)}
The stop function is even simpler. We just close the stream:
func Stop() { stream.Close()}
The code looks something like this:
Project Entrypoint
Now we can create the entrypoint to our app! Let's import our packages:
package mainimport ( "fmt" "os" "os/signal" "github.com/raymond-design/kpop-cli/connect" "github.com/raymond-design/kpop-cli/play")
Now let's define the server URL that we'll stream from:
const JPOP string = "https://listen.moe/fallback"const KPOP string = "https://listen.moe/kpop/fallback"const JSOCKET string = "wss://listen.moe/gateway_v2"const KSOCKET string = "wss://listen.moe/kpop/gateway_v2"
By the way, you can also stream J-pop music now!
Now create the main function:
func main(){ c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) mode := "kpop" var stream string var socket string if len(os.Args) == 2 { mode = os.Args[1] }}
We can use a switch function to switch between K-pop and J-pop music:
switch mode { case "kpop": stream = KPOP socket = KSOCKET case "jpop": stream = JPOP socket = JSOCKET default: fmt.Println("Error") os.Exit(1)}
Now, we can connect and start streaming music!
connect.Start(socket)play.Play(stream)interrupt := make(chan os.Signal, 1)signal.Notify(interrupt, os.Interrupt)<-interruptfmt.Println("Exiting Player")play.Stop()connect.Stop()
(Notice we stop first stop decoding audio, then disconnect from the WebSockets server)
The main function looks like this:
Listening to the radio
- Run a
go get
to get all dependencies. - Run
go build .
in the project. - Run
./kpop-cli kpop
to play K-pop music or./kpop-cli jpop
(If you implemented J-pop).
Now you know how to implement sound and WebSocket streaming in Go!
Also try streaming other types of data in the future
The full code can be found here:
https://github.com/raymond-design/kpop-cli
Original Link: https://dev.to/rayfrompsu/build-a-k-pop-radio-in-go-2080
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To