Version 0 of basic snack stream player 2 - shoutcast

Updated 2005-01-10 17:07:08

DZ - 10 Jan 2005: This is a simple shoutcast stream player. It supports stream titles, future versions should support saving stream to disk.

 ###### player-cmd.tcl ######################################################
 #
 # Shoutcast stream player
 #
 # Copyright 2005 - DZ
 #
 # Shared under NOL licence : http://wiki.tcl.tk/nol
 #
 # Version 0.1 - January 2005
 #
 ############################################################################
 #
 # This is a simple shoutcast stream player. It supports titles. Future
 # versions will support saving the stream to disk.
 #
 #
 #
 #        connect
 #        readHeader
 #         /      \
 # readStream   readStreamMetaInt
 #              readTitleLength
 #              readTitle
 #

 package require http
 package require Memchan
 package require snack


 set doDebug 0

 proc debug {text} {
   if {$::doDebug == 1} {
     puts $text
   }
 }



 namespace eval shoutcast {

   namespace export *

   set header(icy-metaint) 0
   set doDebug 0
   set title "No data"

   set readSize 8192
   set total 0

   set s {}
 }


 proc shoutcast::init {} {

   package forget snack
   package require snack

   array unset header
   set header(icy-metaint) 0
   set title "No data"

   set readSize 8192
   set total 0

   set s {}
   set sock {}

   set fd {}
 }

 proc shoutcast::openChannel {} {
   variable fd

 # save stream in file
 #  set fd [open 1.mp3 w]
 #  fconfigure $fd -translation binary

   set fd [fifo]
   fconfigure $fd -translation binary -encoding binary \
     -buffering full -buffersize 200000 ;# -blocking true

 }

 proc shoutcast::closeChannel {} {
   variable fd

   catch {close $fd}
 }


 proc shoutcast::initSnack {} {
   variable s

   if {$s == {}} {
     set s [snack::sound s]
   }
 }


 proc shoutcast::connect {url port} {
   global sock

   shoutcast::init
   shoutcast::openChannel
   shoutcast::initSnack

   set sock [socket $url $port]

   fconfigure $sock -blocking 0 -buffering full -buffersize 100000 \
     -translation {binary auto}
 #  fconfigure $fd -translation binary -encoding binary \
 #    -buffering full -buffersize 200000 ;# -blocking true


   append buff "GET / HTTP/1.0\n"
   append buff "Host: $url\n"
   append buff "Accept: */*\n"
   append buff "User-Agent: xmms/1.2.7\n"
   append buff "Icy-MetaData:1\n"
   #append buff "x-audiocast-udpport: $::udp_port\n"
   append buff "\n"

   puts $sock $buff
   flush $sock

 #  after 2000

   fileevent $sock readable { shoutcast::readHeader $sock }
 }


 proc shoutcast::readHeader {sock} {
   variable header
   variable fd

   set count [gets $sock line]
   if {$count == -1} { return }


   set h [split $line ":"]
   if {[llength $h] == 2} {
     foreach {key value} $h { set header($key) $value }
   }



   if {$count == 1 && $line == "\r"} {

     if {$header(icy-metaint) != 0} {
       fileevent $sock readable { shoutcast::readStreamMetaInt $sock }
     } else {
       fileevent $sock readable { shoutcast::readStream $sock }
     }
   }
 }


 proc shoutcast::readStream {sock} {
   variable readSize
   variable total
   variable fd

   set data [read $sock 4096]

   puts -nonewline $fd $data
 }


 proc shoutcast::readStreamMetaInt {sock} {
   variable readSize
   variable total
   variable fd


   set data [read $sock $readSize]
   set count [string length $data]
   set total [incr total $count]

   puts -nonewline $fd $data

   if {$total != 8192} {
     set readSize [expr {$readSize - $count}]
     debug "-count: $count, total: $total, readSize: $readSize"
   } else {
     debug "+count: $count, total: $total, readSize: $readSize"
     set readSize 8192
     set total 0
     fileevent $sock readable { shoutcast::readTitleLength $sock }
   }
 }

 proc shoutcast::readTitleLength {sock} {
   set c 0
   set titleSize [read $sock 1]

   scan $titleSize %c c
   debug "c: $c"

   set titleSize [expr {$c * 16}]

   fileevent $sock readable [list shoutcast::readTitle $sock $titleSize]
 }

 proc shoutcast::readTitle {sock size} {
   variable title

   #StreamTitle='';StreamUrl='';

   if {$size != 0} {
     set t [read $sock $size]
     set t [string trim $t]
     set rx [regexp -- {StreamTitle='(.*)';StreamUrl='(.*)';} $t - _title url]
     if {$rx} {
       set title $_title
     }
   }

   fileevent $sock readable { shoutcast::readStreamMetaInt $sock }
 }

 proc shoutcast::play {} {
   variable s
   variable fd

   $s configure -channel $fd -buffersize 100000 -debug 0
   $s play ;#-command shoutcast::disconnect
 }

 proc shoutcast::stop {} {
   variable s

   $s stop
   shoutcast::disconnect
   $s destroy
 }

 proc shoutcast::disconnect {} {
   global sock
   catch {close $sock}

   shoutcast::closeChannel
 }

This is the GUI:

 ###### player-gui.tcl ######################################################  
 #                                                   
 # Shoutcast stream player - gui
 #
 # Copyright 2005 - DZ
 #
 # Shared under NOL licence : http://wiki.tcl.tk/nol
 #
 # Version 0.1 - January 2005
 #
 ############################################################################

 source player-cmd.tcl


 #set host 127.0.0.1
 #set port 5002

 set host 206.98.167.99
 set port 8712


 namespace eval player {

   namespace export *

   set status stop
 }

 proc player::createGui {} {
   label .title -textvariable shoutcast::title
   pack .title -fill both -expand 1
   button .play -text Play -command player::cmdPlay
   button .stop -text Stop -command player::cmdStop
   button .record -text Rec
   button .quit -text Quit -command player::cmdQuit

   pack .play .stop .record .quit -side left
 }


 proc player::cmdQuit {} {
   variable status

   if {$status == "play"} {
     shoutcast::stop
     set status stop
   }

   exit
 }

 proc player::cmdPlay {} {
   variable status

   if {$status == "play"} {
 #    shoutcast::stop
 #    set status stop
      return
   }

   shoutcast::connect $host $port
   set status play

   after 3000 shoutcast::play
 }

 proc player::cmdStop {} {
   variable status

   if {$status == "play"} {
     shoutcast::stop
     set status stop
   }
 }


 if {0} {
 proc showInfo {} {

   puts [$shoutcast::s info]
   after 3000 showInfo
 }
 after 3000 showInfo
 }

 player::createGui