spotify-ws/main.go

153 lines
4.7 KiB
Go

package main
import (
"context"
"log"
"net/http"
"os"
"time"
"github.com/gorilla/websocket"
"github.com/joho/godotenv"
"github.com/zmb3/spotify"
"golang.org/x/oauth2"
)
var (
clients = make(map[*websocket.Conn]bool) // Map to keep track of connected clients
broadcast = make(chan *spotify.CurrentlyPlaying) // Channel for broadcasting currently playing track
upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true }, // Allow all origins
HandshakeTimeout: 10 * time.Second, // Timeout for WebSocket handshake
ReadBufferSize: 1024, // Buffer size for reading incoming messages
WriteBufferSize: 1024, // Buffer size for writing outgoing messages
Subprotocols: []string{"binary"}, // Supported subprotocols
Error: func(w http.ResponseWriter, r *http.Request, status int, reason error) {
log.Printf("Error upgrading WebSocket connection: %v", reason)
},
}
spotifyClient spotify.Client // Spotify API client
tokenSource oauth2.TokenSource // OAuth2 token source
config *oauth2.Config // OAuth2 configuration
)
func main() {
// Load environment variables from .env file if not already set
if os.Getenv("CLIENT_ID") == "" || os.Getenv("CLIENT_SECRET") == "" || os.Getenv("REFRESH_TOKEN") == "" {
if err := godotenv.Load(); err != nil {
log.Fatalf("Error loading .env file: %v", err)
}
}
clientID := os.Getenv("CLIENT_ID")
clientSecret := os.Getenv("CLIENT_SECRET")
refreshToken := os.Getenv("REFRESH_TOKEN")
// Setup OAuth2 configuration for Spotify API
config = &oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
Endpoint: oauth2.Endpoint{
AuthURL: "https://accounts.spotify.com/authorize",
TokenURL: "https://accounts.spotify.com/api/token",
},
}
token := &oauth2.Token{RefreshToken: refreshToken}
tokenSource = config.TokenSource(context.Background(), token)
// Create an OAuth2 HTTP client
httpClient := oauth2.NewClient(context.Background(), tokenSource)
spotifyClient = spotify.NewClient(httpClient)
// Handle WebSocket connections at the root endpoint
http.HandleFunc("/", ConnectionHandler)
// Log server start-up and initialize background tasks
log.Println("Server started on :3000")
go TrackFetcher() // Periodically fetch currently playing track from Spotify
go MessageHandler() // Broadcast messages to connected clients
// Start the HTTP server
if err := http.ListenAndServe(":3000", nil); err != nil {
log.Fatalf("Error starting server: %v", err)
}
}
// ConnectionHandler upgrades HTTP connections to WebSocket and handles communication with clients
func ConnectionHandler(w http.ResponseWriter, r *http.Request) {
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
defer ws.Close()
clients[ws] = true
// Immediately send the current track to the newly connected client
currentTrack, err := spotifyClient.PlayerCurrentlyPlaying()
if err != nil {
log.Printf("Error getting currently playing track: %v", err)
ws.Close()
delete(clients, ws)
return
}
// Send the current track information to the client
err = ws.WriteJSON(currentTrack)
if err != nil {
ws.Close()
delete(clients, ws)
return
}
// Keep the connection open to listen for incoming messages (heartbeat)
for {
_, _, err := ws.ReadMessage()
if err != nil {
delete(clients, ws)
break
}
}
}
// MessageHandler continuously listens for messages on the broadcast channel and sends them to all connected clients
func MessageHandler() {
for {
msg := <-broadcast
for client := range clients {
err := client.WriteJSON(msg)
if err != nil {
client.Close()
delete(clients, client)
}
}
}
}
// TrackFetcher periodically fetches the currently playing track from the Spotify API and broadcasts it to clients
func TrackFetcher() {
for {
// Fetch the currently playing track
current, err := spotifyClient.PlayerCurrentlyPlaying()
if err != nil {
log.Printf("Error getting currently playing track: %v", err)
// Refresh the access token if it has expired
if err.Error() == "token expired" {
log.Println("Token expired, refreshing token...")
newToken, err := tokenSource.Token()
if err != nil {
log.Fatalf("Couldn't refresh token: %v", err)
}
httpClient := oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(newToken))
spotifyClient = spotify.NewClient(httpClient)
}
// Wait before retrying to avoid overwhelming the API
time.Sleep(30 * time.Minute)
continue
}
// Broadcast the current track information to all clients
broadcast <- current
// Fetch track information every 5 seconds
time.Sleep(5 * time.Second)
}
}