diff --git a/Changelog7.html b/Changelog7.html index c5f26497962..486f159afef 100644 --- a/Changelog7.html +++ b/Changelog7.html @@ -351,7 +351,7 @@

Version 7.7.7

  • Server Changes:

  • diff --git a/Slim/Player/Squeezebox2.pm b/Slim/Player/Squeezebox2.pm index 186d9f407c3..c3e5641a14c 100644 --- a/Slim/Player/Squeezebox2.pm +++ b/Slim/Player/Squeezebox2.pm @@ -116,7 +116,7 @@ sub model { sub modelName { my $client = shift; - + if ($client->model(1) eq 'squeezebox3') { return 'Squeezebox Classic'; } @@ -127,20 +127,20 @@ sub modelName { # in order of preference based on whether we're connected via wired or wireless... sub formats { my $client = shift; - + return qw(wma ogg flc aif pcm mp3); } sub statHandler { my ($client, $code, $jiffies, $error_code) = @_; - + if ($code eq 'STMc') { $client->streamStartTimestamp($jiffies); } else { return if ! defined($client->streamStartTimestamp()); } - - + + if ($code eq 'STMd') { $client->readyToStream(1); $client->controller()->playerReadyToStream($client); @@ -169,20 +169,20 @@ sub statHandler { $client->controller()->playerEndOfStream($client); } else { $client->controller->playerStatusHeartbeat($client); - } - + } + } - + # The original Squeezebox2 firmware supported a fairly narrow volume range # below unity gain - 129 levels on a linear scale represented by a 1.7 # fixed point number (no sign, 1 integer, 7 fractional bits). # From FW 22 onwards, volume is sent as a 16.16 value (no sign, 16 integer, # 16 fractional bits), significantly increasing our fractional range. -# Rather than test for the firmware level, we send both values in the +# Rather than test for the firmware level, we send both values in the # volume message. -# We thought about sending a dB scale volume to the client, but decided -# against it. Sending a fixed point multiplier allows us to change +# We thought about sending a dB scale volume to the client, but decided +# against it. Sending a fixed point multiplier allows us to change # the mapping of UI volume settings to gain as we want, without being # constrained by any scale other than that of the fixed point range allowed # by the client. @@ -191,16 +191,16 @@ sub statHandler { # we only have 129 levels to work with now, and within 100 range, # that's pretty tight. # this table is optimized for 40 steps (like we have in the current player UI. -my @volume_map = ( -0, 1, 1, 1, 2, 2, 2, 3, 3, 4, -5, 5, 6, 6, 7, 8, 9, 9, 10, 11, -12, 13, 14, 15, 16, 16, 17, 18, 19, 20, -22, 23, 24, 25, 26, 27, 28, 29, 30, 32, -33, 34, 35, 37, 38, 39, 40, 42, 43, 44, -46, 47, 48, 50, 51, 53, 54, 56, 57, 59, -60, 61, 63, 65, 66, 68, 69, 71, 72, 74, -75, 77, 79, 80, 82, 84, 85, 87, 89, 90, -92, 94, 96, 97, 99, 101, 103, 104, 106, 108, 110, +my @volume_map = ( +0, 1, 1, 1, 2, 2, 2, 3, 3, 4, +5, 5, 6, 6, 7, 8, 9, 9, 10, 11, +12, 13, 14, 15, 16, 16, 17, 18, 19, 20, +22, 23, 24, 25, 26, 27, 28, 29, 30, 32, +33, 34, 35, 37, 38, 39, 40, 42, 43, 44, +46, 47, 48, 50, 51, 53, 54, 56, 57, 59, +60, 61, 63, 65, 66, 68, 69, 71, 72, 74, +75, 77, 79, 80, 82, 84, 85, 87, 89, 90, +92, 94, 96, 97, 99, 101, 103, 104, 106, 108, 110, 112, 113, 115, 117, 119, 121, 123, 125, 127, 128 ); @@ -211,7 +211,7 @@ sub dBToFixed { # Map a floating point dB value to a 16.16 fixed point value to # send as a new style volume to SB2 (FW 22+). my $floatmult = 10 ** ($db/20); - + # use 8 bits of accuracy for dB values greater than -30dB to avoid rounding errors if ($db >= -30 && $db <= 0) { return int($floatmult * (1 << 8) + 0.5) * (1 << 8); @@ -223,7 +223,7 @@ sub dBToFixed { sub getVolumeParameters { # A negative stepPoint ensures that the alternate (low level) ramp never kicks in. - my $params = + my $params = { totalVolumeRange => -50, # dB stepPoint => -1, # Number of steps, up from the bottom, where a 2nd volume ramp kicks in. @@ -240,15 +240,15 @@ sub getVolume my $stepdB = $volume_parameters->{totalVolumeRange} * $volume_parameters->{stepFraction}; my $maxVolumedB = (defined $volume_parameters->{maximumVolume}) ? $volume_parameters->{maximumVolume} : 0; - - # Equation for a line: + + # Equation for a line: # y = mx+b - # y1 = mx1+b, y2 = mx2+b. + # y1 = mx1+b, y2 = mx2+b. # y2-y1 = m(x2 - x1) # y2 = m(x2 - x1) + y1 my $slope_high = ($maxVolumedB-$stepdB)/(100-$stepPoint) ; my $slope_low = ($stepdB-$totalVolumeRange)/($stepPoint-0); - + my $x2 = $volume; my $m = undef; my $x1 = undef; @@ -265,7 +265,7 @@ sub getVolume my $y2 = $m * ($x2 - $x1) + $y1; # print "$m, ($x1, $y1), ($x2, $y2)\n"; return $y2; - + } sub volume { @@ -277,7 +277,7 @@ sub volume { if (defined($newvolume)) { # Old style volume: my $oldGain = $volume_map[int($volume)]; - + my $newGain; # Negative volume = muting if ($volume <= 0) { @@ -287,12 +287,12 @@ sub volume { my $db = $client->getVolume($volume, $client->getVolumeParameters()); $newGain = $client->dBToFixed($db); } - + my $dvc = $prefs->client($client)->get('digitalVolumeControl'); if ( !defined $dvc ) { $dvc = $Slim::Player::Player::defaultPrefs->{digitalVolumeControl}; } - + my $preamp = 255 - int( 2 * ( $prefs->client($client)->get('preampVolumeControl') || 0 ) ); my $data; @@ -335,7 +335,7 @@ sub upgradeFirmware { $client->showBriefly( { 'line' => [ $client->string( 'FIRMWARE_MISSING' ), $client->string( 'FIRMWARE_MISSING_DESC' ) ] }, - { + { 'block' => 1, 'scroll' => 1, 'firstline' => 1, @@ -348,17 +348,17 @@ sub upgradeFirmware { return(0); } - + if (-f $file2 && !-f $file) { $file = $file2; } - + $client->stop(); main::INFOLOG && $log->info("Using new update mechanism: $file"); - + $client->isUpgrading(1); - + # Notify about firmware upgrade starting Slim::Control::Request::notifyFromArray( $client, [ 'firmware_upgrade' ] ); @@ -421,13 +421,13 @@ sub stop { sub songElapsedSeconds { my $client = shift; - + return 0 if $client->isStopped() || defined $_[0]; my ($jiffies, $elapsedMilliseconds, $elapsedSeconds) = Slim::Networking::Slimproto::getPlayPointData($client); return 0 unless $elapsedMilliseconds || $elapsedSeconds; - + # Use milliseconds for the song-elapsed-time if has not suffered truncation my $songElapsed; if (defined $elapsedMilliseconds) { @@ -438,12 +438,12 @@ sub songElapsedSeconds { } else { $songElapsed = $elapsedSeconds; } - + if ($client->isPlaying(1)) { my $timeDiff = Time::HiRes::time() - $client->jiffiesToTimestamp($jiffies); $songElapsed += $timeDiff if ($timeDiff > 0); } - + return $songElapsed; } @@ -471,7 +471,7 @@ sub directHeaders { my $controller = $client->controller()->songStreamController(); my $handler = $controller ? $controller->protocolHandler() : undef; - + if ($handler && $handler->can('handlesStreamHeaders')) { if ($handler->handlesStreamHeaders($client, $headers)) { @@ -484,16 +484,16 @@ sub directHeaders { my $url = $controller->streamUrl(); my $songHandler = $controller->songProtocolHandler(); - + # We involve the protocol handler in the header parsing process. - # The current iteration of the firmware only knows about HTTP - # headers. Specifically, it returns headers after finding a + # The current iteration of the firmware only knows about HTTP + # headers. Specifically, it returns headers after finding a # CRLF pair in the stream. In the future, we could tell the firmware - # to return a specific number of bytes or look for a specific + # to return a specific number of bytes or look for a specific # byte sequence and make this less HTTP specific. For now, we only # support this type of direct streaming for HTTP-esque protocols. - # Trim embedded nulls + # Trim embedded nulls $headers =~ s/[\0]*$//; $headers =~ s/\r/\n/g; @@ -502,9 +502,9 @@ sub directHeaders { my @headers = split "\n", $headers; chomp(@headers); - + my $response = shift @headers; - + if (!$response || $response !~ m/ (\d\d\d)/) { $directlog->warn("Invalid response code ($response) from remote stream $url"); @@ -512,19 +512,19 @@ sub directHeaders { $client->failedDirectStream($response); } else { - + my $status_line = $response; $response = $1; - + if (($response < 200) || $response > 399) { $directlog->warn("Invalid response code ($response) from remote stream $url"); if ($songHandler && $songHandler->can("handleDirectError")) { - + # bug 10407 - make sure ready to stream again $client->readyToStream(1); - + $songHandler->handleDirectError($client, $url, $response, $status_line); } else { @@ -548,7 +548,7 @@ sub directHeaders { if ($songHandler && $songHandler->can("parseDirectHeaders")) { # Could use a hash ref for header parameters main::INFOLOG && $directlog->info("Calling $songHandler ::parseDirectHeaders"); - ($title, $bitrate, $metaint, $redir, $contentType, $length, $body) + ($title, $bitrate, $metaint, $redir, $contentType, $length, $body) = $songHandler->parseDirectHeaders($client, $controller->song()->currentTrack(), @headers); } elsif ($handler->can("parseDirectHeaders")) { # Could use a hash ref for header parameters @@ -559,34 +559,34 @@ sub directHeaders { $controller->song()->isLive($length ? 0 : 1) if !$redir; # XXX maybe should (also) check $song->scanData()->{$url}->{metadata}->{info}->{broadcast} # for WMA streams here. - + # update bitrate, content-type title for this URL... Slim::Music::Info::setContentType($url, $contentType) if $contentType; Slim::Music::Info::setBitrate($url, $bitrate) if $bitrate; - + # Always prefer the title returned in the headers of a radio station if ( $title ) { main::INFOLOG && $directlog->is_info && $directlog->info( "Setting new title for $url, $title" ); Slim::Music::Info::setCurrentTitle( $url, $title ); - + # Bug 7979, Only update the database title if this item doesn't already have a title my $curTitle = Slim::Music::Info::title($url); if ( !$curTitle || $curTitle =~ /^(?:http|mms)/ ) { Slim::Music::Info::setTitle( $url, $title ); } } - + # Bitrate may have been set in Scanner by reading the mp3 stream if ( !$bitrate ) { $bitrate = Slim::Music::Info::getBitrate( $url ) || 0; } - + # WMA handles duration based on metadata if ( $contentType ne 'wma' ) { - + # See if we have an existing track object with duration info for this stream. if ( my $secs = Slim::Music::Info::getDuration($url) ) { - + # Display progress bar $client->streamingProgressBar( { 'url' => $redirects->{ $url } || $url, @@ -594,7 +594,7 @@ sub directHeaders { } ); } else { - + if ( $bitrate && $length && $bitrate > 0 && $length > 0 && !$client->shouldLoop($length) ) { # if we know the bitrate and length of a stream, display a progress bar if ( $bitrate < 1000 ) { @@ -618,19 +618,14 @@ sub directHeaders { if ($redir) { main::INFOLOG && $directlog->info("Redirecting to: $redir" . (defined($controller->song->seekdata()) ? ' with seekdata' : '')); - + # Store the old URL so we can update its bitrate/content-type/etc - $redirects->{ $redir } = $url; - - # For any track-based services that use redirects (e.g. MOG Australia) this stop will - # cause each track to get cut off at the end. It doesn't appear to be necessary but - # just in case I'll leave it here commented out. -Andy - # $client->stop(); + $redirects->{ $redir } = $url; $controller->song->streamUrl($redir); $client->play({ - 'paused' => ($client->isSynced(1)), - 'format' => ($client->master())->streamformat(), + 'paused' => ($client->isSynced(1)), + 'format' => ($client->master())->streamformat(), 'url' => $redir, 'controller' => $controller, 'seekdata' => $controller->song->seekdata(), @@ -654,7 +649,7 @@ sub directHeaders { Slim::Music::Info::setContentType( $oldURL, $contentType ) if $contentType; Slim::Music::Info::setBitrate( $oldURL, $bitrate ) if $bitrate; - + # carry the original title forward to the new URL my $title = Slim::Music::Info::title( $oldURL ); Slim::Music::Info::setTitle( $url, $title ) if $title; @@ -663,12 +658,12 @@ sub directHeaders { main::INFOLOG && $directlog->is_info && $directlog->info("Beginning direct stream!"); my $loop = $client->shouldLoop($length); - + # Some looping sounds are too short and mess up the buffer secs # This will let quickstart avoid buffering these tracks too long if ( $loop ) { Slim::Music::Info::setDuration( $url, 0 ); - + main::INFOLOG && $directlog->info('Using infinite loop mode'); } @@ -681,7 +676,7 @@ sub directHeaders { $client->failedDirectStream(); } - + # Bug 6482, refresh the cached Track object in the client playlist from the database # so it picks up any changed data such as title, bitrate, etc Slim::Player::Playlist::refreshTrack( $client, $url ); @@ -691,21 +686,21 @@ sub directHeaders { sub sendContCommand { my ($client, $metaint, $loop, @guids) = @_; - + $metaint ||= 0; - + if ( main::DEBUGLOG ) { my $log = logger('player.streaming.direct'); $log->is_debug && $log->debug("Sending cont frame: metaint $metaint, loop $loop"); } - + $client->sendFrame('cont', \(pack('NCnC*', $metaint, $loop, scalar @guids, @guids))); } sub directBodyFrame { my $client = shift; my $body = shift; - + my $isInfo = $directlog->is_info; my $controller = $client->controller()->songStreamController(); @@ -717,7 +712,7 @@ sub directBodyFrame { my $done = 0; $isInfo && $directlog->info("Got some body from the player, length " . length($body)); - + if (length($body)) { $isInfo && $directlog->info("Saving away that body message until we get an empty body"); @@ -739,7 +734,7 @@ sub directBodyFrame { $isInfo && $directlog->info("directBodyFrame: Saving to temp file: " . $fh->filename); } - + $client->directBody->write( $body, length($body) ); } @@ -755,7 +750,7 @@ sub directBodyFrame { if ($done) { if ( defined $client->directBody ) { - + # seek back to the front of the file seek $client->directBody, 0, 0; @@ -771,8 +766,8 @@ sub directBodyFrame { else { @items = Slim::Formats::Playlists->parseList( $url, $client->directBody ); } - - if ( scalar @items ) { + + if ( scalar @items ) { Slim::Player::Source::explodeSong($client, \@items); Slim::Player::Source::playmode($client, 'play'); @@ -807,7 +802,7 @@ sub directMetadata { if ( $handler->can('parseMetadata') ) { $handler->parseMetadata( $client, $controller->song(), $metadata ); } - + # new song, so reset counters $client->songBytes(0); } @@ -815,7 +810,7 @@ sub directMetadata { sub failedDirectStream { my $client = shift; my $error = shift; - + # bug 10407 - make sure ready to stream again $client->readyToStream(1); @@ -827,7 +822,7 @@ sub failedDirectStream { $directlog->warn("Oh, well failed to do a direct stream for: $url: ", ($error || '')); $client->directBody(undef); - + $client->controller()->playerStreamingFailed($client, $error || 'PROBLEM_CONNECTING'); } @@ -836,13 +831,13 @@ sub failedDirectStream { sub shouldLoop { my $client = shift; my $audio_size = shift; - + # Ask the client if the track is small enough for this return 0 unless ( $audio_size && $client->canLoop($audio_size) ); - + # Check with the protocol handler my $url = Slim::Player::Playlist::url($client); - + if ( Slim::Music::Info::isRemoteURL($url) ) { my $handler = Slim::Player::ProtocolHandlers->handlerForURL($url); if ( $handler && $handler->can('shouldLoop') ) { @@ -876,7 +871,7 @@ sub canDoReplayGain { } -sub audio_outputs_enable { +sub audio_outputs_enable { my $client = shift; my $enabled = shift; @@ -944,13 +939,13 @@ sub setPlayerSetting { my $value = shift; return unless defined $value; - + my $isInfo = $prefslog->is_info; $isInfo && $prefslog->info("Setting pref: [$pref] to [$value]"); my $currpref = $pref_settings->{$pref}; - + my $status = $client->pendingPrefChanges()->{$pref} || 0; # Only send a setd packet to the player if it is stopped and we are not @@ -959,7 +954,7 @@ sub setPlayerSetting { my $data = pack('C'.$currpref->{pack}, $currpref->{firmwareid}, $value); $client->sendFrame('setd', \$data); - + # We are now waiting for a response to this setd call $client->pendingPrefChanges()->{$pref} = SETD_WAITING; } @@ -977,7 +972,7 @@ sub setPlayerSetting { sub playerSettingsFrame { my $client = shift; my $data_ref = shift; - + my $isInfo = main::INFOLOG && $prefslog->is_info; my $id = unpack('C', $$data_ref); @@ -987,7 +982,7 @@ sub playerSettingsFrame { if ($currpref->{'firmwareid'} != $id) { next; } - + # We've received a response, so remove waiting status from this pref $client->pendingPrefChanges()->{$pref} ||= 0; $client->pendingPrefChanges()->{$pref} &= ~SETD_WAITING; @@ -1009,7 +1004,7 @@ sub playerSettingsFrame { } } else { - utf8::decode($value) if $pref eq 'playername'; + utf8::decode($value) if $pref eq 'playername'; $prefs->client($client)->set( $pref, $value ); } } @@ -1073,7 +1068,7 @@ sub playPoint { sub startAt { my ($client, $at) = @_; - + main::DEBUGLOG && $synclog->is_debug && $synclog->debug( $client->id, ' startAt: ' . int(($at - $client->jiffiesEpoch()) * 1000) ); $client->stream( 'u', { 'interval' => int(($at - $client->jiffiesEpoch()) * 1000) } ); @@ -1082,7 +1077,7 @@ sub startAt { sub resume { my ($client, $at) = @_; - + $client->stream('u', ($at ? { 'interval' => int(($at - $client->jiffiesEpoch()) * 1000) } : undef)); $client->SUPER::resume(); return 1; diff --git a/Slim/Plugin/MOG/HTML/EN/plugins/MOG/html/images/icon.png b/Slim/Plugin/MOG/HTML/EN/plugins/MOG/html/images/icon.png deleted file mode 100644 index 6fc7c22f79d..00000000000 Binary files a/Slim/Plugin/MOG/HTML/EN/plugins/MOG/html/images/icon.png and /dev/null differ diff --git a/Slim/Plugin/MOG/Plugin.pm b/Slim/Plugin/MOG/Plugin.pm deleted file mode 100644 index e5c75367dc4..00000000000 --- a/Slim/Plugin/MOG/Plugin.pm +++ /dev/null @@ -1,128 +0,0 @@ -package Slim::Plugin::MOG::Plugin; - -# Logitech Media Server Copyright 2003-2020 Logitech. -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License, -# version 2. - -use strict; -use base qw(Slim::Plugin::OPMLBased); - -use URI::Split qw(uri_split); -use URI::Escape qw(uri_escape_utf8); -use Slim::Plugin::MOG::ProtocolHandler; -use Slim::Networking::SqueezeNetwork; -use Slim::Utils::Strings qw(cstring); - -my $log = Slim::Utils::Log->addLogCategory( { - category => 'plugin.mog', - defaultLevel => 'ERROR', - description => 'PLUGIN_MOG_MODULE_NAME', -} ); - -sub initPlugin { - my $class = shift; - - Slim::Player::ProtocolHandlers->registerHandler( - mog => 'Slim::Plugin::MOG::ProtocolHandler' - ); - - Slim::Player::ProtocolHandlers->registerIconHandler( - qr|mysqueezebox\.com.*/api/mog/|, - sub { $class->_pluginDataFor('icon') } - ); - - # add custom commands to control radio's diversity - Slim::Control::Request::addDispatch(['mog', 'radiodiversity', '_diversity'], - [0, 1, 1, \&Slim::Plugin::MOG::ProtocolHandler::setRadioDiversity]); - - # Track Info item - Slim::Menu::TrackInfo->registerInfoProvider( mog => ( - after => 'middle', - func => \&trackInfoMenu, - ) ); - - if ( main::WEBUI ) { - # Add a function to view trackinfo in the web - Slim::Web::Pages->addPageFunction( - 'plugins/mog/trackinfo.html', - sub { - my $client = $_[0]; - my $params = $_[1]; - - my $url; - - my $id = $params->{sess} || $params->{item}; - - if ( $id ) { - # The user clicked on a different URL than is currently playing - if ( my $track = Slim::Schema->find( Track => $id ) ) { - $url = $track->url; - } - - # Pass-through track ID as sess param - $params->{sess} = $id; - } - else { - $url = Slim::Player::Playlist::url($client); - } - - Slim::Web::XMLBrowser->handleWebIndex( { - client => $client, - feed => Slim::Plugin::MOG::ProtocolHandler->trackInfoURL( $client, $url ), - path => 'plugins/mog/trackinfo.html', - title => 'MOG Track Info', - timeout => 35, - args => \@_ - } ); - }, - ); - } - - $class->SUPER::initPlugin( - feed => Slim::Networking::SqueezeNetwork->url('/api/mog/v1/opml'), - tag => 'mog', - menu => 'music_services', - weight => 40, - is_app => 1, - ); - -} - -sub trackInfoMenu { - my ( $client, $url, $track, $remoteMeta ) = @_; - - return unless $client; - - # Only show if in the app list - return unless $client->isAppEnabled('mog'); - - my $artist = $track->remote ? $remoteMeta->{artist} : $track->artistName; - my $album = $track->remote ? $remoteMeta->{album} : ( $track->album ? $track->album->name : undef ); - my $title = $track->remote ? $remoteMeta->{title} : $track->title; - - if ( $artist || $album || $title ) { - - my $snURL = Slim::Networking::SqueezeNetwork->url( - '/api/mog/v1/opml/context?artist=' . uri_escape_utf8($artist) - . '&album=' . uri_escape_utf8($album) - . '&track=' . uri_escape_utf8($title) - ); - - return { - type => 'link', - name => $client->string('PLUGIN_ON_MOG'), - url => $snURL, - favorites => 0, - }; - } -} - -sub getDisplayName () { - return 'PLUGIN_MOG_MODULE_NAME'; -} - -# Don't add this item to any menu -sub playerMenu { } - -1; diff --git a/Slim/Plugin/MOG/ProtocolHandler.pm b/Slim/Plugin/MOG/ProtocolHandler.pm deleted file mode 100644 index bbc4fa30ab1..00000000000 --- a/Slim/Plugin/MOG/ProtocolHandler.pm +++ /dev/null @@ -1,669 +0,0 @@ -package Slim::Plugin::MOG::ProtocolHandler; - -# Logitech Media Server Copyright 2003-2020 Logitech. -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License, -# version 2. - -use strict; -use base qw(Slim::Player::Protocols::HTTP); - -use JSON::XS::VersionOneAndTwo; -use Scalar::Util qw(blessed); -use URI::Escape qw(uri_escape_utf8); - -use Slim::Networking::SqueezeNetwork; -use Slim::Music::Info; -use Slim::Utils::Log; -use Slim::Utils::Misc; -use Slim::Utils::Timers; -use Slim::Utils::Prefs; - -use constant MIN_RADIO_QUEUE => 10; - -my $log = Slim::Utils::Log->addLogCategory( { - category => 'plugin.mog', - defaultLevel => 'ERROR', - description => 'PLUGIN_MOG_MODULE_NAME', -} ); - -my $prefs = preferences('server'); -my $log = logger('plugin.mog'); - -my $value = 0; - -sub getFormatForURL { 'mp3' } - -sub isRepeatingStream { 0 } - -sub isRemote { 1 } - -sub canSeek { return 1; } - -sub canSeekError { return ( 'SEEK_ERROR_TYPE_NOT_SUPPORTED', 'MOG' ); } - -# XXX: Port to new streaming - -# To support remote streaming (synced players), we need to subclass Protocols::HTTP -sub new { - my $class = shift; - my $args = shift; - - my $client = $args->{client}; - - my $song = $args->{song}; - my $streamUrl = $song->streamUrl() || return; - my $track = $song->pluginData('info') || {}; - - main::DEBUGLOG && $log->debug( 'Remote streaming MOG track: ' . $streamUrl ); - - my $sock = $class->SUPER::new( { - url => $streamUrl, - song => $args->{song}, - client => $client, - bitrate => ($track->{bitrate} || 320) * 1000, - } ) || return; - - ${*$sock}{contentType} = 'audio/mpeg'; - - return $sock; -} - -# Avoid scanning -sub scanUrl { - my ( $class, $url, $args ) = @_; - - $args->{cb}->( $args->{song}->currentTrack() ); -} - -sub audioScrobblerSource { - my ( $class, $client, $url ) = @_; - - # P = Chosen by the user - return 'P'; -} - -# parseHeaders is used for proxied streaming -sub parseHeaders { - my ( $self, @headers ) = @_; - - __PACKAGE__->parseDirectHeaders( $self->client, $self->url, @headers ); - - return $self->SUPER::parseHeaders( @headers ); -} - -sub parseDirectHeaders { - my ( $class, $client, $url, @headers ) = @_; - - my $song = $client->streamingSong(); - my $track = $song->pluginData('info'); - - my $duration = $track->{duration}; - - my $length; - my $rangelength; - my $redir; - - foreach my $header (@headers) { - if ( $header =~ /^Content-Length:\s*(.*)/i ) { - $length = $1; - } - elsif ($header =~ /^Location:\s*(.*)/i) { - $redir = $1; - } - elsif ( $header =~ m{^Content-Range: .+/(.*)}i ) { - $rangelength = $1; - last; - } - } - - if ( $rangelength ) { - $length = $rangelength; - } - - my $bitrate = $track->{bitrate} * 1000; - - $song->bitrate($bitrate); - $song->duration($duration); - - # ($title, $bitrate, $metaint, $redir, $contentType, $length, $body) - return (undef, $bitrate, 0, $redir, 'mp3', $length, undef); -} - -# Don't allow looping -sub shouldLoop { 0 } - -sub getNextTrack { - my ( $class, $song, $successCb, $errorCb ) = @_; - - my $client = $song->master(); - my $url = $song->track()->url; - - $song->pluginData( abandonSong => 0 ); - - _getTrack($client, $song, { - song => $song, - url => $url, - successCb => $successCb, - errorCb => $errorCb, - }); -} - -sub _getRadioTracks { - my ( $client, $id ) = @_; - - main::DEBUGLOG && $log->is_debug && $log->debug("Getting tracks for station $id from SN"); - - my $http = Slim::Networking::SqueezeNetwork->new( - \&_gotRadioTracks, - sub { - my $http = shift; - - if ( main::DEBUGLOG && $log->is_debug ) { - $log->debug( 'getRadioTracks failed: ' . $http->error ); - } - }, - { - client => $client, - radioId => $id, - }, - ); - - $http->get( - Slim::Networking::SqueezeNetwork->url( - '/api/mog/v1/playback/getRadioTracks?radioid=' . $id - ) - ); -} - -sub _gotRadioTracks { - my $http = shift; - - my $client = $http->params->{client}; - - my $info = eval { from_json( $http->content ) }; - - if ( $@ ) { - $log->error(Data::Dump::dump($info, $@)); - } - else { - my $icon = __PACKAGE__->getIcon(); - my $cache = Slim::Utils::Cache->new; - - if ( main::DEBUGLOG && $log->is_debug ) { - $log->debug( 'getRadioTracks ok: ' . Data::Dump::dump($info) ); - } - - my $song = $client->playingSong(); - my $currentId = 0; - - if ($song && $song->track) { - ($currentId) = getIds($song->track->url || ''); - } - - my @tracks; - foreach my $track ( @$info ) { - next unless ref $track eq 'HASH'; - - # cache the metadata we need for display - my $trackId = delete $track->{id}; - - # don't queue track which we're already playing - next if $trackId == $currentId; - - my $meta = { - artist => $track->{artist}, - album => $track->{album}, - title => $track->{title}, - cover => $track->{cover} || $icon, - duration => $track->{duration}, - genre => $track->{genre}, - year => $track->{year}, - bitrate => '320k CBR', # XXX bulk API call does not know the actual bitrate, it will be replaced in _gotTrack - type => 'MP3 (MOG)', - info_link => 'plugins/mog/trackinfo.html', - icon => $icon, - }; - - $cache->set( 'mog_meta_' . $trackId, $meta, 86400 ); - - my $url = 'mog://' . $http->params->{radioId} . '-' . $trackId . '.mp3'; - - push @tracks, Slim::Schema->updateOrCreate( { - 'url' => $url, - 'attributes' => { - title => $meta->{title}, - album => $meta->{album}, - cover => $meta->{cover}, - duration => $meta->{duration}, - year => $meta->{year}, - CT => 'mp3', - }, - } ); - } - - $client->execute([ 'playlist', 'addtracks', 'listRef', \@tracks ]); - $client->execute([ 'play' ]); - - # Update the playlist time so the web will refresh, etc - $client->currentPlaylistUpdateTime( Time::HiRes::time() ); - - Slim::Control::Request::notifyFromArray( $client, [ 'newmetadata' ] ); - } -} - -sub _getTrack { - my ( $client, $song, $params ) = @_; - - return if $song->pluginData('abandonSong'); - - # Get track URL for the next track - my ($trackId, $radioId) = getIds($params->{url}); - - my $http = Slim::Networking::SqueezeNetwork->new( - sub { - my $http = shift; - my $info = eval { from_json( $http->content ) }; - if ( $@ || $info->{error} || !$info->{url} ) { - if ( main::DEBUGLOG && $log->is_debug ) { - $log->debug( 'getTrackInfo failed: ' . ( $@ || $info->{error} ) ); - $log->debug( ' data received: ' . Data::Dump::dump($info) ); - } - - _gotTrackError( $@ || $info->{error}, $client, $params ); - } - else { - _gotTrack( $client, $info, $params ); - - if ( main::DEBUGLOG && $log->is_debug ) { - delete $info->{url}; - $log->debug( 'getTrackInfo ok: ' . Data::Dump::dump($info) ); - } - } - }, - sub { - my $http = shift; - - if ( main::DEBUGLOG && $log->is_debug ) { - $log->debug( 'getTrackInfo failed: ' . $http->error ); - } - - _gotTrackError( $http->error, $client, $params ); - }, - { - client => $client, - }, - ); - - main::DEBUGLOG && $log->is_debug && $log->debug('Getting next track playback info from SN'); - - $http->get( - # XXX - this call is to be renamed to getMediaURL, it's only kept here until we update mysb.com for backwards compatibility - Slim::Networking::SqueezeNetwork->url( - '/api/mog/v1/playback/playStream?trackid=' . ($trackId || '') . '&radioid=' . ($radioId || '') - ) - ); -} - -sub _gotTrack { - my ( $client, $info, $params ) = @_; - - my $song = $params->{song}; - - return if $song->pluginData('abandonSong'); - - # Save the media URL for use in strm - $song->streamUrl($info->{url}); - - # Cache the rest of the track's metadata - my $meta = { - artist => $info->{artist}, - album => $info->{album}, - title => $info->{title}, - cover => $info->{cover}, - duration => $info->{duration}, - genre => $info->{genre}, - year => $info->{year}, - bitrate => ($info->{bitrate} || 320). 'k CBR', - type => 'MP3 (MOG)', - info_link => 'plugins/mog/trackinfo.html', - }; - - $song->pluginData( info => $info ); - $song->duration( $info->{duration} ); - - my $cache = Slim::Utils::Cache->new; - - $cache->set( 'mog_meta_' . $info->{id}, $meta, 86400 ); - - # Update the playlist time so the web will refresh, etc - $client->currentPlaylistUpdateTime( Time::HiRes::time() ); - - Slim::Control::Request::notifyFromArray( $client, [ 'newmetadata' ] ); - - # Async resolve the hostname so gethostbyname in Player::Squeezebox::stream doesn't block - # When done, callback will continue on to playback - my $dns = Slim::Networking::Async->new; - $dns->open( { - Host => URI->new( $info->{url} )->host, - Timeout => 3, # Default timeout of 10 is too long, - # by the time it fails player will underrun and stop - onDNS => $params->{successCb}, - onError => $params->{successCb}, # even if it errors, keep going - passthrough => [], - } ); - - # Watch for playlist commands - Slim::Control::Request::subscribe( - \&_playlistCallback, - [['playlist'], ['newsong']], - $song->master(), - ); - -} - -# Metadata for a URL, used by CLI/JSON clients -sub getMetadataFor { - my ( $class, $client, $url ) = @_; - - my $icon = $class->getIcon(); - - return {} unless $url; - - my $cache = Slim::Utils::Cache->new; - - # If metadata is not here, fetch it so the next poll will include the data - my ($trackId) = getIds($url); - my $meta = $cache->get( 'mog_meta_' . $trackId ); - - if ( !$meta && !$client->master->pluginData('fetchingMeta') ) { - # Go fetch metadata for all tracks on the playlist without metadata - my @need; - - for my $track ( @{ Slim::Player::Playlist::playList($client) } ) { - my ($trackURL) = blessed($track) ? $track->url : $track; - if ( my ($trackId) = getIds($trackURL) ) { - if ( !$cache->get("mog_meta_$trackId") ) { - push @need, $trackId; - } - } - } - - if ( main::DEBUGLOG && $log->is_debug ) { - $log->debug( "Need to fetch metadata for: " . join( ', ', @need ) ); - } - - if (scalar @need) { - $client->master->pluginData( fetchingMeta => 1 ); - - my $metaUrl = Slim::Networking::SqueezeNetwork->url( - "/api/mog/v1/playback/getBulkMetadata" - ); - - my $http = Slim::Networking::SqueezeNetwork->new( - \&_gotBulkMetadata, - \&_gotBulkMetadataError, - { - client => $client, - timeout => 60, - }, - ); - - $http->post( - $metaUrl, - 'Content-Type' => 'application/x-www-form-urlencoded', - 'trackids=' . join( ',', @need ), - ); - } - } - - #$log->debug( "Returning metadata for: $url" . ($meta ? '' : ': default') ); - - return $meta || { - bitrate => '320k CBR', - type => 'MP3 (MOG)', - icon => $icon, - cover => $icon, - }; -} - -sub getIds { - my $url = shift; - - my ($radioId, $trackId); - - # radio track - if ( $url =~ m{mog://([at].+v\d+)-(.+)\.mp3}) { - $radioId = $1; - $trackId = $2; - } - else { - ($trackId) = $url =~ m{mog://(.+)\.mp3}; - } - - return ($trackId, $radioId); -} - -sub _gotBulkMetadata { - my $http = shift; - - my $client = $http->params->{client}; - - $client->master->pluginData( fetchingMeta => 0 ); - - my $info = eval { from_json( $http->content ) }; - - if ( $@ || ref $info ne 'ARRAY' || !scalar @$info ) { - $log->error( "Error fetching track metadata: " . ( $@ || 'Invalid JSON response' ) ); - return; - } - - if ( main::DEBUGLOG && $log->is_debug ) { - $log->debug( "Caching metadata for " . scalar( @{$info} ) . " tracks: " . Data::Dump::dump($info) ); - } - - # Cache metadata - my $cache = Slim::Utils::Cache->new; - my $icon = Slim::Plugin::MOG::Plugin->_pluginDataFor('icon'); - - for my $track ( @{$info} ) { - next unless ref $track eq 'HASH'; - - # cache the metadata we need for display - my $trackId = delete $track->{id}; - - my $meta = { - artist => $track->{artist}, - album => $track->{album}, - title => $track->{title}, - cover => $track->{cover} || $icon, - duration => $track->{duration}, - genre => $track->{genre}, - year => $track->{year}, - bitrate => '320k CBR', # XXX bulk API call does not know the actual bitrate, it will be replaced in _gotTrack - type => 'MP3 (MOG)', - info_link => 'plugins/mog/trackinfo.html', - icon => $icon, - }; - - $cache->set( 'mog_meta_' . $trackId, $meta, 86400 ); - } - - # Update the playlist time so the web will refresh, etc - $client->currentPlaylistUpdateTime( Time::HiRes::time() ); - - Slim::Control::Request::notifyFromArray( $client, [ 'newmetadata' ] ); -} - -sub _gotBulkMetadataError { - my $http = shift; - - my $client = $http->params('client'); - my $error = $http->error; - - $client->master->pluginData( fetchingMeta => 0 ); - - $log->warn("Error getting track metadata from SN: $error"); -} - -sub _gotTrackError { - my ( $error, $client, $params ) = @_; - - main::DEBUGLOG && $log->debug("Error during getTrackInfo: $error"); - - return if $params->{song}->pluginData('abandonSong'); - - _handleClientError( $error, $client, $params ); -} - -sub _handleClientError { - my ( $error, $client, $params ) = @_; - - my $song = $params->{song}; - - return if $song->pluginData('abandonSong'); - - # Tell other clients to give up - $song->pluginData( abandonSong => 1 ); - - $params->{errorCb}->($error); -} - -sub canDirectStreamSong { - my ( $class, $client, $song ) = @_; - - # We need to check with the base class (HTTP) to see if we - # are synced or if the user has set mp3StreamingMethod - return $class->SUPER::canDirectStream( $client, $song->streamUrl(), $class->getFormatForURL() ); -} - -sub _playlistCallback { - my $request = shift; - - my $client = $request->client(); - my $event = $request->getRequest(1); - - return unless defined $client; - - # check that user is still using MOG Radio - my $song = $client->playingSong(); - - if ( !$song || $song->currentTrackHandler ne __PACKAGE__ ) { - # User stopped playing MOG - main::DEBUGLOG && $log->debug( "Stopped MOG, unsubscribing from playlistCallback" ); - Slim::Control::Request::unsubscribe( \&_playlistCallback, $client ); - - return; - } - - my $url = $song->track->url; - - my ($trackId, $radioId) = getIds($url); - - if ( $radioId ) { - # disable repeat in radio mode - Slim::Player::Playlist::repeat($client, 0); - - Slim::Utils::Timers::killTimers($client, \&_cleanupRadioTracks); - Slim::Utils::Timers::setTimer($client, time() + 5, \&_cleanupRadioTracks, $radioId); - } -} - -sub _cleanupRadioTracks { - my ($client, $radioId) = @_; - - my $pos = Slim::Player::Source::playingSongIndex($client); - - # remove played/skipped tracks from queue - if ($pos > 0) { - my @tracks = Slim::Player::Playlist::songs($client, 0, $pos-1); - $client->execute([ 'playlist', 'deletetracks', 'listRef', \@tracks ]) if scalar @tracks; - } - - my $length = Slim::Player::Playlist::count($client); - - if ($length < MIN_RADIO_QUEUE) { - main::DEBUGLOG && $log->debug( "Need to queue up new MOG Radio tracks... $radioId" ); - _getRadioTracks($client, $radioId); - } -} - -# set diversity of the currently playing radio station -sub setRadioDiversity { - my $request = shift; - my $client = $request->client(); - - $log->error($client); - return unless defined $client; - - # ignore if user is not using Pandora - my $song = $client->playingSong() || return; - my $url = $song->currentTrack()->url; - - my ($trackId, $radioId) = getIds($url); - my $diversity = $request->getParam('_diversity') || 0; - - $radioId =~ s/([at].+v)(\d+)/$1$diversity/; - - # only continue if we're actually playing a radio station - return unless $trackId && $radioId; - - # remove queued tracks - my $pos = Slim::Player::Source::playingSongIndex($client); - my $length = Slim::Player::Playlist::count($client); - - $pos++; - while ($length-- > $pos) { - $client->execute([ 'playlist', 'delete', $pos ]); - } - - _getRadioTracks($client, $radioId); - - $request->setStatusDone(); -} - -sub trackInfoURL { - my ( $class, $client, $url ) = @_; - - my ($trackId, $radioId) = getIds($url); - - # SN URL to fetch track info menu - my $trackInfoURL = Slim::Networking::SqueezeNetwork->url( - '/api/mog/v1/opml/trackinfo?trackid=' . ($trackId || '') . '&radioid=' . ($radioId || '') - ); - - return $trackInfoURL; -} - -# Track Info menu -=pod XXX - legacy track info menu from before Slim::Menu::TrackInfo times? -sub trackInfo { - my ( $class, $client, $track ) = @_; - - my $url = $track->url; - my $trackInfoURL = $class->trackInfoURL( $client, $url ); - - # let XMLBrowser handle all our display - my %params = ( - header => 'PLUGIN_MOG_GETTING_STREAM_INFO', - modeName => 'MOG Now Playing', - title => Slim::Music::Info::getCurrentTitle( $client, $url ), - url => $trackInfoURL, - ); - - main::DEBUGLOG && $log->debug( "Getting track information for $url" ); - - Slim::Buttons::Common::pushMode( $client, 'xmlbrowser', \%params ); - - $client->modeParam( 'handledTransition', 1 ); -} -=cut - -sub getIcon { - my ( $class, $url ) = @_; - - return Slim::Plugin::MOG::Plugin->_pluginDataFor('icon'); -} - -1; - diff --git a/Slim/Plugin/MOG/install.xml b/Slim/Plugin/MOG/install.xml deleted file mode 100644 index 569c36585d3..00000000000 --- a/Slim/Plugin/MOG/install.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - 4f729f30-5c70-11e1-b86c-0800200c9a66 - PLUGIN_MOG_MODULE_NAME - Slim::Plugin::MOG::Plugin - 1.0 - PLUGIN_MOG_MODULE_NAME - Logitech - enabled - true - http://www.mysqueezebox.com/appgallery/mog - plugins/MOG/html/images/icon.png - 2 - - Logitech Media Server - 7.5 - * - - diff --git a/Slim/Plugin/MOG/strings.txt b/Slim/Plugin/MOG/strings.txt deleted file mode 100644 index e23af608b1a..00000000000 --- a/Slim/Plugin/MOG/strings.txt +++ /dev/null @@ -1,44 +0,0 @@ -PLUGIN_MOG_ADD_ALL_PLAYLIST - EN Add All to MOG Playlist - NL Voeg Alles toe aan MOG-playlist - NO Legg til alt i MOG-spilleliste - -PLUGIN_ADD_TRACK_PLAYLIST - EN Add track to MOG Playlist - NL Voeg nummer toe aan MOG-playlist - NO Legg til spor i MOG-spilleliste - -PLUGIN_MOG_PLAYLIST - EN MOG Playlist Options - NL Opties voor MOG-playlist - NO Alternativer for MOG-spilleliste - -PLUGIN_MOG_BOOKMARKS - EN MOG Favorites Options - NL Opties voor MOG-favorieten - NO Alternativer for MOG-favoritter - -PLUGIN_MOG - EN MOG - NL MOG - NO MOG - -PLUGIN_MOG_MODULE_NAME - EN MOG - NL MOG - NO MOG - -PLUGIN_ON_MOG - CS Na MOG - DA På MOG - DE Auf MOG - EN On MOG - ES En MOG - FI MOGssa - FR Sur MOG - IT Su MOG - NL Op MOG - NO På MOG - PL W usłudze MOG - RU На MOG - SV På MOG