Skip to content

Commit 9987a40

Browse files
authored
Merge pull request #39 from paul-1/copilot/fix-38
Replace channel name guessing with xmplaylist.com API integration and fix concurrent startup calls
2 parents 43483fa + deebbc1 commit 9987a40

File tree

3 files changed

+211
-18
lines changed

3 files changed

+211
-18
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,6 @@ cover_db/
4141

4242
# Development dependencies
4343
lms-server/
44+
45+
# Test output files
46+
output.txt

Plugins/SiriusXM/API.pm

Lines changed: 86 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ my $cache = Slim::Utils::Cache->new();
1818
# Cache timeout in seconds
1919
use constant CACHE_TIMEOUT => 86400; # 24 hours (1 day)
2020

21+
# Track in-flight channel requests to prevent concurrent calls
22+
my %channel_fetch_callbacks = ();
23+
2124
sub init {
2225
my $class = shift;
2326
$log->debug("Initializing SiriusXM API");
@@ -29,14 +32,18 @@ sub cleanup {
2932
# Clear any cached data
3033
$cache->remove('siriusxm_channels');
3134
$cache->remove('siriusxm_channel_info');
35+
$cache->remove('siriusxm_processed_channels');
3236
$cache->remove('siriusxm_auth_token');
37+
# Clear any pending callbacks
38+
%channel_fetch_callbacks = ();
3339
}
3440

3541
sub invalidateChannelCache {
3642
my $class = shift;
3743
$log->info("Invalidating channel cache due to playback failure");
3844
$cache->remove('siriusxm_channels');
3945
$cache->remove('siriusxm_channel_info');
46+
$cache->remove('siriusxm_processed_channels');
4047
}
4148

4249
=begin comment
@@ -208,6 +215,27 @@ sub getChannels {
208215
return;
209216
}
210217

218+
# Check if we have completely processed channel data in cache
219+
# This includes both proxy data and station mappings
220+
my $cached_processed = $cache->get('siriusxm_processed_channels');
221+
if ($cached_processed) {
222+
$log->debug("Using cached processed channel data");
223+
my $menu_items = $class->buildCategoryMenu($cached_processed);
224+
$cache->set('siriusxm_channels', $menu_items, CACHE_TIMEOUT);
225+
$cb->($menu_items);
226+
return;
227+
}
228+
229+
# Check if we're already fetching channels
230+
if (exists $channel_fetch_callbacks{'in_progress'}) {
231+
$log->debug("Channel fetch already in progress, queuing callback");
232+
push @{$channel_fetch_callbacks{'in_progress'}}, $cb if $cb;
233+
return;
234+
}
235+
236+
# Initialize the callback queue
237+
$channel_fetch_callbacks{'in_progress'} = $cb ? [$cb] : [];
238+
211239
my $port = $prefs->get('port') || '9999';
212240
my $url = "http://localhost:$port/channel/all";
213241

@@ -228,31 +256,52 @@ sub getChannels {
228256

229257
if ($@) {
230258
$log->error("Failed to parse channel data from proxy: $@");
231-
$cb->([]);
259+
# Call all queued callbacks with empty result
260+
for my $callback (@{$channel_fetch_callbacks{'in_progress'} || []}) {
261+
$callback->([]) if $callback;
262+
}
263+
delete $channel_fetch_callbacks{'in_progress'};
232264
return;
233265
}
234266

235267
# Process and organize channels by category
236-
my $categories = $class->processChannelData($channels_data);
237-
238-
# Build hierarchical menu structure
239-
my $menu_items = $class->buildCategoryMenu($categories);
240-
241-
# Cache both the menu structure and the processed channel data separately
242-
$cache->set('siriusxm_channels', $menu_items, CACHE_TIMEOUT);
243-
$cache->set('siriusxm_channel_info', $categories, CACHE_TIMEOUT);
244-
245-
$log->info("Retrieved and processed channels into menu structure");
246-
$cb->($menu_items);
268+
# First fetch station listings from xmplaylist.com for accurate mappings
269+
Plugins::SiriusXM::APImetadata->fetchStationListings(sub {
270+
my $station_lookup = shift || {};
271+
my $categories = $class->processChannelData($channels_data, $station_lookup);
272+
273+
# Cache the completely processed channel data with station mappings
274+
$cache->set('siriusxm_processed_channels', $categories, CACHE_TIMEOUT);
275+
276+
# Build hierarchical menu structure
277+
my $menu_items = $class->buildCategoryMenu($categories);
278+
279+
# Cache the menu structure
280+
$cache->set('siriusxm_channels', $menu_items, CACHE_TIMEOUT);
281+
$cache->set('siriusxm_channel_info', $categories, CACHE_TIMEOUT);
282+
283+
$log->info("Retrieved and processed channels into menu structure");
284+
285+
# Call all queued callbacks with the result
286+
for my $callback (@{$channel_fetch_callbacks{'in_progress'} || []}) {
287+
$callback->($menu_items) if $callback;
288+
}
289+
delete $channel_fetch_callbacks{'in_progress'};
290+
});
247291
},
248292
sub {
249293
my ($http, $error) = @_;
250294
$log->error("Failed to fetch channels from proxy: $error");
251295

252296
# Invalidate cache since proxy communication failed
253297
$cache->remove('siriusxm_channels');
298+
$cache->remove('siriusxm_processed_channels');
254299

255-
$cb->([]);
300+
# Call all queued callbacks with empty result
301+
for my $callback (@{$channel_fetch_callbacks{'in_progress'} || []}) {
302+
$callback->([]) if $callback;
303+
}
304+
delete $channel_fetch_callbacks{'in_progress'};
256305
},
257306
{
258307
timeout => 30,
@@ -413,18 +462,28 @@ sub normalizeChannelName {
413462

414463

415464
sub processChannelData {
416-
my ($class, $raw_channels) = @_;
465+
my ($class, $raw_channels, $station_lookup) = @_;
417466

418467
return [] unless $raw_channels && ref($raw_channels) eq 'ARRAY';
419468

469+
$station_lookup ||= {}; # Default to empty hash if not provided
420470
my %categories = ();
421471

422472
# Process channels and organize by primary category
423473
for my $channel (@$raw_channels) {
424474
next unless $channel->{channelId} && $channel->{name};
425475

426476
my $channel_name = $channel->{name};
427-
my $xmp_name = $class->normalizeChannelName($channel_name);
477+
my $sirius_number = $channel->{siriusChannelNumber} || $channel->{channelNumber} || '';
478+
479+
# Get xmplaylist name from station lookup if available
480+
my $xmp_name;
481+
if ($sirius_number && $station_lookup->{$sirius_number}) {
482+
$xmp_name = $station_lookup->{$sirius_number};
483+
$log->debug("Using xmplaylist deeplink for channel $sirius_number: $xmp_name");
484+
} else {
485+
$log->debug("No xmplaylist metadata available for channel $sirius_number ($channel_name), will use channel artwork");
486+
}
428487

429488
# Find the primary category
430489
my $primary_category = 'Other'; # Default fallback
@@ -516,7 +575,16 @@ sub getChannelsSortedByNumber {
516575

517576
$log->debug("Getting channels sorted by channel number");
518577

519-
# Check cache first
578+
# Check cache first for the processed data
579+
my $cached_processed = $cache->get('siriusxm_processed_channels');
580+
if ($cached_processed) {
581+
$log->debug("Building channel list from cached processed data");
582+
my $sorted_channels = $class->buildChannelNumberMenu($cached_processed);
583+
$cb->($sorted_channels);
584+
return;
585+
}
586+
587+
# Check legacy cache for backward compatibility
520588
my $cached_channel_info = $cache->get('siriusxm_channel_info');
521589
if ($cached_channel_info) {
522590
$log->debug("Building channel list from cached data");
@@ -529,8 +597,8 @@ sub getChannelsSortedByNumber {
529597
$class->getChannels($client, sub {
530598
my $category_menu = shift;
531599

532-
# Get the channel info from cache (which should be populated by getChannels)
533-
my $channel_info = $cache->get('siriusxm_channel_info');
600+
# Get the processed channel info from cache (which should be populated by getChannels)
601+
my $channel_info = $cache->get('siriusxm_processed_channels') || $cache->get('siriusxm_channel_info');
534602
if ($channel_info) {
535603
my $sorted_channels = $class->buildChannelNumberMenu($channel_info);
536604
$cb->($sorted_channels);

Plugins/SiriusXM/APImetadata.pm

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@ use warnings;
55

66
use Slim::Utils::Log;
77
use Slim::Utils::Prefs;
8+
use Slim::Utils::Cache;
89
use Slim::Networking::SimpleAsyncHTTP;
910
use JSON::XS;
1011
use Date::Parse;
1112
use Time::HiRes;
1213

1314
my $log = logger('plugin.siriusxm');
1415
my $prefs = preferences('plugin.siriusxm');
16+
my $cache = Slim::Utils::Cache->new();
1517

1618
use constant METADATA_STALE_TIME => 230;
19+
use constant STATION_CACHE_TIMEOUT => 21600; # 6 hours
1720

1821
# xmplaylists.com API JSON Schema:
1922
# {
@@ -55,6 +58,125 @@ use constant METADATA_STALE_TIME => 230;
5558
# }
5659
# }
5760

61+
# Track in-flight station listing requests to prevent concurrent calls
62+
my %station_fetch_callbacks = ();
63+
64+
# Fetch station listings from xmplaylist.com API
65+
# Returns mapping of siriusChannelNumber -> xmplaylist deeplink name
66+
sub fetchStationListings {
67+
my ($class, $callback) = @_;
68+
69+
# Check cache first
70+
my $cached_stations = $cache->get('xmplaylist_stations');
71+
if ($cached_stations) {
72+
$log->debug("Using cached station listings from xmplaylist.com");
73+
$callback->($cached_stations) if $callback;
74+
return;
75+
}
76+
77+
# Check if we're already fetching station listings
78+
if (exists $station_fetch_callbacks{'in_progress'}) {
79+
$log->debug("Station listings fetch already in progress, queuing callback");
80+
push @{$station_fetch_callbacks{'in_progress'}}, $callback if $callback;
81+
return;
82+
}
83+
84+
# Initialize the callback queue
85+
$station_fetch_callbacks{'in_progress'} = $callback ? [$callback] : [];
86+
87+
my $url = "https://xmplaylist.com/api/station";
88+
89+
$log->debug("Fetching station listings from xmplaylist.com");
90+
91+
my $http = Slim::Networking::SimpleAsyncHTTP->new(
92+
sub {
93+
my $response = shift;
94+
$class->_processStationListings($response, $station_fetch_callbacks{'in_progress'});
95+
},
96+
sub {
97+
my ($http, $error) = @_;
98+
$log->warn("Failed to fetch station listings from xmplaylist.com: $error");
99+
# Call all queued callbacks with empty result
100+
for my $cb (@{$station_fetch_callbacks{'in_progress'} || []}) {
101+
$cb->({}) if $cb;
102+
}
103+
delete $station_fetch_callbacks{'in_progress'};
104+
},
105+
{
106+
timeout => 30,
107+
}
108+
);
109+
110+
$http->get($url);
111+
}
112+
113+
# Process station listings response and build lookup table
114+
sub _processStationListings {
115+
my ($class, $response, $callbacks) = @_;
116+
117+
return unless $response;
118+
119+
# Ensure callbacks is an array reference
120+
$callbacks = [$callbacks] unless ref($callbacks) eq 'ARRAY';
121+
122+
my $content = $response->content;
123+
my $data;
124+
125+
eval {
126+
$data = decode_json($content);
127+
};
128+
129+
if ($@) {
130+
$log->error("Failed to parse station listings response: $@");
131+
# Call all queued callbacks with empty result
132+
for my $cb (@$callbacks) {
133+
$cb->({}) if $cb;
134+
}
135+
delete $station_fetch_callbacks{'in_progress'};
136+
return;
137+
}
138+
139+
# Build lookup table: siriusChannelNumber -> deeplink
140+
my %station_lookup = ();
141+
142+
if ($data->{results} && ref($data->{results}) eq 'ARRAY') {
143+
for my $station (@{$data->{results}}) {
144+
my $number = $station->{number};
145+
my $deeplink = $station->{deeplink};
146+
147+
if ($number && $deeplink) {
148+
$station_lookup{$number} = $deeplink;
149+
$log->debug("Station mapping: $number -> $deeplink");
150+
}
151+
}
152+
}
153+
154+
# Cache the lookup table
155+
$cache->set('xmplaylist_stations', \%station_lookup, STATION_CACHE_TIMEOUT);
156+
$log->info("Cached " . scalar(keys %station_lookup) . " station mappings for 6 hours");
157+
158+
# Call all queued callbacks
159+
for my $cb (@$callbacks) {
160+
$cb->(\%station_lookup) if $cb;
161+
}
162+
163+
# Clear the in-progress flag
164+
delete $station_fetch_callbacks{'in_progress'};
165+
}
166+
167+
# Get xmplaylist deeplink name for a given sirius channel number
168+
sub getChannelDeeplink {
169+
my ($class, $sirius_number, $callback) = @_;
170+
171+
return unless $sirius_number && $callback;
172+
173+
$class->fetchStationListings(sub {
174+
my $station_lookup = shift || {};
175+
my $deeplink = $station_lookup->{$sirius_number};
176+
$callback->($deeplink);
177+
});
178+
}
179+
58180
# Fetch metadata from xmplaylist.com API
59181
sub fetchMetadata {
60182
my ($class, $client, $channel_info, $callback) = @_;

0 commit comments

Comments
 (0)