The total code could be discovered right here:
https://github.com/raymond-design/kpop-cli
Intro:
We’ll be studying the way to use the Gorilla websockets websocket consumer and faiface/beep to stream Ok-pop music 🎹
Setup the challenge:
Let’s first init our Go challenge by operating:go mod init [project name]
We’ll have to obtain two libraries:
Beep:go get github.com/faiface/beep
Gorilla Websocket:go get github.com/gorilla/websocket
Begin coding!
It will likely be useful if we first setup our challenge construction.
First create a foremost.go
file at in our challenge listing. This can be our entrypoint.
Then create 4 extra directories:join
play
sorts
ui
The challenge construction ought to look one thing like this (I additionally suggest making a .gitignore
when you plan on pushing to git:
Consumer Interface
Let’s first create a file contained in the ui
folder. We identify the file ui.go
.
This file will outline a perform that prints track information the terminal! First let’s import the "fmt"
bundle:
bundle ui
import (
"fmt"
)
Now let’s create a perform named WriteToFunction
. Be certain that to capitalize the primary letter (since we’ll use it elsewhere):
func WriteToScreen(identify string, creator string, album string) {
fmt.Print(" 33[H 33[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 types
import "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 []Track `json:"lastPlayed"`
Listeners int64 `json:"listeners"`
}
sort Track struct {
ID int64 `json:"id"`
Title string `json:"title"`
Sources []interface{} `json:"sources"`
Artists []Album `json:"artists"`
Albums []Album `json:"albums"`
Length int64 `json:"length"`
}
sort Album struct {
ID int64 `json:"id"`
Title string `json:"identify"`
NameRomaji *string `json:"nameRomaji"`
Picture *string `json:"picture"`
}
Now that we completed definitions, we are able to create the consumer to stream audio!
Create the WebSocket Shopper 🔌
Head over to the join
listing and create a join.go
file.
On this bundle, we’ll have to import Gorilla websocket and the 2 packages we have already created:
bundle join
import (
"encoding/json"
"log"
"time"
"github.com/raymond-design/kpop-cli/sorts"
"github.com/raymond-design/kpop-cli/ui"
"github.com/gorilla/websocket"
)
We additionally have to outline 3 package-level variables:
var conn *websocket.Conn
var performed = false
var ticker *time.Ticker
Let’s a create a perform to initialize the connection:
func Begin(url string) {
}
(In a while, url string
would be the WebSocket server url that we wish to stream from)
Now paste the next:
conn_l, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
log.Deadly("Could not hook up with websocket")
}
conn = conn_l
If the conn does not work, there could possibly be an error with the URL!
Now, let’s run an nameless perform Goroutine to keep up the WebSocket connection:
go func() {
for {
if performed {
conn.Shut()
break
}
_, msg, err := conn.ReadMessage()
if err != nil {
log.Deadly("Could not learn websocket message")
}
handleMessage(msg)
}
}()
We’ll carry on sustaining the connection till program break or a learn error. The perform ought to look one thing like this:
Now we have to implement that handleMessage
perform!
func handleMessage(in []byte) {
var msg sorts.SocketRes
json.Unmarshal(in, &msg)
swap msg.Op {
case 0:
var knowledge sorts.HeartbeatData
json.Unmarshal(msg.D, &knowledge)
setHeartbeat(knowledge.Heartbeat)
case 1:
var knowledge sorts.PlayingData
json.Unmarshal(msg.D, &knowledge)
album := "None"
if len(knowledge.Track.Albums) > 0 {
album = knowledge.Track.Albums[0].Title
}
ui.WriteToScreen(knowledge.Track.Title, knowledge.Track.Artists[0].Title, album)
}
}
Within the begin perform, we regularly name this perform which is able to seize the present track knowledge and print it.
To make the code cleaner, the precise set heartbeat logic can be in two different capabilities:
func sendHeartBeat() {
knowledge := sorts.SendData{
Op: 9,
}
conn.WriteJSON(knowledge)
}
func setHeartbeat(repeat int64) {
sendHeartBeat()
ticker = time.NewTicker(time.Length(repeat) * time.Millisecond)
go func() {
<-ticker.C
sendHeartBeat()
}()
}
If you wish to learn extra about WebSockets connections, here is a useful article:
https://www.programmerall.com/article/821816187/
Lastly, we simply want a stopping perform that can get away of that for loop:
func Cease() {
ticker.Cease()
performed = true
}
Now that we’ve got these WebSockets connection capabilities, we are able to carry sound into the app!
Together with Sound!
To usher in sound, we can be importing faiface/beep
:
bundle play
import (
"log"
"internet/http"
"time"
"github.com/faiface/beep"
"github.com/faiface/beep/mp3"
"github.com/faiface/beep/speaker"
)
We can even create a worldwide var from this beep bundle:
var stream beep.StreamSeekCloser
We’ll want two capabilities. One to play and one to cease.
The play perform is kind of easy. We’ll verify the validity of the http url after which use the beep/mp3
to beginning streaming audio contents!
func Play(url string) {
resp, err := http.Get(url)
if err != nil {
log.Deadly("http error")
}
l_streamer, format, err := mp3.Decode(resp.Physique)
stream = l_streamer
if err != nil {
log.Deadly("decoding error")
}
speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
speaker.Play(stream)
}
The cease perform is even easier. We simply shut the stream:
func Cease() {
stream.Shut()
}
The code seems one thing like this:
Mission Entrypoint
Now we are able to create the entrypoint to our app! Let’s import our packages:
bundle foremost
import (
"fmt"
"os"
"os/sign"
"github.com/raymond-design/kpop-cli/join"
"github.com/raymond-design/kpop-cli/play"
)
Now let’s outline the server URL that we’ll stream from:
const JPOP string = "https://pay attention.moe/fallback"
const KPOP string = "https://pay attention.moe/kpop/fallback"
const JSOCKET string = "wss://pay attention.moe/gateway_v2"
const KSOCKET string = "wss://pay attention.moe/kpop/gateway_v2"
By the way in which, it’s also possible to stream J-pop music now!
Now create the principle perform:
func foremost(){
c := make(chan os.Sign, 1)
sign.Notify(c, os.Interrupt)
mode := "kpop"
var stream string
var socket string
if len(os.Args) == 2 {
mode = os.Args[1]
}
}
We are able to use a swap perform to change between Ok-pop and J-pop music:
swap mode {
case "kpop":
stream = KPOP
socket = KSOCKET
case "jpop":
stream = JPOP
socket = JSOCKET
default:
fmt.Println("Error")
os.Exit(1)
}
Now, we are able to join and begin streaming music!
join.Begin(socket)
play.Play(stream)
interrupt := make(chan os.Sign, 1)
sign.Notify(interrupt, os.Interrupt)
<-interrupt
fmt.Println("Exiting Participant")
play.Cease()
join.Cease()
(Discover we cease first cease decoding audio, then disconnect from the WebSockets server)
The primary perform seems like this:
Listening to the radio 🎶🤘
- Run a
go get
to get all dependencies. - Run
go construct .
within the challenge. - Run
./kpop-cli kpop
to play Ok-pop music or./kpop-cli jpop
(If you happen to carried out J-pop).
Now you understand how to implement sound and WebSocket streaming in Go!
Additionally strive streaming different forms of knowledge sooner or later 😀
The total code could be discovered right here:
https://github.com/raymond-design/kpop-cli