Building a CLI Music Player in Go

Creating a terminal-based SomaFM player in Go was an interesting exercise in audio streaming and terminal UI design. This post details our implementation and the technical challenges we faced.

Why SomaFM?

SomaFM offers several advantages for a CLI project:

  • High-quality streams
  • Diverse music selection
  • Simple streaming protocols
  • No authentication required

Technical Implementation

1. Core Player Structure

type Player struct {
    currentStation *Station
    audioPlayer    *mpg123.Decoder
    output         *ao.Device
    volume         float64
    isPlaying      bool
    ui             *UI
}

type Station struct {
    Name     string
    URL      string
    Genre    string
    Bitrate  int
    Listeners int
}

func NewPlayer() (*Player, error) {
    // Initialize mpg123
    mpg123.Init()
    
    // Initialize audio output
    ao.Initialize()
    
    return &Player{
        volume: 1.0,
        ui:     NewUI(),
    }, nil
}

2. Audio Streaming

func (p *Player) Play(station *Station) error {
    // Stop current playback
    p.Stop()
    
    // Create new decoder
    decoder, err := mpg123.NewDecoder("")
    if err != nil {
        return err
    }
    
    // Open stream
    if err := decoder.Open(station.URL); err != nil {
        return err
    }
    
    // Setup audio output
    format := ao.Format{
        Bits:       16,
        Rate:       44100,
        Channels:   2,
        ByteFormat: ao.FormatNative,
    }
    
    device, err := ao.OpenLive(&format)
    if err != nil {
        return err
    }
    
    p.audioPlayer = decoder
    p.output = device
    p.currentStation = station
    p.isPlaying = true
    
    // Start playback loop
    go p.playbackLoop()
    
    return nil
}

func (p *Player) playbackLoop() {
    buffer := make([]byte, 4096)
    
    for p.isPlaying {
        // Read audio data
        length, err := p.audioPlayer.Read(buffer)
        if err != nil {
            break
        }
        
        // Apply volume
        p.applyVolume(buffer[:length])
        
        // Play audio
        p.output.Play(buffer[:length])
    }
}

Features

  1. Station Management:

    • List all available stations
    • Search by name or genre
    • Show current listeners
    • Display bitrate information
  2. Playback Controls:

    • Play/Pause
    • Volume control
    • Station switching
    • Stream quality selection
  3. UI Features:

    • Real-time station information
    • Progress bar
    • Keyboard shortcuts
    • Color themes

Conclusion

Building a CLI music player in Go was a great exercise in working with audio streams and terminal interfaces. The combination of Go’s concurrency features and the simplicity of SomaFM’s API made it possible to create a robust and user-friendly application.

Sometimes the simplest interfaces can provide the most enjoyable user experiences.