First, thank you for purchasing PawTunes, our internet radio station player. This player is the culmination of a decade of experience in creating web players for both clients and personal projects. Designed with flexibility in mind, it is highly extendable and easily customizable to meet your specific needs.
PawTunes has been fine-tuned for high performance, capable of handling thousands of listeners simultaneously. It can also be seamlessly integrated into Android/iOS web app frameworks. However, please note that integration services are not currently offered as part of my services.
PawTunes offers the flexibility to manage multiple channels within a single instance, each utilizing different methods for track information retrieval. It supports multi-language translations and includes built-in templates that are easily customizable through the integrated control panel. The player features an extensive artwork search library, supporting major providers, and seamlessly integrates into your website, allowing visitors to browse without interruptions.
There are many online radio players available today. Most of these players are simple JavaScript scripts that play internet radio stations and display basic information from a single source. What sets PawTunes apart is its ability to utilize multiple methods to retrieve live information from your streaming solution, including song history and artwork. It also excels at combining various sources for a more comprehensive experience.
Unlike most subscription-based services, PawTunes is committed to remaining perpetual, charging only for additional functionality customized to the specific needs of individuals, stations, or companies.
PawTunes can use various sources, like Shoutcast (both Public and Private (admin authenticated)), Icecast, SAM Broadcaster, AzuraCast, CentovaCast, Live Stream (directly from audio stream), and Custom (per your choosing). All of these APIs/methods will use the PawTunes Artwork Library, which is powerful and allows multiple sources to work at the same time.
This player uses Spotify, iTunes, LastFM, FanArt TV, and Custom methods to get images for Artist or Track artwork. The benefit of using PawTunes instead of a direct API query is that it optimizes image quality and caches images for future use, avoiding overloading API sources with excessive amounts of requests. The player can also import large amounts of images from your defined source.
PawTunes does not have many requirements, thanks to its core code being designed to work on most systems available today. The base requirement is a web server that supports executing PHP scripts (PHP 7.4+).
The biggest potential issue for some users might be ensuring firewall-forwarded ports that allow PawTunes to connect to the outside world on the streaming ports you use (e.g., Shoutcast uses port 8000). These ports are necessary for the player to query/connect to the streaming service and retrieve its current state and information.
For extreme cases where port forwarding is not possible, Prahec is developing a service that will allow proxying those requests via ports 80/443, which are universally open across all web hosting providers worldwide.
Tip
You may also want to set up FTP access to your hosting provider, for easier file upload.
To begin the installation, first, download the installable ZIP file from where you purchased the player. The ZIP file contains two main folders: installation and documentation.
Extract the installation folder to your web host. You can do this directly on the server or by extracting the files on your computer and uploading them via FTP. Once all the files are uploaded, ensure the following permissions are correctly applied:
You might also need to change CHMOD of file "/inc/config/general.php" to 644. That’s it! After setting up, visit the control panel and log in using the default authentication details.
To access the control panel you need to go to http(s)://your-domain.com/player-folder/panel .
Note
Username: admin
Password: password
Caution
It is important that, after logging in, you update the login credentials immediately!
PawTunes comes with an extensive control panel that offers a wide range of options and settings for various areas of the player. Initially, the player does not include any preconfigured channels or special settings. The most crucial step is to configure the channels, as the player will display an error if no channels are set up.
To configure your first channel, navigate to the Channels section and click "Add Channel". All the necessary details for creating a channel are provided on the channel creation page. I recommend avoiding the "Live Stream (Direct)" method unless absolutely necessary, as it is the most resource-intensive for both your server and streaming provider.
Once you’ve filled out the required fields and clicked Save, your first channel will be successfully created.
Note
Live Stream (Direct) method uses the same approach as players like Winamp, Windows Media Player, VLC, and others by reading metadata directly from the stream itself. However, since that cannot be done on the "Playing side" (JS) it must be done via Back-end PHP side. That means every info check, stream is briefly connected and disconnected.
When a single channel is configured, the player automatically hides the option to switch channels. Configuring multiple channels, however, allows you to assign unique color themes to each channel, dynamically changing the player's appearance based on the selected channel. Aside from this, there is no difference in the player's functionality.
PawTunes also includes the ability to define multiple streams per channel, allowing your listeners to choose different quality or encoding options based on their needs. For example, a 128kbps AAC stream might be ideal for mobile users, while a 320kbps stream could be preferred for desktop computers or similar devices. To add a group, click the "Add Group" button on the channel creation page.
When only a single stream is defined, the player will hide the option to change the channel stream source.
Tip
To modify the name of a quality stream group, click on the "Default Quality" name, which is an editable field.
In PawTunes, passwords are hashed (one-way encrypted), which means you will need to reset the password value in the configuration to empty. This will reset the login credentials to the default upon visiting the control panel. Sometimes web hosting providers use PHP OPCache
, so make sure that the cache is also reset before attempting to log in.
To clear the password value, go to inc/config/general.php
and find the key admin_password
, then set it to empty, like this:
<?php
return [
// ...more above
'images_size' => '360',
'license' => '',
'admin_username' => 'admin',
'admin_password' => '', // This will be set next time you visit panel
'development' => true,
'cache' =>
[
'path' => './data/cache',
'mode' => 'apcu',
],
];
Most general player options are self-explanatory and are also explained on the settings page. However, there are a few important notes to consider:
The Live Information settings allow you to configure the stats refresh speed, which significantly impacts the player's performance. Here’s what you need to consider:
Low Refresh Speed:
Setting a low refresh speed means the player will make frequent requests. With a large number of listeners, this can overload your web server, causing it to crash or stop responding.
High Refresh Speed:
Reducing the frequency of refreshes (i.e., increasing the refresh time) decreases the number of requests per second, making it more suitable for shared hosting providers or servers with request limits.
Caching:
Stats are cached for a minimum of 5 seconds. This means that if you have 1,000 listeners, only one will make the long request to retrieve stats and artist images (e.g., from LastFM), while the other 999 will receive the cached response.
Recommendation: For shared hosting, set a higher refresh time to avoid potential issues with resource limitations or account termination due to excessive requests. Adjust the refresh speed carefully to balance responsiveness and server load.
Azura Cast now-playing information is currently the most recommended method to use when available. Using Web Sockets (please read more on API & Web Sockets here), you can get instantaneous information from the stream when playback information changes. Web Sockets are highly recommended. An additional benefit is that PawTunes can be configured to use real song history from Azura Cast and artworks as well. This allows perfect control over your streaming information and centralized management of ID3 tags. Another advantage is that PawTunes can still handle artwork while Web Sockets provide real-time track information.
The Custom method supports both JSON with History and simple Artist - Title formats. If an image is missing, the player will attempt to fetch it from other available sources, just as it does with other methods. Below is an example of a JSON format that can be used - This format allows the player to display both current and historical track information along with associated artwork.
{
"artist": "David Guetta",
"title": "Lonely Is The Night",
"image": "https://prahec.com/assets/img/album/lonely-is-the-night.png",
"history": [
{
"played_at": "2024-09-27T06:02:11.0120033-03:00",
"title": "Lonely Is The Night",
"artist": "Air Supply"
},
{
"played_at": "2024-09-27T05:58:17.5843617-03:00",
"title": "Dona",
"artist": "Roupa Nova"
},
{
"played_at": "2024-09-27T05:55:40.6524339-03:00",
"title": "I've Been Around",
"artist": "Nathan Jones"
},
{
"played_at": "2024-09-27T05:55:35.8128193-03:00",
"title": "100% romântica",
"artist": "Rádio Só Kakarecos Light"
},
{
"played_at": "2024-09-27T05:49:42.6854779-03:00",
"title": "Do It To Me",
"artist": "Lionel Richie"
},
{
"played_at": "2024-09-27T05:46:30.5140752-03:00",
"title": "Graffiti",
"artist": "The Paris Group"
},
{
"played_at": "2024-09-27T05:41:27.1440521-03:00",
"title": "Esquinas",
"artist": "Djavan"
},
{
"played_at": "2024-09-27T05:36:03.4899532-03:00",
"title": "I'll Be There For You",
"artist": "Bon Jovi"
},
{
"played_at": "2024-09-27T05:35:45.005106-03:00",
"title": "A Só Kakarecos Light",
"artist": "Esta é"
},
{
"played_at": "2024-09-27T05:31:51.9160074-03:00",
"title": "A Horse With No Name",
"artist": "America"
}
]
}
Some time ago, I developed a simple Go application that listens to the stream and provides real-time information to PawTunes via Web Sockets. This functionality is similar to how media players like Windows Media Player, Winamp, or iTunes operate.
While this app is not included as part of PawTunes, I am happy to provide it upon request. It is easy to configure, but it is best installed directly on the streaming server (hence, an advanced feature). Although it can work elsewhere, the app includes a built-in proxy for HTTPS and a Web Sockets server, which may require adequate bandwidth for optimal performance.
If you are interested in using this functionality, please reach out to me via the Contact page.
server:
port: 8001
certificate: "path/to/cert.crt"
key: "path/to/cert.key"
stream_url: "https://radio.prahec.com/listen/prahec_house/house.96K.mp3"
websockets: true
history:
enabled: true
max_entries: 10
The Player API is a powerful addition to your player, enabling you to display the status of your stream on your website or any other web app you use. This eliminates the need for additional code to fetch live information and artworks. Below are examples showing how to integrate your player into your website.
The code below uses jQuery in conjunction with JSONP (JSONP, or JSON with Padding, is a technique that bypasses the same-origin policy by allowing cross-domain requests through dynamically injected <script>
tags).
$.getJSON( 'https://your-radio-url.com/player/?channel=all&callback=?', response => {
if ( !response ) {
return console.error( 'Unable to get list of channels...' );
}
response.forEach( channel => {
$( '<li><a class="dropdown-item" href="#">' + channel.name + '</a></li>' )
.on( 'click', () => {
window.open( 'https://your-radio-url.com/player/', 'pawtunes', 'width=1024,height=650' );
return false;
} )
.appendTo( '.channels-list' );
} );
} );
The HTML side of code is using Bootstrap 5 dropdowns
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Radio Player
</button>
<ul class="dropdown-menu channels-list"></ul>
</div>
The code below uses the jQuery JavaScript library and a JSONP request, which simplifies cross-domain requests and allows you to retrieve the status from any domain. Replace your-radio-url.com
with your radio URL and CHANNEL_NAME
with your channel name (note: use URI encoding, such as %20
for spaces).
$.getJSON( 'https://your-radio-url.com/player/?channel=CHANEL_NAME&callback=?', response => {
if ( !response ) {
return console.error( 'Unable to get channel status...' );
}
// Write to DOM <div class="channel-info">REPLACED BY INFO</div>
$( '.channel-info' ).text( `${response.artist} - ${response.title}` );
} );
The Serve via Web option is disabled by default as it may not function correctly on some servers. However, it is an excellent choice for those with properly configured NGINX, LiteSpeed, or Apache setups. This option enables files to be served internally without redirecting the browser to another page. It leverages browser caching and is significantly faster because PHP does not handle file delivery directly but simply redirects to the files.
In the Artwork Settings, you can configure caching, which is a crucial feature that significantly reduces the load on artwork APIs such as iTunes and Spotify. Caching also optimizes images to ensure they fit the player better and enhance the overall visual experience. Cached images are stored in the cache folder, allowing them to be shared with other players you may use. The player supports two modes: Artist Images Mode and Track Artwork Mode.
Lazy Artwork Loading is a fantastic option that allows track information to be displayed before the artwork is fully loaded. Since artwork loading is typically slower, this ensures the player displays the currently playing song much faster, giving the impression of a more responsive application. When combined with multiple artwork sources, lazy loading becomes an essential feature to maintain the responsiveness of your player. Loading artwork simultaneously with track information can lead to prolonged PHP sessions, which may slow down your entire server.
As mentioned before, there are multiple artwork sources available. Under this setting, you can adjust the priority of these sources by dragging and dropping the dots to the right of the checkboxes, which enable or disable the specific methods. On the right side, you'll find an empty input field that must be filled with either an API key, a URL, or another input method specific to the selected artwork source.
Note
Spotify requires OAuth Authentication, which involves generating an API key and secret. This can only be done with a Spotify Developer Account, available at Spotify for Developers.
If you have any suggestions for additional artwork sources, I’d be happy to hear about them and consider incorporating them in future updates.
There are many ways to debug the player. My recommendation is to use the developer console built into your web browser, which you can open with F12
or CTRL + J
. This method helps identify front-end or JavaScript errors. Additionally, the player includes a built-in debugger under the "Tools" page, allowing you to debug your streams and their configurations. Read more about these methods below.
When using the stream debugging tool under the Tools page, there are two possible responses:
Without Debugging Mode Enabled:
If debugging mode is not enabled (configured on the Settings page), the tool will simply check whether the stream works or not.
With Debugging Mode Enabled:
When debugging mode is set to enabled
, it will display the output of the CURL call, as shown in the example below
Below is an example of a valid and successful response from the demo web server. For general use, enabling debugging mode is not recommended. Instead, you should use the log-only mode, which will still help you track any errors from the player without impacting performance.
Connecting to https://prahec.com/api/test (Update Center)...
**Connection successfully established!**
**CURL VERBOSE LOG** ( https://prahec.com/api/test )
*********************************************************************************************************
* Host prahec.com:443 was resolved.
* IPv6: (none)
* IPv4: 167.235.33.194
* Trying 167.235.33.194:443...
* Connected to prahec.com (167.235.33.194) port 443
* ALPN: curl offers h2,http/1.1
* CAfile: D:\Development\Web Development\Envato\pawtunes\inc\lib\bundle.crt
* CApath: none
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / RSASSA-PSS
* ALPN: server accepted http/1.1
* Server certificate:
* subject: CN=prahec.com
* start date: Nov 13 20:10:38 2024 GMT
* expire date: Feb 11 20:10:37 2025 GMT
* subjectAltName: host "prahec.com" matched cert\'s "prahec.com"
* issuer: C=US; O=Let\'s Encrypt; CN=R10
* SSL certificate verify ok.
* Certificate level 0: Public key type RSA (4096/152 Bits/secBits), signed using sha256WithRSAEncryption
* Certificate level 1: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Certificate level 2: Public key type RSA (4096/152 Bits/secBits), signed using sha256WithRSAEncryption
* using HTTP/1.x
> GET /api/test HTTP/1.1
Host: prahec.com
Range: bytes=0-500
User-Agent: Mozilla/5.0 (PawTunes) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: */*
* old SSL session ID is stale, removing
< HTTP/1.1 200 OK
< Server: nginx
< Content-Type: application/json
< Transfer-Encoding: chunked
< Connection: keep-alive
< Vary: Accept-Encoding
< Cache-Control: no-cache, private
< Date: Fri, 15 Nov 2024 13:33:08 GMT
< X-RateLimit-Limit: 60
< X-RateLimit-Remaining: 59
< Access-Control-Allow-Origin: *
< Alt-Svc: h3=":443"; ma=86400
< X-Cache-Status: MISS
< X-Powered-By: Defikon
< Strict-Transport-Security: max-age=31536000; includeSubDomains
< X-Frame-Options: SAMEORIGIN
<
* Leftovers after chunking: 11 bytes
* Connection #0 to host prahec.com left intact
*********************************************************************************************************
Many shared and private web hosting providers implement firewall configurations that often interfere with connectivity between players and streaming services. These measures are not intended to block legitimate traffic but are essential for hardening network security against hacking attempts. Firewalls play a crucial role in safeguarding servers and services, which is why hosting providers are obligated to use such protective measures.
For PawTunes, this can mean that the PHP or back-end service may fail to connect to your streaming server, especially if it operates on non-standard ports, such as 8000, which are commonly blocked by firewalls. This results in the player being unable to retrieve track information or any data from the streaming server.
This is a frequently asked question, and for those still seeking clarity, here’s the answer:
Most other players or scripts are client-side, meaning they rely on your computer to fetch the track information. Since your computer is making the request, it typically bypasses firewall restrictions on the web server. Your local machine usually doesn’t block ports used for such purposes, which is why these players appear unaffected.
However, PawTunes operates differently. To ensure the best performance and accuracy, the task of fetching track information is handled on the server-side. If your server lacks access to the required ports or host, the player cannot retrieve or display live track information (e.g., artist and title). This is why your web hosting environment must have the necessary port and host access enabled to allow PawTunes to function properly.
There is a potential solution using the Media Source API built into modern web browsers. This API could enable the player to parse the stream and extract metadata directly from the audio before playback, eliminating the need for server-side requests to fetch track information. Since most streaming servers support metadata retrieval through the Media Source API, this approach could become a versatile and robust solution for future versions of PawTunes. However, the current version of PawTunes does not include this functionality, as it is not yet supported by all major browsers.
While most of the control panel is self-explanatory and includes helpful descriptions and guidance, this section covers features and options that are not included in the control panel.
You might not know this, but the app can function without the control panel entirely. This means you can configure the player during the initial setup and then safely delete the control panel files located in the /panel folder. Once removed, the player will continue to work without any security risks or vulnerabilities to hacking attempts.
To be clear, this doesn’t imply that the control panel itself is insecure. In fact, it includes built-in security features such as authorization throttling and ban functionality. However, removing the control panel files eliminates any possibility—however remote—of someone attempting to disturb your player.
The PawTunes player receives regular updates to ensure compatibility with evolving browser developments, system updates, and other changes, keeping the player functioning as intended. Some updates also introduce new functionality, themes, and templates. Updates are seamlessly connected to the update service hosted at https://prahec.com.
Updates require a valid license, which you can obtain from the Envato purchases page. Please note that improper use of licensing may result in your license being revoked.
There are two ways to create custom color schemes for the player: manual mode and automatic mode.
// Imports (other required SCSS files)
@import "reset-browsers.scss";
// Configure SASS options (Colors, Animations, Fonts etc...)
$font-family: "Poppins", sans-serif;
$ease-out: cubic-bezier(0.25, 0.8, 0.25, 1);
$ease-in: cubic-bezier(0.55, 0, 0.55, 0.2);
// Other options
$base-font-size: .8rem; // Base font size
$font-bold: 400; // Bold font weight
$font-light: 300; // Light font weight
// Default accent color
$accent-color: #62a8ea !default;
$icon-fill: #919191 !default;
$player-height: 6.5rem;
html {
font-size: 16px;
}
body {
font-size: $base-font-size;
}
Both methods offer flexibility to customize the player's appearance to suit your branding and preferences. After modifying the variable $accent-color, proceed with the SCSS compilation process and save the new file under templates/{template_name}/custom/. Once the compilation is complete, your new color scheme will appear in the Color Scheme setting within the channel edit/add mode.
PawTunes was built from the ground up without dependencies and is capable of handling thousands of concurrent listeners. By default, caching is enabled using DISK, but for extremely high traffic, I recommend using Redis or Memcached. For localized deployments, the best caching configuration is APCu.
APCu is a simple in-memory (RAM) caching solution that operates with very low latency. However, it does not scale vertically, meaning it cannot be used across multiple servers. If you're deploying the player on multiple servers, APCu may not be suitable. Below is an example configuration, which needs to be manually set in inc/config/general.php:
<?php
return array(
// Other configs above
'cache' => array (
'mode' => 'disk', // Possible modes: apcu, memcache (diff PHP lib), memcached, redis
'path' => './data/cache', // Not used for APCu. Disk path, or server address/socket e.g. 127.0.0.1:11211
'extra' => [ // Add authentication or additional options, e.g., Memcached::OPT_HASH
Memcached::OPT_HASH, Memcached::HASH_MURMUR
]
)
);
Beyond this, PawTunes also allows for splitting the track info handler and artwork handler across multiple servers to distribute the load. If you need assistance with such a deployment, I’d be more than happy to help – for free! Feel free to contact me for support.
Additionally, the player and its files can be containerized using Docker and deployed as custom services in Kubernetes. However, this level of deployment is typically only required for handling tens of thousands of requests per second, a demand that most radio stations currently do not face as a bottleneck.
PawTunes is extremely developer-friendly, as it is written in JavaScript ES6 as a module, making it easy to extend. For example, you can replace the function responsible for connecting WebSockets or handling fetch requests with your own custom function or method to achieve entirely different functionality.
The module is designed so that you can remove the back-end entirely and integrate the front-end directly into your website using your own styles, classes, and stylesheets. While this customization is beyond the scope of this documentation, below you will find some of the public methods available in the PawTunes library.
/**
* Generate HTML for current playing song timer (resets on stop/play)
*/
public onAirTimer(): void {
...
this.writeText( '.onair .time', timer );
}
/**
* Starts interval to request track info from API or sets up websocket
* Can be overriden by a method that does what ever you want it to do
* This is called on connection state change, channel change and initial startup.
*
* @return {void}
*/
public async trackInfoInit(): Promise<void> {
// API Interval? Stop it.
if ( this.timers.trackInfo ) {
clearInterval( this.timers.trackInfo );
}
// Already connected to a web socket? Close it first.
if ( this.ws.isWebSocketActive() ) {
await this.ws.close();
}
// Invalid channel config?
if ( !this.channel || !this.channel.name ) {
return;
}
// Web Sockets - Azuracast
if ( this.channel.ws && this.channel.ws.url ) {
this.ws.connectToSocket( this.channel.ws.url, this.channel.ws.station );
return;
}
// Every other method
let pawTunesAPI = () => {
fetch( `${this.settings.api}?channel=${this.channel.name}` )
.then( response => {
if ( !response.ok ) {
throw new Error( 'Network response was not ok' );
}
return response.json();
} )
.then( data => {
this.handleOnAirResponse( data );
} )
.catch( error => {
console.error( 'There was a problem with the fetch operation:', error );
} );
}
pawTunesAPI();
this.timers.trackInfo = setInterval( pawTunesAPI, this.settings.refreshRate * 1000 )
}
/**
* Handles track information, works with API calls, web sockets or anything else.
* Updates player with new data if it has changed since last check.
*
* @param data
*/
public handleOnAirResponse( data: any ): void {}
/**
* Load artwork
*
* @param URL string|null
*/
public loadArtwork( URL: string | null ): void {}
/**
* Generate the artwork URL from setting
*/
public pawArtworkURL( artist: string, title: string ): string {}
You can also listen to various player events using the .on
function in PawTunes. It works as follows:
const pawtunes = new PawTunes({ /* OPTIONS */});
pawtunes.on('track.change', (data) => {
console.log('Track changed to:', data);
});
This allows you to easily attach custom functionality to specific player events.
PawTunes triggers HTML5Audio events along with a few custom events, such as track.info
. See the list below:
Event | Description |
---|---|
status.change | Triggers when a player status change is detected (e.g., volume, play, stop). |
track.change | Triggers when a track change is detected. |
history.change | Fires every time the History DOM is updated. |
channel.change | Triggers when a channel change is detected. |
stream.change | Triggers when user selects a different stream from the dropdown/options |
theme.change | Fires when a channel change triggers a theme or color scheme update. |
status | Fires when the browser detects a change in the internet connection state. |
stopped | HTML5Audio doesn't have "stopped" event. PawTunes emits the event when playback is stopped and reloaded. |
If you need more detailed information on how to use the PawTunes library, feel free to reach out! I’m more than happy to provide additional details about the code, module, methods, and more.
This player is custom-coded with minimal dependencies, no bulky frameworks or outdated technologies that waste space and hinder execution speed. For example, the main JavaScript file, which handles most of the functionality, is approximately 31KB minified and only 10KB gzipped.
The control panel, however, leverages a few excellent open-source projects, to which I owe immense credit. Without them, my work would have been far more challenging and likely less refined.
I’d also like to extend my gratitude to StreamingPulse for generously providing a free stream for the demos.