[Slimp3-checkins] CVS: slimp3/server/lib/SliMP3 Client.pm,NONE,1.1 Info.pm,1.33,1.34 OSDetect.pm,1.1
Brought to you by:
blackketter,
slimdevices
From: dean b. <bla...@us...> - 2002-04-25 15:51:33
|
Update of /cvsroot/slimp3/slimp3/server/lib/SliMP3 In directory usw-pr-cvs1:/tmp/cvs-serv11180/lib/SliMP3 Modified Files: Info.pm OSDetect.pm Playlist.pm Prefs.pm Added Files: Client.pm Log Message: Some code clean up. Moved client data structure and client protocol code to new Client.pm file from server.pl Removed or moved some globals that didn't really belong in server.pl to more approprate places. --- NEW FILE: Client.pm --- # SliMP3 Server Copyright (C) 2001 Sean Adams, Slim Devices Inc. # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License, # version 2. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # package SliMP3::Client; use strict; use Class::Struct; use IO::Socket; use IO::Select; my $IRMINTIME = 60000; # If time between IR commands > $irmintime, then the code is considered a new button press my $MAXCHUNKSIZE = 1400; # Reduce this if your path MTU is small # # This is a hash of clientState structs, indexed by the IP:PORT of the client # my %clients = (); # The following struct contains all the state that we keep about each player. # Each time we receive a packet for a particular client, we set the global $c # to point to the struct object for this client. # # FIXME: # Currently, we identify a client by it's IP address and source port. The # problem with this is that if the client is behind NAT, his UDP source port # may change at any time. In the future, we'll have the client send a unique # code with each request packet, and we'll allow his source port to change. struct( clientState => [ udpsock =>'$', # filehandle the socket we should use to talk to this client paddr =>'$', # sockaddr_in client's ip and port address =>'$', # string client's ip & port as string ip =>'$', # string client's ip deviceid =>'$', # device id 1=slimp3 revision =>'$', # int firmware rev 0=old, 0x13 = 1.3 lastirtime =>'$', # int time at which we last received an IR code (in client's 625KHz ticks) lastircode =>'$', # string the last IR command we received, so we can tell if a button's being held down irtimediff =>'$', # int calculated diff ir time easteregg =>'$', # string IR history for the easter egg, see IR.pm numirrepeats =>'$', # int number of times we've repeatedly received this code epochirtime =>'$', # int epoch time that we last received an IR signal modestack =>'@', # array stack of current browse modes: 'playlist', 'browse', 'off', etc... buttons =>'$', # \hash reference to hash of button routines for current mode lines =>'$', # \function reference to function to display lines for current mode blocklines =>'@', # strings what to display when we're blocked. trackInfoTrack =>'$', # string current trackinfo track trackInfoLine =>'$', # int current trackinfo line trackInfoLines =>'@', # strings current trackinfo lines browseID3selection =>'%', # hash current ID3 browsing location (artist, album, song, genre...) browseID3lastSelection =>'%', # hash the item that was last selected for a given browse position browseID3dir =>'@', # array current browsing list browseID3dirIndex =>'$', # int current browsing scroll position homeSelection =>'$', # int index into home selection: 'music', 'playlist', 'settings', ... gamesSelection =>'$', # int index into games list: 'slimtris', 'shooter' pwd =>'$', # string present directory, relative to $mp3dir currentDirItem =>'$', # int the index of the currently selected item (in @dirItems) numberOfDirItems =>'$', # int size of @dirItems FIXME this is redundant, right? dirItems =>'@', # strings list of file names in the current directory prevwptr =>'$', # int wptr at previous request - see protocol docs mp3filehandle =>'$', # filehandle the currently open MP3 file OR TCP stream mp3filehandleIsSocket =>'$', # bool becase Windows gets confused if you select on a regular file. waitforstart =>'$', # bool 1 = we've sent the client the start command, and we're waiting # for him to start a new stream chunk =>'$', # binary the last chunk of mpeg data we sent to the client (in case it gets lost) shoutMetaPointer =>'$', # int shoutcast metadata stream pointer shoutMetaInterval =>'$', # int shoutcast metadata interval vfdcustomchars =>'@', # array array of custom character strings (0 - 8) vfdbrightness =>'$', # int current brightness setting of the client's VFD display, range 0..4 prevline1 =>'$', # string what's currently on the the client's display, line 1 prevline2 =>'$', # string and line 2 lastSelection =>'%', # hash the curdiritem (integer) that was selected last time we # were in each directory (string) playlist => '@', # array playlist of songs shufflelist => '@', # bool indicates whether the items in the playlist are shuffled playmode => '$', # string 'stop', 'play', or 'pause' currentsong => '$', # int index into the playlist of the currently playing song (updated when reshuffled) songtime => '$', # float epoch time since start of song songtotalbytes => '$', # float length of this song in bytes songbytes => '$', # int number of bytes sent to client for current song currentplayingsong => '$', # string current song that's playing out from player. May not be the same as in the playlist as the client's buffer plays out. modetime => '$', # float epoch time of last mode change. htmlstatus => '$', # string html formatted status page htmlstatusvalid => '$', # bool current status is valid? browseplaylistindex => '$', # int if we are browsing the playlist, this is our current position searchSelection => '$', # int index into search selection searchFor => '$', # string what we are searching for from the remote: "ALBUMS", "ARTISTS", "SONGS" searchTerm => '@', # array of characters we are searching for searchCursor => '$', # int position of cursor (zero based) lastLetterIndex => '$', # int index into letters for each digit when using digits to type letters lastLetterDigit => '$', # int last digit hit while entering text lastLetterTime => '$', # int epoch time of previous letter currentSleepTime => '$', # int what the sleep time is currently set to otype => '@', # array game obstacles opos => '@', # array game obstacles cpos => '$', # int game player position gplay => '$', # int is the game playing? currentlyAnimating => '$', # \function use to find out if we are currently animating animationFunction => '$', # \function timer function to use to kill animation master => '$', # client if we're synchronized, 'master' points to master client slaves => '@', # clients if we're a master, this is an array of slaves which are synced to us RTT => '$', # float rtt estimate (seconds) ]); ################### # return the client based on IP address and socket. will create a new one if # necessary sub getClient { my ($udpsock,$clientpaddr) = @_; # return the cached copy if it exists my ($port, $ip) = sockaddr_in($clientpaddr); $ip = inet_ntoa($ip); my $address = $ip.":".$port; return $SliMP3::Client::clients{$address} if exists $SliMP3::Client::clients{$address}; # if we haven't seen this client, initialialize a new one if ($main::d_p) { my ($clientport, $clientip) = sockaddr_in($clientpaddr); print "New client connected: ".inet_ntoa($clientip)."\n"; } my $client = clientState->new(); $client->revision(0); $client->lastirtime(0); $client->lastircode(0); $client->numirrepeats(0); $client->epochirtime(0); $client->udpsock($udpsock); $client->paddr($clientpaddr); $client->address($address); $client->ip($ip); $client->prevwptr(-1); $client->pwd(''); # start browsing at the root MP3 directory $client->prevline1(''); $client->prevline2(''); $client->lastircode(''); $client->lastLetterIndex(0); $client->lastLetterDigit(''); $client->lastLetterTime(0); $client->playmode("stop"); $client->currentsong(0); $client->songtime(0); $client->songbytes(0); $client->songtotalbytes(0); $client->currentplayingsong(""); $client->htmlstatus(""); $client->htmlstatusvalid(0); $client->RTT(.5); SliMP3::Buttons::Playlist::jump($client); $client->searchCursor(0); $SliMP3::Client::clients{$address} = $client; # make sure any preferences this client may not have set are set to the default SliMP3::Prefs::checkClientPrefs($client); if (SliMP3::Prefs::clientGet($client,"powerOnBrightness") < 1) { # don't let it be completely dark. SliMP3::Prefs::clientSet($client,"powerOnBrightness", 1); } # temporarily give new player a name that is their ip address. # set up the default volume if none has been previously set if (!defined SliMP3::Prefs::clientGet($client,"playername")) { SliMP3::Prefs::clientSet($client,"playername", $ip); } # fire it up! SliMP3::Buttons::Power::powerUp($client); # start the screen saver SliMP3::Buttons::ScreenSaver::screenSaver($client); # say hello! SliMP3::Animation::showBriefly($client, SliMP3::Strings::string('WELCOME_TO_SLIMP3'), ""); # add the new client all the currently known clients & save, removing duplicates: my $clientlist = SliMP3::Prefs::get("clients"); if (defined($clientlist)) { $clientlist .= ",$ip:$port"; } else { $clientlist = "$ip:$port"; } my %seen = (); my @uniq = (); foreach my $item (split( /,/, $clientlist)) { push(@uniq, $item) unless $seen{$item}++ || $item eq ''; } SliMP3::Prefs::set("clients", join(',', @uniq)); return $client; } # This is all there is to it - it's like tftp, except that the client requests each # chunk, as opposed to acknowlegding the data as we send it. Client handles timeouts. sub gotRequest { # FIXME - it would be nice to have per-client and server-wide statistics on bytes sent, # packets dropped, average data rate, etc. my ($client, @octets) = @_; my $chunk = 0; my $wptrhp = $octets[2]; # see protocol docs my $wptrlp = $octets[3]; my $wptrh = unpack 'C', $wptrhp; my $wptrl = unpack 'C', $wptrlp; my $wptrhh = unpack 'H2', $wptrhp; my $wptrlh = unpack 'H2', $wptrlp; my $wptr = $wptrh * 256 + $wptrl; $main::d_pv && print "Request wptr = $wptr - $wptrhh.$wptrlh\n"; if (!($client->waitforstart())) { # if we're not waiting for the client to request the first chunk # of a new stream: if ($wptr != 0 ) { # if $wptr!=0 then this is probably a stray packet from the last stream we were playing # just log it and do nothing (it's normal). $main::d_p && print "Ignoring request from previous stream\n"; return; } } # if we're not currently playing anything, just ignore the client. # FIXME - shouldn't we send the stop command? if ((!$client->master) && ($client->playmode ne "play")) { $main::d_p && print "got a request, but we're not playing anything.\n"; } # packet was dropped, and the client requested a resend. if ($client->prevwptr() == $wptr) { $main::d_p && print "Duplicate request: wptr = $wptr\n"; } elsif ($client->mp3filehandle()) { if ($client->mp3filehandleIsSocket) { # If the MP3 file handle is a remote stream and it's not readable, # just return instead of blocking here. The client will repeat the # request. # my $selRead = IO::Select->new(); $selRead->add($client->mp3filehandle); my ($selCanRead,$selCanWrite)=IO::Select->select($selRead,undef,undef,0); if (!$selCanRead) { $main::d_p && print "remote stream not readable\n"; return; } else { $main::d_p && print "remote stream readable.\n"; } } my $chunksize = $MAXCHUNKSIZE; # adjust chunksize to lie on metadata boundary (for shoutcast/icecast) if ($client->shoutMetaInterval() && ($client->shoutMetaPointer() + $chunksize) > $client->shoutMetaInterval()) { $chunksize = $client->shoutMetaInterval() - $client->shoutMetaPointer(); } $client->mp3filehandle()->read($chunk, $chunksize); $client->chunk($chunk); $client->shoutMetaPointer($client->shoutMetaPointer() + $chunksize); # handle instream metadata for shoutcast/icecast if ($client->shoutMetaPointer() == $client->shoutMetaInterval()) { SliMP3::RemoteStream::readMetaData($client); $client->shoutMetaPointer(0); } } else { SliMP3::Control::stop($client); return 1; } # if nothing was read from the filehandle, then we're done with it, # so open the next filehandle. if (length($client->chunk()) == 0 || ($client->songtotalbytes() != 0 && ($client->songbytes()) > $client->songtotalbytes())) { # FIXME - all of this logic does not belong here! # It should all be encapsulated inside openNext. if (SliMP3::Prefs::clientGet($client, "repeat") == 2) { # play the next song and start over if necessary SliMP3::Playlist::skipsong($client); SliMP3::Playlist::openNext($client); } elsif (SliMP3::Prefs::clientGet($client, "repeat") == 1) { #play the same song again SliMP3::Playlist::openNext($client); } else { #stop at the end of the list if ($client->currentsong == (SliMP3::Playlist::count($client) - 1)) { $client->playmode("stop"); } else { SliMP3::Playlist::skipsong($client); SliMP3::Playlist::openNext($client); } return; } $client->mp3filehandle()->read($chunk, $MAXCHUNKSIZE); $client->chunk($chunk); $client->shoutMetaPointer($MAXCHUNKSIZE); } else { # pad to a length of 18 my $header = sprintf('m %-16s', $wptrhp.$wptrlp); # We must send an even number of bytes. if ((length($client->chunk()) % 2) != 0) { $client->chunk( $client->chunk() . '.' ); } $client->udpsock()->send( $header.$client->chunk(), 0, $client->paddr()); $client->songbytes($client->songbytes + length($client->chunk())); # set the start time of the song once we have filled the client's buffer if ($client->songtime == 0 && $client->songbytes >= (128 * 1024)) { $client->songtime(time()); $client->currentplayingsong(SliMP3::Playlist::currentSong($client)); } $main::d_pv && print "Sending ".length($client->chunk())." bytes. wptr = $wptrhh.$wptrlh\n"; } $client->prevwptr($wptr); } sub processMessage { my ($client,$msg) = @_; my @octets = split(//,$msg); my $type = unpack('a',$octets[0]); # Packet format for IR code. Numbers are unsigned, network order. # # [0] 'i' # [1] 0x00 # [2..5] player's time since startup in 'ticks' @ 625 KHz # [6] 0xFF (will eventually be an identifier for different IR code sets) # [7] number of bits ( always 16 for JVC ) # [8..11] the IR code, up to 32 bits # [12..17] reserved/ignored if ($type eq 'i') { if ($main::d_u) { print 'irCode: ['; for (my $oi = 0; $oi <= 11; $oi++) { print unpack('H2',$octets[$oi]), ' '; } print "]\n"; } # translate the 32-bit IR code into an button identifier, eg "up" or "play" my $irCode = SliMP3::IR::lookup(unpack('H*', $octets[8].$octets[9].$octets[10].$octets[11])); my $irTime = unpack('N',$octets[2].$octets[3].$octets[4].$octets[5]); $client->irtimediff($irTime - $client->lastirtime()); $client->lastirtime($irTime); if ($main::d_u) { print "irCode = [$irCode] timer = [$irTime] timediff = [$client->irtimediff] " . "last = [".$client->lastircode()."] numrep = [".$client->numirrepeats()."]\n"; print "irmintime: [$IRMINTIME]\n"; } if (defined($irCode) && ($irCode eq ($client->lastircode())) && ($client->irtimediff < $IRMINTIME)) { SliMP3::IR::repeatCode($client); } else { $client->numirrepeats(0); SliMP3::IR::processCode($client, $irCode); } } elsif ($type eq 'r') { gotRequest($client, @octets); } elsif ($type eq 'h') { SliMP3::Discovery::gotHello($client, unpack('C',$octets[1]), unpack('C',$octets[2]) ); } elsif ($type eq '2') { # NOP - just the client acknowledging and i2c command. } elsif ($type eq 'd') { SliMP3::Discovery::gotDiscoveryRequest($client); } elsif ($type eq 'a') { my $wptr = unpack('C', $octets[6]) * 256 + unpack('C', $octets[7]); my $rptr = unpack('C', $octets[8]) * 256 + unpack('C', $octets[9]); my $seq = unpack('C', $octets[10]) * 256 + unpack('C', $octets[11]); SliMP3::Stream::gotAck($client, $wptr, $rptr, $seq); } else { $main::d_u && print "debug: unknown type: [$type]\n"; } return 1; } 1; Index: Info.pm =================================================================== RCS file: /cvsroot/slimp3/slimp3/server/lib/SliMP3/Info.pm,v retrieving revision 1.33 retrieving revision 1.34 diff -C2 -d -r1.33 -r1.34 *** Info.pm 28 Mar 2002 05:28:36 -0000 1.33 --- Info.pm 25 Apr 2002 15:51:28 -0000 1.34 *************** *** 63,70 **** # TODO: MacOS X should really store this in a visible, findable place. ! if ($main::OS eq 'unix') { # try the default one (in the home directory) should be read... $dbname = catdir(SliMP3::Prefs::preferencesPath(), '.slimp3info.db'); ! } elsif ($main::OS eq 'win') { $dbname = catdir(SliMP3::Prefs::preferencesPath(), 'SLIMP3INFO.DB'); } else { --- 63,70 ---- # TODO: MacOS X should really store this in a visible, findable place. ! if (SliMP3::OSDetect::OS() eq 'unix') { # try the default one (in the home directory) should be read... $dbname = catdir(SliMP3::Prefs::preferencesPath(), '.slimp3info.db'); ! } elsif (SliMP3::OSDetect::OS() eq 'win') { $dbname = catdir(SliMP3::Prefs::preferencesPath(), 'SLIMP3INFO.DB'); } else { Index: OSDetect.pm =================================================================== RCS file: /cvsroot/slimp3/slimp3/server/lib/SliMP3/OSDetect.pm,v retrieving revision 1.12 retrieving revision 1.13 diff -C2 -d -r1.12 -r1.13 *** OSDetect.pm 16 Mar 2002 22:59:52 -0000 1.12 --- OSDetect.pm 25 Apr 2002 15:51:28 -0000 1.13 *************** *** 10,40 **** use strict; # # Figures out where the preferences file should be on our platform, and loads it. ! # also sets the global $main::OS to 'unix' 'win' or 'mac' # sub init { ! if (!$main::OS || ($main::OS eq 'auto')) { $main::d_f && print "Auto-detecting OS: $^O\n"; if ($^O =~/^macos/i) { ! $main::OS = 'mac'; } elsif ($^O =~ /^m?s?win/i) { ! $main::OS = 'win'; } else { ! $main::OS = 'unix'; } ! $main::d_f && print "I think it's \"$main::OS\".\n"; } else { ! $main::d_f && print "OS detection skipped, using \"$main::OS\".\n"; } # figure out where the prefs file should be on this platform: ! # if ($main::OS eq 'mac') { # # die "Sorry, the SliMP3 server runs on MacOS X, but not Mac OS Classic (9.X)" --- 10,46 ---- use strict; + my $detectedOS = 'auto'; # Usually 'auto' - can override with 'unix' or 'win' + + sub OS { + return $detectedOS; + } + # # Figures out where the preferences file should be on our platform, and loads it. ! # also sets the global $detectedOS to 'unix' 'win' or 'mac' # sub init { ! if (!$detectedOS || ($detectedOS eq 'auto')) { $main::d_f && print "Auto-detecting OS: $^O\n"; if ($^O =~/^macos/i) { ! $detectedOS = 'mac'; } elsif ($^O =~ /^m?s?win/i) { ! $detectedOS = 'win'; } else { ! $detectedOS = 'unix'; } ! $main::d_f && print "I think it's \"$detectedOS\".\n"; } else { ! $main::d_f && print "OS detection skipped, using \"$detectedOS\".\n"; } # figure out where the prefs file should be on this platform: ! # if ($detectedOS eq 'mac') { # # die "Sorry, the SliMP3 server runs on MacOS X, but not Mac OS Classic (9.X)" Index: Playlist.pm =================================================================== RCS file: /cvsroot/slimp3/slimp3/server/lib/SliMP3/Playlist.pm,v retrieving revision 1.77 retrieving revision 1.78 diff -C2 -d -r1.77 -r1.78 *** Playlist.pm 20 Apr 2002 05:49:52 -0000 1.77 --- Playlist.pm 25 Apr 2002 15:51:28 -0000 1.78 *************** *** 521,525 **** } elsif ($p0 eq "button") { # hack this in to pretend like we haven't gotten a button in a while... ! $client->irtimediff(3 * $main::IRMINTIME); $client->epochirtime(time); SliMP3::IR::executeButton($client, $p1); --- 521,525 ---- } elsif ($p0 eq "button") { # hack this in to pretend like we haven't gotten a button in a while... ! $client->irtimediff(3 * $SliMP3::Client::IRMINTIME); $client->epochirtime(time); SliMP3::IR::executeButton($client, $p1); Index: Prefs.pm =================================================================== RCS file: /cvsroot/slimp3/slimp3/server/lib/SliMP3/Prefs.pm,v retrieving revision 1.37 retrieving revision 1.38 diff -C2 -d -r1.37 -r1.38 *** Prefs.pm 29 Mar 2002 07:37:10 -0000 1.37 --- Prefs.pm 25 Apr 2002 15:51:28 -0000 1.38 *************** *** 265,278 **** } ! if ($main::OS eq 'unix') { $prefsPath = $ENV{'HOME'}; } #FIXME: On MacOS, use the correct way to find "System Folder/Preferences" ! if ($main::OS eq 'mac') { $prefsPath = $Bin; } ! if ($main::OS eq 'win') { # We used to save prefs in C:\WINDOWS or C:\WINNT --- 265,278 ---- } ! if (SliMP3::OSDetect::OS() eq 'unix') { $prefsPath = $ENV{'HOME'}; } #FIXME: On MacOS, use the correct way to find "System Folder/Preferences" ! if (SliMP3::OSDetect::OS() eq 'mac') { $prefsPath = $Bin; } ! if (SliMP3::OSDetect::OS() eq 'win') { # We used to save prefs in C:\WINDOWS or C:\WINNT *************** *** 297,310 **** my $pref_path = preferencesPath(); ! if ($main::OS eq 'unix') { # try the default one (in the home directory) should be read... $oldPrefsFile = catdir($pref_path, '.slimp3.conf'); } ! if ($main::OS eq 'win') { $oldPrefsFile = catdir($pref_path, 'SLIMP3.INI'); } ! if ($main::OS eq 'mac') { $oldPrefsFile = ''; # legacy prefs file doens't apply to MacOS } --- 297,310 ---- my $pref_path = preferencesPath(); ! if (SliMP3::OSDetect::OS() eq 'unix') { # try the default one (in the home directory) should be read... $oldPrefsFile = catdir($pref_path, '.slimp3.conf'); } ! if (SliMP3::OSDetect::OS() eq 'win') { $oldPrefsFile = catdir($pref_path, 'SLIMP3.INI'); } ! if (SliMP3::OSDetect::OS() eq 'mac') { $oldPrefsFile = ''; # legacy prefs file doens't apply to MacOS } *************** *** 318,331 **** my $pref_path = preferencesPath(); ! if ($main::OS eq 'unix') { # try the default one (in the home directory) should be read... $prefsFile = catdir($pref_path, '.slimp3.pref'); } ! if ($main::OS eq 'win') { $prefsFile = catdir($pref_path, 'SLIMP3.PRF'); } ! if ($main::OS eq 'mac') { $prefsFile = catdir($pref_path, 'SliMP3 Preferences'); } --- 318,331 ---- my $pref_path = preferencesPath(); ! if (SliMP3::OSDetect::OS() eq 'unix') { # try the default one (in the home directory) should be read... $prefsFile = catdir($pref_path, '.slimp3.pref'); } ! if (SliMP3::OSDetect::OS() eq 'win') { $prefsFile = catdir($pref_path, 'SLIMP3.PRF'); } ! if (SliMP3::OSDetect::OS() eq 'mac') { $prefsFile = catdir($pref_path, 'SliMP3 Preferences'); } *************** *** 352,356 **** ! if ($main::OS eq 'unix') { # if it ain't there, then try a global one unless (-r $oldPrefsFile) { --- 352,356 ---- ! if (SliMP3::OSDetect::OS() eq 'unix') { # if it ain't there, then try a global one unless (-r $oldPrefsFile) { |