A modern PHP SDK for interacting with RadioAPI services to retrieve radio stream metadata and search music tracks across multiple streaming platforms.
Install via Composer:
composer require joeyboli/radioapisdk- PHP 8.3 or higher
- Symfony HTTP Client 7.2+
use RadioAPI\RadioAPI;
// Simple configuration
$api = new RadioAPI('https://api.example.com', 'your-api-key');
// Get current stream metadata
$response = $api->getStreamTitle('https://stream.example.com/radio');
echo "Now Playing: " . $response->getCurrentTrack()['artist'] . " - " . $response->getCurrentTrack()['song'];
// Search for music
$response = $api->searchMusic('The Beatles - Hey Jude');
if ($response->hasResults()) {
$track = $response->getFirstTrack();
echo "Found: {$track['artist']} - {$track['title']}";
}
// Get image colors
$response = $api->getImageColors('https://example.com/image.jpg');
echo "Dominant color: " . $response->getDominantColorHex();use RadioAPI\RadioAPI;
// Basic configuration
$api = new RadioAPI('https://your-radioapi-instance.com', 'your-api-key');
// Advanced configuration with options
$api = new RadioAPI('https://your-radioapi-instance.com', 'your-api-key', [
'throw_on_errors' => true, // Throw exceptions on API errors (default: true)
'language' => 'en', // Response language (ISO 639-1 codes)
'with_history' => true, // Include track history in responses (default: true)
'timeout' => 30, // HTTP request timeout in seconds (default: 30)
'user_agent' => 'MyApp/1.0' // Custom user agent
]);throw_on_errors(bool) - Enable/disable exception throwing on API errors (default: true)language(string) - Set response language using ISO 639-1 codes: 'en', 'fr', 'de', etc.with_history(bool) - Include/exclude track history in responses (default: true)timeout(int) - HTTP request timeout in seconds (default: 30)user_agent(string) - Custom user agent string
The RadioAPI SDK integrates seamlessly with Laravel applications. Here are several ways to use it effectively.
Create a service provider to configure the RadioAPI client:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use RadioAPI\RadioAPI;
class RadioAPIServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton(RadioAPI::class, function ($app) {
return new RadioAPI(
config('services.radioapi.base_url'),
config('services.radioapi.api_key'),
[
'language' => config('app.locale', 'en'),
'with_history' => config('services.radioapi.with_history', true),
'timeout' => config('services.radioapi.timeout', 30),
'throw_on_errors' => config('services.radioapi.throw_on_errors', true),
]
);
});
}
}Add to config/services.php:
'radioapi' => [
'base_url' => env('RADIOAPI_BASE_URL'),
'api_key' => env('RADIOAPI_API_KEY'),
'with_history' => env('RADIOAPI_WITH_HISTORY', true),
'timeout' => env('RADIOAPI_TIMEOUT', 30),
'throw_on_errors' => env('RADIOAPI_THROW_ON_ERRORS', true),
],Add to your .env file:
RADIOAPI_BASE_URL=https://your-radioapi-instance.com
RADIOAPI_API_KEY=your-api-key-here
RADIOAPI_WITH_HISTORY=true
RADIOAPI_TIMEOUT=30
RADIOAPI_THROW_ON_ERRORS=trueUse dependency injection in your controllers:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use RadioAPI\RadioAPI;
use RadioAPI\Exceptions\RadioAPIException;
class RadioController extends Controller
{
public function __construct(private RadioAPI $radioApi)
{
}
public function getCurrentTrack(Request $request)
{
$streamUrl = $request->input('stream_url');
$service = $request->input('service', RadioAPI::AUTO);
try {
$response = $this->radioApi->getStreamTitle($streamUrl, $service);
if ($response->isSuccess()) {
return response()->json([
'success' => true,
'current_track' => $response->getCurrentTrack(),
'stream_info' => $response->getStreamInfo(),
'history' => $response->getHistory(),
]);
}
return response()->json([
'success' => false,
'error' => $response->getError()
], 400);
} catch (RadioAPIException $e) {
return response()->json([
'success' => false,
'error' => $e->getMessage(),
'status_code' => $e->getStatusCode()
], 500);
}
}
public function searchMusic(Request $request)
{
$query = $request->input('query');
$service = $request->input('service', RadioAPI::AUTO);
try {
$response = $this->radioApi->searchMusic($query, $service);
if ($response->hasResults()) {
return response()->json([
'success' => true,
'tracks' => $response->getTracks(),
'first_track' => $response->getFirstTrack(),
]);
}
return response()->json([
'success' => false,
'message' => 'No tracks found'
], 404);
} catch (RadioAPIException $e) {
return response()->json([
'success' => false,
'error' => $e->getMessage()
], 500);
}
}
public function getImageColors(Request $request)
{
$imageUrl = $request->input('image_url');
try {
$response = $this->radioApi->getImageColors($imageUrl);
if ($response->isSuccess()) {
return response()->json([
'success' => true,
'dominant_color' => $response->getDominantColorHex(),
'text_color' => $response->getTextColorHex(),
'flutter_dominant' => $response->getDominantColorFlutterHex(),
'flutter_text' => $response->getTextColorFlutterHex(),
'palette' => $response->getPalette(),
]);
}
return response()->json([
'success' => false,
'error' => $response->getError()
], 400);
} catch (RadioAPIException $e) {
return response()->json([
'success' => false,
'error' => $e->getMessage()
], 500);
}
}
}Create Artisan commands for radio metadata operations:
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use RadioAPI\RadioAPI;
use RadioAPI\Exceptions\RadioAPIException;
class GetRadioMetadata extends Command
{
protected $signature = 'radio:metadata {stream_url} {--service=auto}';
protected $description = 'Get current track metadata from a radio stream';
public function __construct(private RadioAPI $radioApi)
{
parent::__construct();
}
public function handle()
{
$streamUrl = $this->argument('stream_url');
$service = $this->option('service');
try {
$response = $this->radioApi->getStreamTitle($streamUrl, $service);
if ($response->isSuccess()) {
$track = $response->getCurrentTrack();
$streamInfo = $response->getStreamInfo();
$this->info("Stream: {$streamInfo['name']}");
if ($track) {
$this->info("Now Playing: {$track['artist']} - {$track['song']}");
$this->info("Album: {$track['album']}");
$this->info("Year: {$track['year']}");
} else {
$this->warn('No current track information available');
}
$history = $response->getHistory();
if (!empty($history)) {
$this->info("\nRecent History:");
foreach (array_slice($history, 0, 5) as $historyTrack) {
$this->line("- {$historyTrack['artist']} - {$historyTrack['song']} ({$historyTrack['relative_time']})");
}
}
} else {
$this->error('Failed to get metadata: ' . $response->getError());
}
} catch (RadioAPIException $e) {
$this->error('API Error: ' . $e->getMessage());
return 1;
}
return 0;
}
}Use Laravel jobs for background processing:
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use RadioAPI\RadioAPI;
use RadioAPI\Exceptions\RadioAPIException;
use App\Models\RadioStation;
class UpdateRadioMetadata implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
private RadioStation $station,
private string $service = RadioAPI::AUTO
) {
}
public function handle(RadioAPI $radioApi)
{
try {
$response = $radioApi->getStreamTitle($this->station->stream_url, $this->service);
if ($response->isSuccess()) {
$track = $response->getCurrentTrack();
$streamInfo = $response->getStreamInfo();
// Update station metadata
$this->station->update([
'current_artist' => $track['artist'] ?? null,
'current_song' => $track['song'] ?? null,
'current_album' => $track['album'] ?? null,
'stream_name' => $streamInfo['name'] ?? null,
'bitrate' => $streamInfo['bitrate'] ?? null,
'format' => $streamInfo['format'] ?? null,
'last_updated' => now(),
]);
// Store track history
$history = $response->getHistory();
foreach ($history as $historyTrack) {
$this->station->trackHistory()->updateOrCreate([
'artist' => $historyTrack['artist'],
'song' => $historyTrack['song'],
'played_at' => $historyTrack['timestamp'],
]);
}
}
} catch (RadioAPIException $e) {
\Log::error('RadioAPI error for station ' . $this->station->id . ': ' . $e->getMessage());
$this->fail($e);
}
}
}Display radio metadata in Blade templates:
{{-- resources/views/radio/player.blade.php --}}
<div class="radio-player" x-data="radioPlayer('{{ $station->stream_url }}')" x-init="init()">
<div class="current-track">
<template x-if="currentTrack">
<div>
<h3 x-text="currentTrack.artist + ' - ' + currentTrack.song"></h3>
<p x-text="currentTrack.album"></p>
<img x-show="currentTrack.artwork" :src="currentTrack.artwork" alt="Album artwork">
</div>
</template>
<template x-if="!currentTrack">
<div class="no-metadata">
<p>No track information available</p>
</div>
</template>
</div>
<div class="track-history" x-show="history.length > 0">
<h4>Recently Played</h4>
<ul>
<template x-for="track in history.slice(0, 5)" :key="track.timestamp">
<li>
<span x-text="track.artist + ' - ' + track.song"></span>
<small x-text="track.relative_time"></small>
</li>
</template>
</ul>
</div>
</div>
<script>
function radioPlayer(streamUrl) {
return {
currentTrack: null,
history: [],
streamInfo: null,
init() {
this.fetchMetadata();
// Update every 30 seconds
setInterval(() => this.fetchMetadata(), 30000);
},
async fetchMetadata() {
try {
const response = await fetch('/api/radio/metadata', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify({
stream_url: streamUrl,
service: 'spotify'
})
});
const data = await response.json();
if (data.success) {
this.currentTrack = data.current_track;
this.history = data.history || [];
this.streamInfo = data.stream_info;
}
} catch (error) {
console.error('Failed to fetch metadata:', error);
}
}
}
}
</script>Define API routes for radio metadata:
// routes/api.php
use App\Http\Controllers\RadioController;
Route::prefix('radio')->group(function () {
Route::post('/metadata', [RadioController::class, 'getCurrentTrack']);
Route::post('/search', [RadioController::class, 'searchMusic']);
Route::post('/colors', [RadioController::class, 'getImageColors']);
});Set up scheduled tasks to update radio metadata:
// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
// Update all radio stations every minute
$schedule->call(function () {
$stations = \App\Models\RadioStation::active()->get();
foreach ($stations as $station) {
\App\Jobs\UpdateRadioMetadata::dispatch($station, RadioAPI::SPOTIFY);
}
})->everyMinute();
}Implement caching for better performance:
<?php
namespace App\Services;
use Illuminate\Support\Facades\Cache;
use RadioAPI\RadioAPI;
use RadioAPI\Exceptions\RadioAPIException;
class CachedRadioAPIService
{
public function __construct(private RadioAPI $radioApi)
{
}
public function getStreamTitle(string $streamUrl, string $service = RadioAPI::AUTO, int $cacheTtl = 60)
{
$cacheKey = "radio_metadata:" . md5($streamUrl . $service);
return Cache::remember($cacheKey, $cacheTtl, function () use ($streamUrl, $service) {
try {
$response = $this->radioApi->getStreamTitle($streamUrl, $service);
if ($response->isSuccess()) {
return [
'success' => true,
'current_track' => $response->getCurrentTrack(),
'stream_info' => $response->getStreamInfo(),
'history' => $response->getHistory(),
];
}
return [
'success' => false,
'error' => $response->getError()
];
} catch (RadioAPIException $e) {
return [
'success' => false,
'error' => $e->getMessage(),
'status_code' => $e->getStatusCode()
];
}
});
}
}Create tests for your RadioAPI integration:
<?php
namespace Tests\Feature;
use Tests\TestCase;
use RadioAPI\RadioAPI;
use RadioAPI\Responses\StreamTitleResponse;
use Mockery;
class RadioAPITest extends TestCase
{
public function test_can_get_stream_metadata()
{
$mockApi = Mockery::mock(RadioAPI::class);
$mockResponse = Mockery::mock(StreamTitleResponse::class);
$mockResponse->shouldReceive('isSuccess')->andReturn(true);
$mockResponse->shouldReceive('getCurrentTrack')->andReturn([
'artist' => 'Test Artist',
'song' => 'Test Song',
'album' => 'Test Album'
]);
$mockApi->shouldReceive('getStreamTitle')
->with('https://test.stream.com', RadioAPI::SPOTIFY)
->andReturn($mockResponse);
$this->app->instance(RadioAPI::class, $mockApi);
$response = $this->postJson('/api/radio/metadata', [
'stream_url' => 'https://test.stream.com',
'service' => 'spotify'
]);
$response->assertStatus(200)
->assertJson([
'success' => true,
'current_track' => [
'artist' => 'Test Artist',
'song' => 'Test Song',
'album' => 'Test Album'
]
]);
}
}Retrieve current playing track metadata from radio streams.
$response = $api->getStreamTitle('https://stream.example.com/radio');
if ($response->isSuccess()) {
$currentTrack = $response->getCurrentTrack();
echo "Now Playing: {$currentTrack['artist']} - {$currentTrack['song']}\n";
echo "Album: {$currentTrack['album']}\n";
// Access stream info
$streamInfo = $response->getStreamInfo();
echo "Stream: {$streamInfo['name']} ({$streamInfo['bitrate']}kbps)\n";
// Get track history
$history = $response->getHistory();
foreach ($history as $track) {
echo "Previous: {$track['artist']} - {$track['song']} ({$track['relative_time']})\n";
}
}getCurrentTrack()- Get current playing track informationgetStreamInfo()- Get stream metadata (name, bitrate, format)getHistory()- Get track history arrayisSuccess()- Check if the request was successfulgetRawData()- Get the complete raw response datagetError()- Get error message if request failed
The response objects provide convenient methods to access data, but you can also access the raw response data:
Current Track Information:
$currentTrack = $response->getCurrentTrack();
// Returns:
[
'artist' => 'RESCENE',
'song' => 'LOVE ATTACK',
'album' => 'SCENEDROME',
'genre' => 'Asiatische Musik',
'artwork' => 'https://icdn2.streamafrica.net/stacks/e24820c67fecb4c0.jpg',
'year' => 2024,
'duration' => 181,
'elapsed' => 111,
'remaining' => 70,
'time' => '03:01',
'stream' => 'https://song.link/d/2966352091',
'explicit' => false
]Stream Information:
$streamInfo = $response->getStreamInfo();
// Returns:
[
'name' => 'Hunter.FM - O Canal K-pop',
'bitrate' => 256,
'format' => 'AAC'
]Track History:
$history = $response->getHistory();
// Returns array of:
[
[
'artist' => 'ENHYPEN',
'song' => 'Bite Me',
'timestamp' => '2025-10-12 16:39:25.868785',
'relative_time' => '4 minutes ago',
'artwork' => 'https://icdn2.streamafrica.net/stacks/6a615f61aac53844.jpg'
]
// ... more tracks
]Search for music tracks across various streaming platforms.
$response = $api->searchMusic('The Beatles - Hey Jude');
if ($response->hasResults()) {
$track = $response->getFirstTrack();
echo "Found: {$track['artist']} - {$track['title']}\n";
echo "Album: {$track['album']}\n";
echo "Listen: {$track['stream']}\n";
// Access all tracks
$tracks = $response->getTracks();
foreach ($tracks as $track) {
echo "Track: {$track['artist']} - {$track['title']}\n";
}
}
// Search with specific service
$response = $api->searchMusic('Radiohead Creep', RadioAPI::SPOTIFY);getTracks()- Get array of all found tracksgetFirstTrack()- Get the first/best match trackhasResults()- Check if any tracks were foundisSuccess()- Check if the request was successfulgetRawData()- Get the complete raw response datagetError()- Get error message if request failed
Track Information:
$track = $response->getFirstTrack();
// Returns:
[
'artist' => 'SKKST',
'title' => 'Dance With My Hands',
'album' => 'Dance With My Hands',
'genre' => 'Dance',
'artwork' => [
'small' => 'https://cdn-images.dzcdn.net/images/cover/.../56x56-000000-80-0-0.jpg',
'medium' => 'https://cdn-images.dzcdn.net/images/cover/.../250x250-000000-80-0-0.jpg',
'large' => 'https://cdn-images.dzcdn.net/images/cover/.../500x500-000000-80-0-0.jpg',
'xl' => 'https://cdn-images.dzcdn.net/images/cover/.../1000x1000-000000-80-0-0.jpg'
],
'artist_artwork' => 'https://cdn-images.dzcdn.net/images/artist/.../1000x1000-000000-80-0-0.jpg',
'duration' => 124,
'stream' => 'https://www.deezer.com/track/2109711027',
'explicit' => false,
'year' => 2023
]Extract dominant colors and generate color palettes from images.
$response = $api->getImageColors('https://example.com/image.jpg');
if ($response->isSuccess()) {
echo "Dominant Color: " . $response->getDominantColorHex() . "\n";
echo "Text Color: " . $response->getTextColorHex() . "\n";
// Get Flutter-compatible hex colors
echo "Flutter Dominant: " . $response->getDominantColorFlutterHex() . "\n";
echo "Flutter Text: " . $response->getTextColorFlutterHex() . "\n";
// Get full color palette
$palette = $response->getPalette();
foreach ($palette as $color) {
echo "Color: {$color['hex']} (Population: {$color['population']})\n";
}
}getDominantColorHex()- Get dominant color as hex string (#RRGGBB)getTextColorHex()- Get recommended text color as hex stringgetDominantColorFlutterHex()- Get dominant color in Flutter format (0xFFRRGGBB)getTextColorFlutterHex()- Get text color in Flutter formatgetPalette()- Get complete color palette with population dataisSuccess()- Check if the request was successfulgetRawData()- Get the complete raw response datagetError()- Get error message if request failed
Enhance metadata with service-specific information by passing the service as a second parameter:
// Spotify integration for stream metadata
$response = $api->getStreamTitle('https://stream.example.com/radio', RadioAPI::SPOTIFY);
// Deezer integration for music search
$response = $api->searchMusic('Radiohead Creep', RadioAPI::DEEZER);
// Auto-detect best service
$response = $api->getStreamTitle('https://stream.example.com/radio', RadioAPI::AUTO);For StreamTitle and MusicSearch:
RadioAPI::SPOTIFY- SpotifyRadioAPI::DEEZER- DeezerRadioAPI::APPLE_MUSIC- Apple Music (iTunes)RadioAPI::YOUTUBE_MUSIC- YouTube MusicRadioAPI::FLO_MUSIC- FLO MusicRadioAPI::LINE_MUSIC- LINE MusicRadioAPI::AUTO- Auto-detect service
For StreamTitle only (Radio Platforms):
RadioAPI::AZURACAST- AzuraCast platformRadioAPI::LIVE365- Live365 platform
For radio platforms, use specific URL formats:
AzuraCast:
$response = $api->getStreamTitle(
'https://azuracast.example.com/listen/stationid/mountpoint',
RadioAPI::AZURACAST
);Live365:
$response = $api->getStreamTitle(
'https://streaming.live365.com/mountid',
RadioAPI::LIVE365
);The SDK uses a single RadioAPIException class for all API errors, with helper methods to categorize error types:
use RadioAPI\Exceptions\RadioAPIException;
try {
$response = $api->getStreamTitle('https://stream.example.com/radio');
} catch (RadioAPIException $e) {
if ($e->isClientError()) {
// Handle 4xx errors (bad request, unauthorized, etc.)
echo "Client error: {$e->getMessage()}";
} elseif ($e->isServerError()) {
// Handle 5xx errors (server issues)
echo "Server error: {$e->getMessage()}";
} elseif ($e->isNetworkError()) {
// Handle network connectivity issues
echo "Network error: {$e->getMessage()}";
}
// Access detailed error information
echo "Status code: {$e->getStatusCode()}";
echo "Error data: " . json_encode($e->getErrorData());
echo "Context: " . json_encode($e->getContext());
}The RadioAPIException class provides:
getMessage()- Get the error messagegetStatusCode()- Get the HTTP status codegetErrorData()- Get the original API error responsegetContext()- Get additional context informationhasErrorField(string $field)- Check if error data contains a fieldgetErrorField(string $field, $default = null)- Get specific error fieldisClientError()- Check if error is a 4xx client errorisServerError()- Check if error is a 5xx server errorisNetworkError()- Check if error is network-related
// Configure to not throw exceptions
$api = new RadioAPI('https://api.example.com', 'api-key', [
'throw_on_errors' => false
]);
$response = $api->getStreamTitle('https://stream.example.com/radio');
if (!$response->isSuccess()) {
echo "Error occurred: " . $response->getError();
}$services = [
RadioAPI::SPOTIFY,
RadioAPI::DEEZER,
RadioAPI::APPLE_MUSIC,
];
$streamUrl = 'https://stream.example.com/radio';
foreach ($services as $service) {
try {
$response = $api->getStreamTitle($streamUrl, $service);
if ($response->isSuccess() && $response->getCurrentTrack()) {
echo "Found metadata using: $service\n";
$track = $response->getCurrentTrack();
echo "Track: {$track['artist']} - {$track['song']}\n";
break;
}
} catch (RadioAPIException $e) {
// Log error and continue to next service
error_log("Service $service failed: " . $e->getMessage());
}
}Configure the client with various options:
// Performance-optimized configuration
$api = new RadioAPI('https://api.example.com', 'api-key', [
'with_history' => false, // Disable history for faster responses
'timeout' => 10, // Shorter timeout for quick responses
'language' => 'en' // Set response language
]);
// Multi-language support
$api = new RadioAPI('https://api.example.com', 'api-key', [
'language' => 'fr' // French, German (de), Spanish (es), Japanese (ja), etc.
]);$response = $api->getStreamTitle('https://stream.example.com/radio');
// Check response status
if ($response->isSuccess()) {
$currentTrack = $response->getCurrentTrack();
if ($currentTrack) {
echo "Track: {$currentTrack['artist']} - {$currentTrack['song']}\n";
} else {
echo "No current track information available\n";
}
// Check for history
$history = $response->getHistory();
if (!empty($history)) {
echo "Track history available: " . count($history) . " tracks\n";
}
} else {
echo "Request failed: " . $response->getError() . "\n";
}$response = $api->getStreamTitle('https://stream.example.com/radio');
// Get complete raw response data
$rawData = $response->getRawData();
echo json_encode($rawData, JSON_PRETTY_PRINT);
// Access specific fields from raw data
if (isset($rawData['metadataFound']) && $rawData['metadataFound']) {
echo "Metadata found in raw response\n";
}-
Disable history when not needed:
$api->withHistory(false);
-
Use specific services instead of auto-detection:
$api->withService(RadioAPI::SPOTIFY); // Better than RadioAPI::AUTO
-
Handle empty responses gracefully:
if (empty($api->streamTitle()->setStreamUrl($url)->fetchArray())) { // Handle empty response }
-
Always validate configuration:
if (empty($baseUrl) || empty($apiKey)) { throw new InvalidArgumentException('Base URL and API key required'); }
-
Use appropriate exception handling:
try { $data = $api->streamTitle()->setStreamUrl($url)->fetchArray(); } catch (ClientErrorException $e) { // Log and handle client errors (bad request, unauthorized, etc.) } catch (ServerErrorException $e) { // Retry logic for server errors }
-
Choose services based on your audience:
- Use
SPOTIFYfor global audiences - Use
DEEZERfor European audiences - Use
APPLE_MUSICfor iOS-focused applications
- Use
-
Implement fallback chains:
$services = [RadioAPI::SPOTIFY, RadioAPI::DEEZER, RadioAPI::APPLE_MUSIC]; foreach ($services as $service) { $data = $api->withService($service)->streamTitle()->setStreamUrl($url)->fetchArray(); if ($data['metadataFound']) break; }
$api = new RadioAPI($radioApiUrl, $apiKey, ['with_history' => true]);
$response = $api->getStreamTitle($stationStreamUrl, RadioAPI::SPOTIFY);
if ($response->isSuccess()) {
$currentTrack = $response->getCurrentTrack();
$streamInfo = $response->getStreamInfo();
if ($currentTrack) {
echo "Now Playing: {$currentTrack['artist']} - {$currentTrack['song']}\n";
echo "Album: {$currentTrack['album']}\n";
echo "Duration: {$currentTrack['time']}\n";
}
echo "Stream: {$streamInfo['name']} ({$streamInfo['bitrate']}kbps)\n";
$history = $response->getHistory();
if (!empty($history)) {
echo "Recently Played:\n";
foreach (array_slice($history, 0, 5) as $track) {
echo "- {$track['artist']} - {$track['song']} ({$track['relative_time']})\n";
}
}
}$searchQuery = "indie rock 2024";
$response = $api->searchMusic($searchQuery, RadioAPI::SPOTIFY);
if ($response->hasResults()) {
$track = $response->getFirstTrack();
echo "Found: {$track['artist']} - {$track['title']}\n";
echo "Listen on Spotify: {$track['stream']}\n";
if (!empty($track['artwork']['large'])) {
echo "Artwork: {$track['artwork']['large']}\n";
}
}$imageUrl = 'https://example.com/album-cover.jpg';
$response = $api->getImageColors($imageUrl);
if ($response->isSuccess()) {
echo "Dominant Color: " . $response->getDominantColorHex() . "\n";
echo "Text Color: " . $response->getTextColorHex() . "\n";
// Use in CSS
echo "CSS: background-color: " . $response->getDominantColorHex() . "; color: " . $response->getTextColorHex() . ";\n";
// Use in Flutter
echo "Flutter: Color(" . $response->getDominantColorFlutterHex() . ")\n";
}class RadioMetadataService {
private RadioAPI $api;
public function __construct(string $baseUrl, string $apiKey) {
$this->api = new RadioAPI($baseUrl, $apiKey);
}
public function getCurrentTrack(string $streamUrl, array $preferredServices = []): ?array {
$services = $preferredServices ?: [
RadioAPI::SPOTIFY,
RadioAPI::DEEZER,
RadioAPI::APPLE_MUSIC
];
foreach ($services as $service) {
try {
$response = $this->api->getStreamTitle($streamUrl, $service);
if ($response->isSuccess()) {
$track = $response->getCurrentTrack();
if ($track) {
return $track;
}
}
} catch (RadioAPIException $e) {
// Log error and continue to next service
error_log("Service $service failed: " . $e->getMessage());
}
}
return null;
}
}-
Empty responses:
- Verify base URL and API key are set
- Check if stream URL is accessible
- Ensure the stream contains metadata
-
Authentication errors:
- Verify API key is correct
- Check if API key has required permissions
-
Service-specific errors:
- Some services may not have metadata for all tracks
- Try different service mount points
- Use fallback to basic endpoint without service
Enable detailed error information:
$api = new RadioAPI('https://api.example.com', 'api-key', [
'throw_on_errors' => true
]);
try {
$response = $api->getStreamTitle($url);
} catch (RadioAPIException $e) {
echo "Error: " . $e->getMessage() . "\n";
echo "Status: " . $e->getStatusCode() . "\n";
echo "Context: " . json_encode($e->getContext()) . "\n";
echo "Error Data: " . json_encode($e->getErrorData()) . "\n";
// Check error type
if ($e->isClientError()) {
echo "This is a client error (4xx)\n";
} elseif ($e->isServerError()) {
echo "This is a server error (5xx)\n";
}
}Apache-2.0