Add support of Logitech Squeezebox Server integration
This commit is contained in:
+260
-4
@@ -23,10 +23,12 @@
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
# OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
use 5.10.0;
|
||||
|
||||
use strict;
|
||||
|
||||
use 5.10.0;
|
||||
# For given() { when() { } ... }
|
||||
use feature ":5.10";
|
||||
|
||||
use constant PORT => 5000;
|
||||
|
||||
use Getopt::Long;
|
||||
@@ -38,6 +40,7 @@ use IO::Socket;
|
||||
use MIME::Base64;
|
||||
use HTTP::Request;
|
||||
use HTTP::Response;
|
||||
use URI::Escape;
|
||||
use IPC::Open2;
|
||||
use Crypt::OpenSSL::RSA;
|
||||
use Digest::MD5 qw/md5_hex/;
|
||||
@@ -60,6 +63,14 @@ my $libao_devicename;
|
||||
my $libao_deviceid;
|
||||
# suppose hairtunes is under same directory
|
||||
my $hairtunes_cli = $FindBin::Bin . '/hairtunes';
|
||||
# Integrate with Squeezebox Server
|
||||
my $squeeze;
|
||||
# SBS CLI port
|
||||
my $cliport;
|
||||
# SB target
|
||||
my $mac;
|
||||
# SB volume
|
||||
my $volume;
|
||||
# output debugging information
|
||||
my $verbose;
|
||||
# where to write PID
|
||||
@@ -82,6 +93,10 @@ GetOptions("a|apname=s" => \$apname,
|
||||
"ao_deviceid=s" => \$libao_deviceid,
|
||||
"v|verbose" => \$verbose,
|
||||
"w|writepid=s" => \$writepid,
|
||||
"s|squeezebox" => \$squeeze,
|
||||
"c|cliport=s" => \$cliport,
|
||||
"m|mac=s" => \$mac,
|
||||
"l|volume=s" => \$volume,
|
||||
"h|help" => \$help);
|
||||
|
||||
sub usage {
|
||||
@@ -96,6 +111,10 @@ sub usage {
|
||||
" --ao_driver=driver Sets the ao driver (optional)\n",
|
||||
" --ao_devicename=devicename Sets the ao device name (optional)\n",
|
||||
" --ao_deviceid=id Sets the ao device id (optional)\n",
|
||||
" -s --squeezebox Enables local Squeezebox Server integration\n",
|
||||
" -c --cliport=port Sets the SBS CLI port\n",
|
||||
" -m --mac=address Sets the SB target device\n",
|
||||
" -l --volume=level Sets the SB volume level (in %)\n",
|
||||
" -d Daemon mode\n",
|
||||
" -w --writepid=path Write PID to this location\n",
|
||||
" -v --verbose Print debugging messages\n",
|
||||
@@ -112,6 +131,81 @@ if (defined($verbose) && $verbose) {
|
||||
$verbose = 0;
|
||||
}
|
||||
|
||||
$cliport = ( (defined( $cliport ) && $cliport && $cliport =~ m/^[0-9]+/ && $cliport > 1023 && $cliport < 65536 ) ? $cliport : 9090 );
|
||||
|
||||
$volume = ( (defined( $volume ) && $volume && $volume =~ m/^[0-9]+/ && $volume >= 0 && $volume <= 101 ) ? $volume : undef );
|
||||
|
||||
if (defined($squeeze) && $squeeze) {
|
||||
my $players;
|
||||
my @details;
|
||||
|
||||
my $response;
|
||||
my $socket = IO::Socket::INET -> new (
|
||||
PeerAddr => "127.0.0.1"
|
||||
, PeerPort => $cliport
|
||||
, Proto => 'tcp'
|
||||
, Timeout => 1
|
||||
);
|
||||
if( !( $socket ) ) {
|
||||
print "ERROR: Could not create socket to interface with SqueezeBox Server on port $cliport: $!\n";
|
||||
print "WARN: Disabling Squeezebox Server integration\n";
|
||||
undef $squeeze;
|
||||
}
|
||||
if( $squeeze ) {
|
||||
print $socket "player count ?\n";
|
||||
$response = <$socket>;
|
||||
if( !( defined( $response ) ) ) {
|
||||
print "ERROR: Could not communicate with SqueezeBox Server on port $cliport\n";
|
||||
print "WARN: Disabling Squeezebox Server integration\n";
|
||||
undef $squeeze;
|
||||
} else {
|
||||
$players = ( $response =~ m/^player count ([0-9]+)$/ );
|
||||
print $socket "players 0 $players\n";
|
||||
$response = <$socket>;
|
||||
@details = split( /playerindex%3A/ , $response );
|
||||
close( $socket );
|
||||
shift( @details );
|
||||
for( my $n = 0; $n <= scalar( @details ); $n++ ) {
|
||||
if( defined( $details[ $n ] ) ) {
|
||||
my $address = $details[ $n ];
|
||||
$address =~ s/^.*playerid%3A([[:xdigit:]%]+)\s.*$/$1/;
|
||||
$address =~ s/%3A/:/g;
|
||||
chomp $address;
|
||||
$details[ $n ] = $address;
|
||||
}
|
||||
}
|
||||
print "Discovered players: $players\n" if $verbose;
|
||||
print "Player MAC addresses:\n" if $verbose;
|
||||
foreach my $address (@details) {
|
||||
print "\t$address\n" if( ( defined( $address ) && $address ) && $verbose );
|
||||
}
|
||||
|
||||
if( defined( $mac ) && $mac ) {
|
||||
chomp $mac;
|
||||
if( !( grep { lc( $_ ) eq lc( $mac ) } @details ) ) {
|
||||
print "ERROR: Invalid or non-present MAC specified.\n\n";
|
||||
print "Please select a target MAC address from:\n";
|
||||
foreach my $address (@details) {
|
||||
print "\t$address\n" if( defined( $address ) && $address );
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
if( 1 == $players ) {
|
||||
$mac = $details[ 0 ];
|
||||
print "WARN: No Squeezebox player specified, using $mac.\n";
|
||||
} else {
|
||||
print "ERROR: No Squeezebox player specified, please select a target MAC address with the '--mac' option with a value from:\n";
|
||||
foreach my $address (@details) {
|
||||
print "\t$address\n" if( defined( $address ) && $address );
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
$mac = uri_escape( $mac ) if( defined( $mac ) && $mac );
|
||||
}
|
||||
}
|
||||
};
|
||||
chomp $apname;
|
||||
|
||||
my @hw_addr = (0, map { int rand 256 } 1..5);
|
||||
@@ -124,10 +218,17 @@ $SIG{PIPE} = \&POPE;
|
||||
|
||||
|
||||
our $avahi_publish;
|
||||
our $squeezebox_setup;
|
||||
|
||||
sub REAP {
|
||||
if ($avahi_publish == waitpid(-1, WNOHANG)) {
|
||||
die("avahi daemon terminated or 'avahi-publish-service' binary not found");
|
||||
my $pid = waitpid( -1, WNOHANG );
|
||||
given( $pid ) {
|
||||
when( $avahi_publish ) {
|
||||
die( "avahi daemon terminated or 'avahi-publish-service' binary not found" );
|
||||
}
|
||||
when( $squeezebox_setup ) {
|
||||
print( "Squeezebox configuration routine completed\n" ) if $verbose;
|
||||
}
|
||||
}
|
||||
print("Child exited\n") if $verbose;
|
||||
$SIG{CHLD} = \&REAP;
|
||||
@@ -140,11 +241,21 @@ $SIG{TERM} = $SIG{INT} = sub {
|
||||
print basename($0) . " killed\n";
|
||||
map { eval { kill $_->{decoder_pid} } } keys %conns;
|
||||
kill 9, $avahi_publish if $avahi_publish;
|
||||
# Clean up any running squeezebox_setup processes...
|
||||
my $child;
|
||||
do {
|
||||
$child = waitpid( -1, WNOHANG );
|
||||
} while $child > 0;
|
||||
exit 0;
|
||||
};
|
||||
$SIG{__DIE__} = sub {
|
||||
map { eval { kill $_->{decoder_pid} } } keys %conns;
|
||||
kill 9, $avahi_publish if $avahi_publish;
|
||||
# Clean up any running squeezebox_setup processes...
|
||||
my $child;
|
||||
do {
|
||||
$child = waitpid( -1, WNOHANG );
|
||||
} while $child > 0;
|
||||
};
|
||||
|
||||
$avahi_publish = fork();
|
||||
@@ -224,6 +335,147 @@ if (defined($writepid) && $writepid) {
|
||||
|
||||
print "Listening...\n" if $verbose;
|
||||
|
||||
sub performSqueezeboxSetup {
|
||||
$squeezebox_setup = fork();
|
||||
if( 0 == $squeezebox_setup ) {
|
||||
my $items;
|
||||
my @favourites;
|
||||
my @ids;
|
||||
my $index;
|
||||
|
||||
my $response;
|
||||
|
||||
my $findFavourites = sub {
|
||||
my ( $socket ) = @_;
|
||||
print $socket "favorites items\n";
|
||||
$response = <$socket>;
|
||||
$response =~ s/^\s*favorites\s+items\s+count%3A([0-9]+)\s*$/$1/;
|
||||
$items = $response;
|
||||
print "Found $items favourites...\n" if $verbose;
|
||||
|
||||
print $socket "favorites items 0 $items want_url%3A1\n";
|
||||
$response = <$socket>;
|
||||
undef( @favourites );
|
||||
@favourites = split( /id%3A/ , $response );
|
||||
@ids = split( /id%3A/ , $response );
|
||||
shift( @favourites );
|
||||
shift( @ids );
|
||||
for( my $n = 0; $n <= scalar( @favourites ); $n++ ) {
|
||||
if( defined( $favourites[ $n ] ) ) {
|
||||
my $url = $favourites[ $n ];
|
||||
$url =~ s/^.*url%3A([^ ]+)\s.*$/$1/;
|
||||
$url = uri_unescape( $url );
|
||||
chomp $url;
|
||||
print "\tFavourite with URL '$url' " if $verbose;
|
||||
$favourites[ $n ] = $url;
|
||||
}
|
||||
if( defined( $ids[ $n ] ) ) {
|
||||
my $id = $ids[ $n ];
|
||||
$id =~ s/^([^ ]+)\s.*$/$1/;
|
||||
chomp $id;
|
||||
my @components = split( /\./, $id );
|
||||
shift ( @components );
|
||||
$id = join( '.', @components );
|
||||
print "and ID '$id'\n" if $verbose;
|
||||
$ids[ $n ] = $id;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
my $socket = IO::Socket::INET -> new (
|
||||
PeerAddr => "127.0.0.1"
|
||||
, PeerPort => ( (defined( $cliport ) and $cliport ) ? $cliport : 9090 )
|
||||
, Proto => 'tcp'
|
||||
, Timeout => 1
|
||||
) or die "Could not create socket: $!";
|
||||
|
||||
&$findFavourites( $socket );
|
||||
|
||||
print "Favourites URLs:\n" if $verbose;
|
||||
foreach my $url (@favourites) {
|
||||
print "\t$url\n" if( ( defined( $url ) && $url ) && $verbose );
|
||||
}
|
||||
my $okay = 1;
|
||||
if( !( grep { $_ =~ m/^wavin:/ } @favourites ) ) {
|
||||
$okay = 0;
|
||||
print "INFO: AirPlay 'wavin' Favourite does not exist - creating... ";
|
||||
print $socket "favorites add url%3Awavin%3Aairplay title%3AAirPlay\n";
|
||||
$response = <$socket>;
|
||||
if( $response =~ m/\scount%3A1/ ) {
|
||||
print "done\nINFO: AirPlay 'wavin' favourite successfully created.\n";
|
||||
$okay = 1;
|
||||
} else {
|
||||
print "failed\nWARN: Could not create AirPlay favourite\n";
|
||||
print " Server response was $response\n";
|
||||
}
|
||||
if( $okay ) {
|
||||
&$findFavourites( $socket );
|
||||
|
||||
print "Updated Favourites URLs:\n" if $verbose;
|
||||
foreach my $url (@favourites) {
|
||||
print "\t$url\n" if( ( defined( $url ) && $url ) && $verbose );
|
||||
}
|
||||
if( !( grep { $_ =~ m/^wavin:/ } @favourites ) ) {
|
||||
print "WARN: Cloud not identify AirPlay Favourite, even after creating it - disabling SqueezeBox integration\n";
|
||||
$squeeze = 0;
|
||||
$okay = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( $okay ) {
|
||||
for ( my $n = 0 ; !( defined( $index ) ) && $n <= scalar( @favourites ) ; $n++ ) {
|
||||
if( $favourites[ $n ] =~ m/^wavin:/ ) {
|
||||
$index = $n;
|
||||
print "Found favourite '" . $favourites[ $index ] . "' with ID '" . $ids[ $index ] . "' at position $index.\n" if $verbose;
|
||||
}
|
||||
}
|
||||
#print "Turning on player... " if $verbose;
|
||||
# For some reason, this one dies...
|
||||
#eval {
|
||||
# local $SIG{ALRM} = sub { die "Socket Operation Timed Out" };
|
||||
# print $socket "$mac power 1\n";
|
||||
# alarm 1;
|
||||
# $response = <$socket>;
|
||||
# print "$response\n" if $verbose;
|
||||
# alarm 0;
|
||||
#};
|
||||
#... even within an eval{} loop?!
|
||||
|
||||
print "Showing message... " if $verbose;
|
||||
print $socket "$mac show line2%3AStarting%20AirPlay duration%3A5 brightness%3ApowerOn font%3Ahuge\n";
|
||||
$response = <$socket>;
|
||||
print "$response\n" if $verbose;
|
||||
|
||||
if( defined( $volume ) ) {
|
||||
print "Setting player volume... " if $verbose;
|
||||
print $socket "$mac mixer volume $volume\n";
|
||||
$response = <$socket>;
|
||||
print "$response\n" if $verbose;
|
||||
}
|
||||
|
||||
print "Unmuting player... " if $verbose;
|
||||
print $socket "$mac mixer muting 0\n";
|
||||
$response = <$socket>;
|
||||
print "$response\n" if $verbose;
|
||||
|
||||
if( defined( $index ) ) {
|
||||
print "Playing favourite... " if $verbose;
|
||||
my $id = uri_escape( $ids[ $index ] );
|
||||
print $socket "$mac favorites playlist play item_id%3A$id\n";
|
||||
$response = <$socket>;
|
||||
} else {
|
||||
print "Resuming play... " if $verbose;
|
||||
print $socket "$mac play\n";
|
||||
$response = <$socket>;
|
||||
}
|
||||
print "$response\n" if $verbose;
|
||||
}
|
||||
close( $socket );
|
||||
|
||||
exit(0);
|
||||
}
|
||||
};
|
||||
|
||||
while (1) {
|
||||
my @waiting = $sel->can_read;
|
||||
foreach my $fh (@waiting) {
|
||||
@@ -234,6 +486,10 @@ while (1) {
|
||||
$sel->add($new);
|
||||
$new->blocking(0);
|
||||
$conns{$new} = {fh => $fh};
|
||||
|
||||
if (defined($squeeze) && $squeeze) {
|
||||
&performSqueezeboxSetup();
|
||||
}
|
||||
} else {
|
||||
if (eof($fh)) {
|
||||
print "Closed: $fh\n" if $verbose;
|
||||
|
||||
Reference in New Issue
Block a user