Meet PawTunes

First, thank you for downloading PawTunes 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.

Why PawTunes?

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.

Track Information

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.

Artwork Library

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.

Installation Requirements

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.

Installation

To begin the installation, first, download the installable ZIP file from the** ⬇️ downloads page** .The ZIP file contains release files. If you prefer, you can also use Docker Image available here: https://hub.docker.com/r/jackyprahec/pawtunes. Instructions to install using Docker, are available on Docker Hub.

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:

  • /inc/config – 755
  • /data/cache – 755
  • /data/images – 755
  • /data/logs – 755
  • /data/updates – 755
  • /templates/{template_name} – 755
  • /panel/views/cache - 755 (only if you are using PawTunes Control Panel)

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 .

Configuration

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.

Single Channel Configuration

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.

Multi-stream option

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.

Reset Password

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',  
            ],  
    ];

General Settings

Most general player options are self-explanatory and are also explained on the settings page. However, there are a few important notes to consider:

  • Site Title vs. Player Title: The site title is used for the website's sharing OGP tags, while the player title is used across the control panel and the player itself. The player title is more significant than the site title in terms of functionality.
  • Cache Path: This is a critical configuration without which many features of the player will not work properly.
  • Autoplay: Autoplay is heavily restricted in many modern browsers due to past abuses. For autoplay to work, your website needs to establish good engagement with the visitor. You can read more about autoplay restrictions in this article

Live Information

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.

Custom Track Parsing: Streams usually deliver raw titles in the Artist - Title format, but not every station follows that convention. Under Settings → Live Information, you can customize the regular expression used to split raw stream titles into artist and title (it uses the named groups artist and title). A reset button restores the default pattern. This parsing applies to all track-info methods.

Live Information - Azura cast

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.

Live Information - Custom

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"
        }
    ]
}

Live Information - Web Sockets (Custom Implementation)

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

Live Information - External API

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.

Get list of Channels

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>
Channel Track Information

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}` );  

} );

Artwork Settings

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.

  • Artist Artwork Mode: This is the recommended option as it requires fewer images, and they can be shared across multiple tracks, reducing the need for frequent API calls.
  • Track Artwork Mode: For advanced users, this mode fetches unique artwork for every track played on your radio station, offering more variety but increasing resource usage.

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.

Artwork Sources

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.

If you have any suggestions for additional artwork sources, I’d be happy to hear about them and consider incorporating them in future updates.

Artwork Manager & Custom Overrides

Many users don't realize that artwork doesn't have to come from an API at all - you can supply your own images, and they always take priority over every API source. All of this is available on the Tools page:

  • Upload with crop: Upload an image for a specific artist (name it ARTIST) or a specific track (ARTIST - TITLE). A draggable crop tool ensures the image fits the player perfectly. This is the ideal way to fix wrong or low-quality covers returned by the APIs.
  • Artwork Manager: Lists all your custom overrides with hover preview, replace, and delete actions. This is also where you can replace the default artwork (shown when nothing else is found - it cannot be deleted, only replaced).
  • Bulk import: Import large amounts of images at once, either from a folder on the server or directly from an FTP URL (ftp://user:pass@host/path). Files are automatically renamed and resized during import - perfect for stations that already maintain a cover library.
  • Artwork search tester: Query your configured artwork sources for any artist exactly like the player would, with an optional "ignore cache" mode - great for verifying API keys and priorities without waiting for a track to play.

All images live in the data/images/ folder, so they can be backed up, or shared between multiple player installations.

Languages & Translations

PawTunes ships with several ready-made player translations (English, German, French, Italian, and Slovenian), and the control panel includes a full translation manager under the Language page:

  • Add a new translation by picking from a catalog of ~130 languages, then edit any of the player's UI strings individually - each string has a reset-to-default option. English is built-in and cannot be deleted.
  • With the Multi-language option enabled (Settings page), the player automatically matches each visitor's browser language and falls back to the configured default language.
  • A specific language can also be forced per embed using the ?language=de URL parameter (see URL Parameters).
  • When directly embedding the player without a back-end, the same strings can be passed via the language key of the settings object.

Embedding & URL Parameters

Embed Codes

The control panel's home page (Player) generates ready-to-use embed codes for your website - either as a popup window or an iframe, complete with a live preview. The generator also lets you customize the embed: player size, initial channel, language, template, autoplay, and HTTP/HTTPS. All of these are simply URL parameters, so you can craft the URLs by hand as well.

URL Parameters

The player endpoint accepts several URL parameters that can be combined freely. These make it possible to run multiple differently-configured embeds from a single installation - no configuration changes required:

Parameter Example Description
#ChannelName (hash) /player/#Rock%20FM Deep-link directly to a channel. Changing the hash while the player is open switches the channel live.
?template=<name> ?template=modern Render a different installed template on the fly, without changing the configured default.
?language=<code> ?language=de Force the player UI language (see Languages & Translations).
?autoplay=false Disable autoplay for a specific embed even when it is enabled globally. (Autoplay cannot be force-enabled this way.)
?channel=<name> Now-playing JSON API - see External API.
?channel=all JSON list of all channels - see External API.
?channel=<name>&playlist&player=<type> &player=winamp Download a playlist file for external players - see Playlist Links.
?artwork&artist=<a>&title=<t> Artwork endpoint - resolves, caches, and redirects to the cover image (used by lazy artwork loading, also handy for your own website).

For example: https://your-domain.com/player/?template=modern&language=de&autoplay=false#Rock%20FM

Some listeners prefer their favorite desktop or mobile app over a web player. The player endpoint can generate playlist files on the fly, containing every stream quality and format of a channel:

  • ?channel=<name>&playlist&player=winampListen.pls (also used for VLC and any other player)
  • ?channel=<name>&playlist&player=wmpListen.asx (Windows Media Player)
  • ?channel=<name>&playlist&player=quicktimeListen.m3u

The AIO Radio and HTML5 Player templates display these as ready-made download buttons (Winamp, WMP, QuickTime, VLC) that always follow the currently selected channel.

Features You Might Have Missed

A few features are built-in and work automatically - easy to overlook, but worth knowing about:

  • Lock screen & Bluetooth controls: The player integrates with the browser's Media Session API. The current artist, title, and artwork appear on phone lock screens, smartwatches, and car displays, and hardware/OS play & pause buttons control the player.
  • Listener preferences are remembered: The volume, last played channel, and the selected stream quality (per channel) are stored in the browser and restored on the next visit.
  • Network resilience: When the connection drops, the player stops gracefully; as soon as the network returns, it reconnects (including Web Sockets) and resumes playback automatically.
  • True stop, not pause: Pressing stop tears down the stream connection entirely instead of pausing. This saves the listener's bandwidth and your streaming server's connection slots - and when playback resumes, it continues live rather than from a stale buffer.
  • Multiple formats per stream: Each quality group can contain multiple encodings (e.g., MP3 + AAC). The player automatically picks the first format the browser can play, so you can serve modern codecs with a safe fallback.
  • Template extras: Each template ships additional options, available via Settings → Template Options. Depending on the template these include: opening a song search/purchase page (e.g., Apple Music) when the artwork is clicked, showing channel logos instead of names, an audio spectrum visualizer, locking channel/quality switching (kiosk-style), hiding the blurred artwork background, playlist icon sizes, sharing buttons, and more.
  • Dynamic window title: When enabled, the browser tab shows the currently playing track (Artist - Title | Your Title) on every song change - a subtle but effective way to keep listeners engaged in background tabs.
  • Per-channel branding: Every channel can have its own logo (swapped in the player header on channel change) in addition to its own color scheme.

Debugging

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.

Debugging Streams

When using the stream debugging tool under the Tools page, there are two possible responses:

  1. 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.

  2. 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\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  
*********************************************************************************************************

Log Files

With debugging set to "Logging only (recommended)" or "Enabled", the player writes errors to data/logs/player_errors.log, while control panel errors go to data/logs/panel_errors.log. Whenever an error log exists, the control panel displays a warning banner with a direct link to the built-in log viewer (panel/?page=logs), where the log can be inspected and deleted.

For production use, keep debugging on log-only mode - you retain full error visibility without any performance impact or error details leaking to visitors.

Connectivity & Live Information Issues

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.

Why Other Players Don’t Face the Same Issue

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.

Future Solutions

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.

Advanced Configuration

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.

Updates

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.

The Updates page in the control panel shows the latest available version, its release date, and the full changelog; a notification badge also appears in the panel navigation whenever a new version is available. From there, a one-click update downloads and installs the new release with a live progress bar.

The updater preserves your configuration during the process - specifically inc/config/general.php, inc/config/channels.php, and your default artwork (data/images/default.png). Files that are not part of the release, such as custom color schemes in templates/<template>/custom/ or your artwork library in data/images/, are left untouched. Everything else is overwritten, so keep any manual template modifications in separate files.

Custom Color Schemes

There are two ways to create custom color schemes for the player: manual mode and automatic mode.

  • Manual Mode: This method requires accessing the template folder and locating the SCSS files within the stylesheets directory. These SCSS files can be edited and then compiled using an SCSS compiler. Below is an example of an SCSS file (only the top portion is shown for demonstration purposes)
// 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;  
}

  • Automatic Mode: Available through the Tools page, this mode simplifies the process by automatically generating custom color schemes without requiring manual compilation.

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.

Performance & Cloud Deployment

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.

Developers

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. A complete walk-through of this approach is available in the Direct Embedding (No iFrame) section below. First, here are 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.

List of Available Events

PawTunes triggers all 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.

Direct Embedding (No iFrame)

Because the PawTunes front-end is a standalone JavaScript (ES6) module, the player can be integrated into any website directly - without an <iframe> and even without the PHP back-end. This is exactly how the "DEMO – Direct Embedding (NO iFrame)" on https://prahec.com/pawtunes works. That demo uses Azura Cast, because Azura Cast provides all the essential information for the stream (track, artwork, and history) directly to the visitor's browser, so no back-end is required at all.

To deploy the player this way, you need two things:

  1. The player assets, hosted anywhere the browser can reach them. The easiest option is to reference the files of an existing PawTunes installation:
    • assets/js/pawtunes.min.js - the core player module
    • templates/pawtunes/js/pawtunes-tpl.min.js - the template logic (each template ships its own module)
    • templates/pawtunes/css/pawtunes.css - the template stylesheet
    • a default artwork image
  2. A track-info source that is accessible from the visitor's browser - either Azura Cast Web Sockets (recommended, used by the demo) or the Player API of a PawTunes installation.

Step 1 - Copy the markup. Open templates/pawtunes/template.html and copy the entire <div class="pawtunes"> … </div> block into your page. Replace the {{$...}} placeholders ({{$loading_message}}, {{$default_artwork}}, {{$artist_default}}, {{$title_default}}, {{$song_history}}, the various {{$ui_*}} titles, …) with your own texts and image URLs - these are normally filled in by the back-end template engine.

Step 2 - Load the stylesheet.

<link href="https://radio.example.com/player/templates/pawtunes/css/pawtunes.css" rel="stylesheet" type="text/css">

Step 3 - Load the modules and configure the player. The settings object passed to the player is the same JSON that index.php normally injects into the template - you simply write it by hand. The example below is adapted from the live demo:

<script type="module">

    import PawTunesTpl from "https://radio.example.com/player/templates/pawtunes/js/pawtunes-tpl.min.js";
    import PawPlayer from "https://radio.example.com/player/assets/js/pawtunes.min.js";

    const player = new PawPlayer(".pawtunes", {
        "channels"     : [
            {
                "name"   : "My Radio",
                "logo"   : null,
                "streams": {
                    "High Quality (320K)": { "mp3": "https://radio.example.com/listen/station/radio.320K.mp3" },
                    "Low Quality (96K)"  : { "mp3": "https://radio.example.com/listen/station/radio.96K.mp3" }
                },
                "ws"     : {
                    "method"         : "azuracast",
                    "station"        : "station_shortcode",
                    "url"            : "wss://radio.example.com/api/live/nowplaying/websocket",
                    "history"        : true,
                    "useRemoteCovers": true
                }
            }
        ],
        "analytics"    : false,
        "defaults"     : {
            "channel"       : "My Radio",
            "default_volume": 75,
            "autoplay"      : false
        },
        "dynamicTitle" : false,
        "prefix"       : "my_website_player",
        "history"      : true,
        "historyMaxLen": 10,
        "refreshRate"  : 10,
        "template"     : "pawtunes",
        "tpl"          : {
            "songSearch"        : "https://music.apple.com/us/search?term={query}",
            "songSearchEnable"  : true,
            "useChannelLogos"   : false,
            "disableSettings"   : false,
            "hideBlurredArtwork": false
        },
        "title"        : "My Radio",
        "trackInfo"    : {
            "artistMaxLen"    : 24,
            "titleMaxLen"     : 28,
            "lazyLoadArtworks": false,
            "default"         : {
                "artist" : "Various Artists",
                "title"  : "Unknown Track",
                "artwork": "https://your-website.com/images/default-artwork.png"
            }
        }
    });

    new PawTunesTpl(player);

</script>

A few notes about the settings:

  • prefix: The player persists the volume and the last used channel/stream in localStorage. Set a unique prefix so your embedded player doesn't clash with other players on the same domain.
  • ws: The Azura Cast Web Socket configuration. station is your Azura Cast station shortcode, and url follows the pattern wss://<azuracast-host>/api/live/nowplaying/websocket. With useRemoteCovers: true, artwork is served straight from Azura Cast - this is what makes a fully back-end-less deployment possible.
  • tpl: Template-specific extras; the available keys differ per template (see the extra section of each template's manifest.json).

Using the Player API instead of Web Sockets

If your streaming server does not expose now-playing data to the browser (Shoutcast, Icecast, SAM, CentovaCast, …), keep a PawTunes back-end running - it can live on a completely different domain than the page embedding the player - and point the front-end to its index URL with the api setting. In that case, omit the ws block from the channel:

const player = new PawPlayer(".pawtunes", {
    "api"     : "https://radio.example.com/player/",
    "channels": [
        {
            "name"   : "My Radio",
            "logo"   : null,
            "streams": { "Default": { "mp3": "https://streaming.example.com:8000/;" } }
        }
    ],
    // ... same as above
});

The player will then request GET {api}?channel={channel name} every refreshRate seconds - the same endpoint described under External API. The channel name must match a channel configured in that PawTunes installation.

You can also point api at your own back-end. The endpoint just needs to return this JSON shape (time is a Unix timestamp in milliseconds):

{
    "artist": "David Guetta",
    "title": "Lonely Is The Night",
    "artwork": "https://example.com/images/lonely-is-the-night.png",
    "history": [
        { 
            "artist": "Air Supply", 
            "title": "Lonely Is The Night", 
            "artwork": "https://...", 
"time": 1727431331012 }
    ]
}

Artworks without a Back-end

As mentioned on the demo page, artworks must be provided from external sources when the player runs without its own back-end:

  • Azura Cast Web Sockets: set useRemoteCovers: true and Azura Cast delivers the covers itself.
  • Player API of a PawTunes installation: the artwork URLs returned by the API are relative to that installation, so they will not resolve on your domain. Instead, let the front-end build absolute artwork URLs by enabling lazy artworks in your embed settings:
"trackInfo": {
    "lazyLoadArtworks": true,
    "lazyLoadURL"     : "https://radio.example.com/player/index.php?artwork&artist={ARTIST}&title={TITLE}",
    "default"         : {
        "artist" : "Various Artists",
        "title"  : "Unknown Track",
        "artwork": "https://your-website.com/images/default-artwork.png"
    }
}

The artwork endpoint responds with an image redirect, so it works cross-domain without any CORS configuration. Always use an absolute URL for trackInfo.default.artwork as well - if an artwork fails to load, the player falls back to it.

Credits & Copyrights©

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.