An Audio Server

George Peter Staplin Mon Sep 17, 2007 I wanted to listen to some music I had, and I looked at the available software such as icecast, and some others, and I was disappointed by the complexity. I decided it was easier to build my own than read the many pages of documentation and config files to make sure I had a secure audio server.

So this is what I created. It works with WMP, xmms, and mplayer over a network.

Usage on the client side:

 mplayer http://localhost:6666

Server-side usage:

 tclsh8.5 audio_server.tcl /path/to/*.mp3

By the way, it's easy to change it to use ogg or flac.


 set ::clients [list]
 set ::songs [list]
 set ::song_offset 0
 set ::song_fd ""
 set ::packet ""
 
 array set ::ready {}
 
 proc ready sock {
   global ready clients
 
   set ready($sock) $sock
 
   if {[array size ready] >= [llength $clients]} {
     foreach {key sock} [array get ready] {
       send-data $sock
     }
     array unset ready
     array set ready {}
     advance
   }
 }
 
 set ::counter 1
 
 proc send-data sock {
   global packet clients
  
   if {[catch {puts -nonewline $sock $packet} err]} {
     puts stderr $err
     catch {close $sock}
     set i [lsearch -exact $clients $sock]
     set clients [lreplace $clients $i $i]
   }
   puts "sent packet to $sock $::counter"
   incr ::counter
 }
 
 proc advance {} {
   global packet song_fd song_offset songs
 
   if {"" eq $song_fd} {
     set song_fd [open [lindex $songs $song_offset] r]
     fconfigure $song_fd -translation binary
   }
 
   set packet [read $song_fd 4096]
 
   if {[eof $song_fd]} {
     catch {close $song_fd}
     set song_fd ""
     incr song_offset 
     if {$song_offset >= [llength $songs]} {
       set song_offset 0
     }
   }
 }
 
 proc accept {sock addr port} {
   global clients
   lappend clients $sock
   fconfigure $sock -blocking 1 -translation binary
 
   puts "Connection from ${addr}:$port"
 
   if {[catch {puts -nonewline $sock "HTTP/1.1 200 OK\r\n"
   puts -nonewline $sock "Content-Type: audio/mpeg\r\n"
   puts -nonewline $sock "\r\n"
   flush $sock} err]} {
     puts stderr $err
     set i [lsearch -exact $clients $sock]
     set clients [lreplace $clients $i $i]
     catch {close $sock}
   }
 
   fileevent $sock writable [list ready $sock]
 }
 
 proc main {argc argv} {
   set ::songs $argv
   socket -server accept 6666
   advance
 
   vwait _forever_
 }
 main $::argc $::argv