Javascript library for APM html5 audio players
npm install apm-html5-playerA DOM-based javascript UI library for HTML5 audio, created for use on American Public Media and Minnesota Public Radio's websites.
Supports live streams and archived playback.
The library was designed for backwards compatibility with older javascript build systems, or even no build system, so it supports usage in a tag, requirejs, commonjs, and ES6 imports.
- ES6 Import
- CommonJS Syntax
- Require.js Syntax
- Script tag
- DOM Structure
- Inner Elements
- DOM Example
- Audio Formats
- Audio object array
- Audio filename array
- Audio string
- Player State
- Player State Classes
- Player State Example
- JS Setup
- ES6/Require.js Setup
- Global Setup
- Playlists
- Linting
- Running a Build
- Testing
As of version 1.0.0, this library has no dependencies for usage. In previous versions, jQuery or a jQuery equivalent (such as Zepto) would need to be used alongside it.
There are several ways to install APM Player on your site.
The best supported method of installation going forward will be through NPM. To install in your project:
``sh`
npm install --save apm-html5-player
or to use yarn:
`sh`
yarn add apm-html5-player
Some legacy apps don't use npm, so we can use bower to manage the version of the player.
The package isn't registered with bower, so we have to reference the git url. If the repo is hosted on Gitlab (or somewhere else), change the url below to the appropriate repo url.
Add the package to your bower.json file:
`javascript
// in bower.json
"dependencies": {
"apm-html5-player": "https://github.com/APMG/apm-html5-player.git#1.0.0",
}
`
Change the version number (after the #) to the appropriate version you need.
Then run:
`sh`
bower install
The easiest way to include this in modern javascript, assuming you are using something like Webpack and Babel, is to use an import statement.
The library uses named exports for all modules.
To import the player module:
`javascript`
import { Player } from 'apm-html5-player';
If you need the player with the analytics plugin:
`javascript`
import { Player, AudioAnalytics } from 'apm-html5-player';
You can also use CommonJS require() functions to import the named modules like this:
`javascript`
var Player = require('apm-html5-player').Player;
var AudioAnalytics = require('apm-html5-player').AudioAnalytics;
If using Require.js, this is the preferred syntax because it's easier to understand. To use a named package as demonstrated in the example above (the 'apm-html5-player' string), you'll need to set up the module in Require.js's paths config. Alternatively it can be referenced with a relative file path in the require() function.
If your app uses old-style require.js syntax for module importing, you can access the named modules like this, assuming Require.js's paths is configured:
`javascript
// Creates a new Player and AudioAnalytics
requirejs(['player'], function(ApmPlayer){
// Get the DOM element
var playerElement = document.getElementById('js-player');
// Invoke the constructors
var player = new ApmPlayer.player(playerElement);
var analytics = new ApmPlayer.AudioAnalytics();
// Initialize the player
player.init();
}
`
This script can be used in the global namespace as well. Assuming you include your scripts in the bottom of your html document:
`html
...
`
The script is then accessible in the global namespace in your javascript by using window.ApmPlayer
This library is DOM-based (i.e. it doesn't use something like props in React, but stores its configuration in the DOM).
It is invoked on a particular DOM element and expects various child elements to exist within that DOM element.
At a minimum, the library needs a containing element and an
`html`
#### Inner Elements
Other DOM elements used by the library are selected by the following classes:
- js-player-play: The play/pause button. To change state of the button (to alternate between a play and pause icon), you can put the appropriate icons inside the button and show/hide based on the CSS state class applied to js-player. Can be multiple elements.js-player-timeline
- : The outer container of the scrubber. Must be a single element.js-player-progress
- : The element indicating the time elapsed inside the scrubber. Must be contained inside js-player-timeline. Must be a single element.js-player-buffered
- : The element indicating the loaded audio buffers. Must be contained inside js-player-timeline and should not contain any elements (or they will be overwritten). Must be a single element.js-player-volume
- : The outer container of the volume bar. Must be a single element.js-player-volume-current
- : The element indicating the current volume. Must be contained inside js-player-volume. Must be a single element.js-player-mute
- : The audio mute button. To change state of the button (to alternate between a mute and unmute icon), you can put the appropriate icons inside the button and show/hide based on the CSS state class applied to js-player. Must be a single element.js-player-duration
- : Displays the total length (in hh:mm:ss) of the audio which is currently loaded. Must be a single element.js-player-currentTime
- : Displays the current time of the currently loaded audio. Must be a single element.
#### DOM Example
The actual structure of the DOM is flexible, allowing for lots of different possible layouts, but here's an example of what it could look like (taken from The Current and abbreviated):
`html`
Listen to The Current
Notice the additional classes used on elements with js- classes. The additional classes should be used for styling, not the js- classes, as those are meant to be only javascript hooks.
URLs to the audio files should be supplied to the data-src attribute on the main container element (.js-player in this example).
Once playback is initiated, the library will create the appropriate element(s) inside the
The player can handle multiple audio sources, allowing the browser to use fallback formats if the OS or browser don't have the preferred codecs.
#### Audio object array
The most explicit, and generally preferred, way of defining the audio used in the player is by supplying an array with objects that detail the url and type of audio.
This is preferred if you know what formats your audio use so that the library doesn't have to guess based on the file extension.
Assume a JSON object that looks like this:
`json`
[
{
"url": "https://example.com/my-audio.aac",
"type": "audio/aac"
},
{
"url": "https://example.com/my-audio.mp3",
"type": "audio/mpeg"
}
]
This provides a file in the AAC codec, which should use the audio/aac file container type as the preferred format, and the browser will fall back to the MP3 file (type audio/mpeg) if it can't play AAC audio.
To apply this to the DOM it should look like this. The JSON can use single quotes here instead of double quotes if preferred:
`html`
For reference, once this is parsed by the library after playback has been initiated, the
`html`
#### Audio filename array
If your data source that provides the audio urls doesn't provide information about the codec, you can just pass an array of url strings and the library will try to figure out the MIME types based on the filename:
`html`
Resulting in:
`html`
If the library can't tell what type a file is from the filename, it will omit the type attribute from that element.
The browser may still be able to play that audio file, but it might not work correctly.
#### Audio string
The library also accepts a simple string as the data-src:
`html`
Resulting in:
`html`
The overall state of the player is communicated in the DOM by is-* classes on the main DOM element (the js-player element).is-*
Any visual state changes of the player (alternating between the play and pause icons, for example) should use CSS to inherit the class.
An exception to this rule of state classes is that the progress bar (scrubber) and the volume slider are set by javascript-driven inline styles to determine their width/height.
#### Player State Classes
All possible player state classes:
- is-playing: Added to the container after audio playback has been initiated or unpaused. Removed when audio is paused. If finite-length audio reches its end, this class is removed.is-paused
- : Added to the container when audio is paused. Removed when audio is playing. If the audio is a live stream (infinite length), this class is removed when the user "pauses" the stream because the audio is unloaded instead of just paused. This also means that when finite-length audio reaches its end, this class does not get added.is-loading
- : Added to the container when the browser is loading the audio file. Removed once playback has begun.is-muted
- : Added to the container if audio is muted (volume == 0). Removed if volume is greater than 0.
#### Player State Example
For example, to alternate between the play and pause icons, you might do something like this in the CSS, assuming the DOM example above:
`css
/ The default state, shows the play icon /
.player-play { display: block; }
/ The default state, hides the pause icon /
.player-pause { display: none; }
/ Using the is-playing class applied to containing .js-player to show the pause icon and hide the play icon /
.is-playing .player-play { display: none; }
.is-playing .player-pause { display: block; }
`
Invoking the library in your app is fairly straightforward, and can be done in a few different ways depending on how you included the script in your app.
#### ES6/Require.js Setup
Assuming you imported or required the player using the existing exported name Player into your js file, invoke the script on your DOM element:
`javascript
// your custom js file
// The DOM element container
const playerElement = document.querySelector('.js-player');
// Create new instance of the Player class
const player = new Player(playerElement);
// Don't forget to initialize
player.init();
`
#### Global Setup
If you included the player library in your project in a