J1 MultiPlayer (formerly: VideoPlayer) is a complete player engine built on top of VideoJS. It gives every audio and video on your site the same good-looking, skinnable player, and it adds a full playlist on top: a searchable, sortable list of media items that the viewer can play, rate, and continue from where they left off. It plays native audio files (mp3) and two kinds of video: native videos (mp4) and YouTube videos.
You do not need to write any JavaScript to use it. You describe your players in a few simple YAML files and J1 builds the HTML for you. You can put multiple players on one page, and each one keeps its own separate playlist and its own settings.
30-60 Minutes to read
| MultiPlayer is built on the VideoJS module of the J1 Template. Everything the player engine itself can do — options, components, methods, events and plugins — is described in the manual VideoJS V8. This manual describes the MultiPlayer module: how it is configured, what a playlist is, and what the viewer sees on the page. |
Overview
The J1 MultiPlayer module turns a few lines of YAML into a complete media player with a playlist. One player can hold many media items, and one page can hold many players.
Key features:
- Three kinds of media
-
Native audio (
mp3), native video (mp4), and YouTube videos. A single playlist can mix all three. - One playlist per player
-
Every player owns a searchable, sortable list of media items. The viewer can play an item, rate it, edit its details, delete it, and — for plain files — download it.
- Remembers the viewer
-
Ratings, the position a viewer stopped at, and the small display preferences (list or cards, merge on or off, loop on or off) are stored in the browser and are still there on the next visit.
- No JavaScript needed
-
Everything is configured in YAML. J1 generates the HTML of the player, the playlist panel and the edit panel for you.
- Many players per page
-
Each player has its own id, its own playlist, and its own settings. Nothing leaks from one player to another.
- Built on VideoJS
-
The playback itself is done by VideoJS, so the player looks and behaves the same for every kind of media and can use the VideoJS plugins shipped with the template.
The building blocks
Before diving into the settings, it helps to know the five pieces that make up a J1 MultiPlayer. You never touch most of them directly, but knowing they exist makes every later section easier to follow.
| Part | Description |
|---|---|
| Three small text files describe your players: what they are called, which playlist they load, and how they look and behave. This is the part you edit. See Configuration. |
| A small piece of J1 JavaScript ( |
| The module itself ( |
| A small modern web component ( |
| Each player keeps its playlist in the browser’s local storage. That is why a viewer’s ratings and stop positions are still there the next time the page is opened. Every player has its own store; see Each player has its own storage. |
Audio, native video and YouTube
A single playlist can mix all three kinds of media. The module decides what to do by looking at the item itself — you never set a "mode" anywhere.
| Only plain files ( |
- Native audio (
mp3) -
An audio file that you host on your own web server. The address goes into the item’s
srcfield. The player shows its poster image while the audio plays. - Native video (
mp4) -
A video file that you host yourself. The address goes into the item’s
srcfield. If no poster image is given, MultiPlayer can create one for you from a frame of the video (see Poster images). - YouTube video
-
A video that lives on YouTube. You give the player the YouTube link in the item’s
videoLinkfield and it plays the video through VideoJS. The poster image is taken from YouTube automatically.
| Native and YouTube media are configured in two different blocks: YouTube playback reads its settings from |
Several players on a page
You can place as many players on a page as you like. Every player has its own id (a short name such as player_1), its own playlist, and its own settings. A media item you add to player_1 never shows up in player_2, and a setting you change for one player never changes another.
Internally each player is a separate instance of the module, created through a factory function — exactly the same idea VideoJS uses for its own players. This is explained in One instance per player.
Configuration
Almost everything you will ever change lives in YAML configuration files. YAML is a plain-text format for settings: a name, a colon, and a value. You do not need to know any programming to edit it.
Configuration files
Settings come from three files that are merged together in order. Later files win, so you only have to write down what you want to change.
| The merge chain is: defaults → overridden by global settings → overridden by per-player settings. The merge works per key: a per-player |
| File | What it is for |
|---|---|
| The defaults. Every possible option with a sensible value and a comment explaining it. You normally leave this file alone and read it as a reference. |
| Your global settings. Anything you put here overrides the defaults for all players. This is where you switch the module on with |
| Your per-player settings. This file lists each player separately, so you can give every player its own id, its own playlist, and its own look. |
| Keep the nesting of the keys in your files exactly as in the defaults file. A key placed at the wrong level is silently ignored. One exception is worth knowing: lists (arrays) are replaced, not merged. If the defaults offer |
General options
These live at the top of the settings: block and control the module as a whole.
| Key | Type | Default | Description |
|---|---|---|---|
| boolean |
| Master on/off switch for the module. Set it to |
| string |
| The folder where your ready-made playlist files live. A short file name in a |
| string |
| The folder the module reads its generated HTML from. |
| boolean |
| Hide the placeholder of a player that is switched off ( |
UI elements
The ui_elements: block controls which parts of a player are shown at all. Each key stands for one visible element and takes true (show it) or false (hide it).
All keys follow the normal chain: set them in the defaults for everyone, in your global multiPlayer.yml for all players of your site, or inside one player’s entry in multiPlayer_control.yml for that player only. The most specific setting wins.
Header buttons
| Key | Type | Default | Description |
|---|---|---|---|
| boolean |
| The Show/Hide Playlist button on the left of the header bar. When set to |
| boolean |
| The Manage Playlists button on the right of the header bar. When set to |
Playlist panel controls
| Key | Type | Default | Description |
|---|---|---|---|
| boolean |
| The search box above the playlist. |
| boolean |
| The sort control of the playlist. |
| boolean |
| The switch that flips the playlist between the list and the cards layout. |
| boolean |
| The merge switch. When on, importing another playlist adds its items to the current one instead of replacing it. |
| boolean |
| The loop switch that repeats the playlist from the top when the last item finishes. |
| boolean |
| The audio only switch for YouTube items. See Audio only. |
Item action buttons
These keys control the small buttons drawn on every item of the playlist.
| Key | Type | Default | Description |
|---|---|---|---|
| boolean |
| The rate button that opens the star-rating dialog of one item. |
| boolean |
| The edit button that opens the Edit Media Settings dialog of one item. |
| boolean |
| The delete button that removes one item from the playlist. |
| boolean |
| The download button of one item. See Downloading media. |
| boolean |
| The Download button in the playlist title bar that exports every downloadable item at once. |
| The playlist-panel controls also manage themselves: they hide automatically while the playlist is empty and appear as soon as it has content. Some are even more careful:
|
Playlist options
The playlist: block controls the playlist panel that sits under each player.
settings:
playlist:
enabled: true
type: list
loop:
enabled: false
pip: false
cards:
perRow: 2| Key | Type | Default | Description |
|---|---|---|---|
| boolean |
| Show the playlist panel. Turn it off for a plain single-media player. |
| boolean |
| Close the playlist panel automatically as soon as an item starts playing. |
| string |
| The starting layout of the playlist: |
| number |
| How many cards to show per row in cards mode. Each player can have its own value. |
| boolean |
| Repeat the playlist from the top when the last item has finished. |
| boolean |
| Allow picture-in-picture: a small floating video window that keeps playing while the viewer scrolls or switches tabs. |
| array |
| A list of ready-made playlist files loaded into this player on page load. See Preloading a playlist. |
| number |
| How often the module retries to load the first item of a preloaded playlist on a first visit while the files are still arriving. Each attempt waits 250 milliseconds. |
Player options (videoJS)
The videoJS: block controls the player engine itself: how an item starts, how fast it can play, and how poster (preview) images are made.
Basic player options
| Key | Type | Default | Description |
|---|---|---|---|
| boolean |
| Start playing as soon as an item is loaded. |
| boolean |
| Let the browser fetch the media ahead of time so playback starts quickly. |
| boolean |
| Hide the VideoJS control bar. Only useful for a player that is driven completely from the playlist. |
| boolean |
| Offer a speed menu on the player. |
| array |
| The speeds available in that menu ( |
players.native
Settings for native media — your own mp3 and mp4 files.
| Key | Type | Default | Description |
|---|---|---|---|
| boolean |
| Play as soon as the media is ready. |
| boolean |
| Show the player’s control bar. |
| boolean |
| Make the player responsive so it fills the width of its container. |
| string |
| How eagerly to load the media: |
| boolean |
| On iPhones, play inside the page instead of forcing full screen. |
| string |
| The image shown before an item plays, when no other poster is available. |
| array |
| The order in which file types are tried when you host the same media in several formats. |
players.youtube
Settings for YouTube playback. Most keys are the official YouTube IFrame API parameters. They are preset so the YouTube player behaves like a native VideoJS player — normally you never change them.
| Key | Type | Default | Description |
|---|---|---|---|
| number |
| Do not start playing automatically ( |
| number |
| Captions (subtitles) are initially hidden. MultiPlayer always forces this value to |
| number |
| Hide the YouTube controls. The VideoJS control bar is used instead. |
| number |
| Disable the YouTube keyboard control. Keyboard support comes from the J1 hotKeys plugin instead. |
| number |
| Allow VideoJS to control the YouTube player. Required, do not change. |
| number |
| Hide the YouTube fullscreen button. Fullscreen is handled by VideoJS. |
| number |
| Video annotations are not shown. |
| number |
| Do not restart the video automatically when it ends. |
| number |
| Reduce the YouTube branding (logo) in the player. |
| number |
| Do not show related videos from other channels when playback ends. |
| number |
| Do not show the video title and uploader before playback starts. |
| boolean |
| Make the player responsive so it fills the width of its container. |
| boolean |
| Run the embed from the privacy-friendly host |
| string |
| Fallback poster image, used if no poster can be loaded from YouTube. |
| string |
| Name of the poster image taken from YouTube. Posters are loaded from the cookieless image host |
| Keeping |
Automatic poster images
A poster is the still image shown before an item plays. For native media, MultiPlayer can create the poster for you by grabbing a frame from the start of the video. This is controlled by videoJS.poster.autoGenerate.
| For YouTube items you never need this: the poster is fetched straight from YouTube’s own thumbnail service. |
| Key | Type | Default | Description |
|---|---|---|---|
| boolean |
| Turn automatic poster creation on or off. |
| number |
| Which second of the video to grab the frame from. If this is |
| number |
| A percentage of the video’s length to use as the capture point (used only when |
| number |
| The poster is shrunk to at most this many pixels wide (the shape is kept). |
| string |
| The image type to save: |
| number |
| Image quality from |
Plugin options
VideoJS gains extra buttons through small plugins. MultiPlayer ships a handful, switched on and off under videoJS.plugins:. What each plugin does in detail is described in the VideoJS manual.
| Plugin | What it does |
|---|---|
| The playlist engine. |
| Adds skip forward and skip back buttons around the play button. |
| Adds next and previous buttons to jump between playlist items. |
| Adds zoom, move and rotate controls for the video image. |
| Keyboard shortcuts (for example the arrow keys to seek). Off by default. |
| When you place several players on one page, set |
Per-player settings
The multiPlayer_control.yml file is where you actually create players. It holds a list of players, and each entry describes one player. Keys you do not set fall back to the global settings and then to the defaults.
| Key | Description |
|---|---|
| Whether this player is built at all. |
| The player’s unique name (for example |
| A human-friendly label for the player. |
| A list of playlist files to load into this player on page load. See Preloading a playlist. |
| Show or hide this player’s controls, for example |
| Any option from the sections above, applied to this player only. |
| Per-player settings really are per-player. Every player resolves its own |
A control file with one fully customised player looks like this:
settings:
players:
- player:
enabled: true
id: player_tiny_desk_concerts
description: NPR Tiny Desk Concerts
# hide the edit button for this player only
ui_elements:
edit_playlist_button: false
# this player gets its own speed menu
videoJS:
autoStart: true
playbackRates:
enabled: true
values: [ 0.5, 1, 1.5, 2 ]
playlist:
preload: [ tinydesk-concerts.json ]
cards:
perRow: 4Placing a player on a page
A player is placed on a content page with the AsciiDoc block macro multiPlayer::, followed by the id of the player you configured:
.The Art Of Loving (2026)
multiplayer::player_olivia_dean_video[role="mb-4"]The macro writes an empty placeholder into the page; the adapter loads the player’s HTML into it and starts the module.
| The id used in the macro must match the |
Playlists and media items
A playlist is simply a list of items, and each item is a small block of information about one audio file, one video file, or one YouTube video.
Media item fields
When you write a playlist file by hand, or read one that was exported, this is the shape of each item. Only videoId is truly required; every other field has a sensible default and can be left out.
Identity and description
| Field | Type | Description |
|---|---|---|
| text | Required. A unique id for this item. For YouTube this is usually the video’s id; for native audio and video any unique string works. |
| text | The title, shown in the playlist and in the player header while the item plays. |
| text | The channel, artist or author name. |
| text | A longer description. It is searchable. |
| text | A category label used for grouping and search. |
| list | A list of tag words, for example |
Where the media lives
| Field | Type | Description |
|---|---|---|
| text | The media’s MIME type, for example |
| text | The address of the native audio or video file. Leave empty for YouTube items. |
| text | The canonical link. For YouTube this is the YouTube URL; for native media it is usually the same as |
| text | An optional link to a page with more information. |
| text | The preview image URL. For YouTube it is filled in automatically; for native media it can be generated (see Poster images). |
Timing and playback
| Field | Type | Description |
|---|---|---|
| number | The length of the media in seconds. If it is missing or wrong, the module measures it the next time the item plays. |
| text | Start playback at this point instead of at the beginning, for example |
| text | Stop playback at this point, for example |
| text | Fade the sound in when the item starts: |
| number | The second the viewer last stopped at, so playback can resume there. Set automatically. |
Dates, series and rating
| Field | Type | Description |
|---|---|---|
| text | The publish date, normalised to |
| text | The date access ends. An empty value means unlimited. See Expiry dates. |
| number | An episode number, if the item is part of a series. |
| number | A series number. |
| number | A star rating from |
| text | When the item was last watched. Set automatically. |
| text | When the item was added. Set automatically. |
| text | Always |
| Older playlist files stay valid. When a file is read, every missing field is filled in with its default, so a file written before |
Playlist files
Ready-made playlist files are ordinary .json files kept in the folder set by playlist_url_base (by default /assets/data/apps/multiPlayer/playlists). A single item looks like this:
{
"videoId": "abcXYZ12345",
"title": "Tiny Desk Concert",
"author": "NPR Music",
"description": "A short live set recorded at the office.",
"category": "music",
"tags": [ "live", "acoustic" ],
"type": "video/youtube",
"videoLink": "https://www.youtube.com/watch?v=abcXYZ12345",
"poster": "",
"duration": 920,
"rating": 0
}| A playlist file may be either a plain array of items ( |
A playlist file mixing one YouTube video, one native video and one audio track:
[
{
"videoId": "abcXYZ12345",
"title": "Live at the Tiny Desk",
"author": "NPR Music",
"type": "video/youtube",
"videoLink": "https://www.youtube.com/watch?v=abcXYZ12345",
"duration": 920
},
{
"videoId": "promo-clip-01",
"title": "Product Tour",
"author": "Our Team",
"type": "video/mp4",
"src": "/assets/video/product-tour.mp4",
"videoLink": "/assets/video/product-tour.mp4",
"duration": 140
},
{
"videoId": "podcast-ep-07",
"title": "Episode 7 - Static Sites",
"author": "Our Team",
"type": "audio/mpeg",
"src": "/assets/audio/podcast-07.mp3",
"videoLink": "/assets/audio/podcast-07.mp3",
"poster": "/assets/image/podcast/cover-07.jpg",
"series": 1,
"episode": 7,
"duration": 2410
}
]| The easiest way to create a playlist file is to build the list in the browser — add items with the edit panel — and then use Export Playlist. The exported file can be dropped straight into your |
Preloading a playlist
To have a player load one or several playlist files on page load, list the file names under that player’s playlist.preload key:
playlist:
preload: [ tinydesk-concerts.json ]How a name is resolved:
| Value | Resolved to |
|---|---|
| A short file name is looked up in |
| A path starting with |
A full web address is used as it is. |
| Preloading is safe to repeat. On every page load the module merges the file into the player’s playlist: it adds items that are not there yet and leaves everything else — including the viewer’s own additions, ratings and stop positions — untouched. Nothing is ever duplicated or overwritten. This is why it is safe to keep a |
Preloading only fills the list; it does not start playing on its own. On a first visit the module loads the first item into the player in the paused state as soon as the preload has arrived, so the viewer sees a poster instead of an empty box. On later visits the first stored item is loaded the same way.
Searching
The search box above the playlist filters the list as you type. The search runs over an index the module builds from the playlist, so it is fast even for long lists.
These fields are searched, in order of importance:
| Field | Weight | Note |
|---|---|---|
| 10 | The strongest match. |
| 5 | Channel or artist name. |
| 3 | Grouping labels. |
| 2 | Free text. |
| 1 | Searched, but not boosted. |
The index is rebuilt automatically whenever the playlist changes — after an import, an edit, a delete, or a preload.
Sorting
The sort control offers the following orders:
| Option | Sorts by |
|---|---|
| When the item was last watched. |
| The publish date of the item. |
| The length of the item. |
| The title, A to Z. |
| The author or channel, A to Z. |
| The category label. |
| The description text. |
| The star rating, best first. |
| The episode number. Useful for a series. |
| The media type, so audio, video and YouTube items are grouped. |
The module also picks a sensible starting order for you. When every item of a list belongs to a series, the list is sorted by episode automatically; otherwise the most recently watched item comes first. As soon as the viewer picks an order by hand, that choice wins.
| Sort options that cannot do anything useful are hidden. A list where nothing is rated offers no |
Playback features
The features in this section are optional. Each one is switched on with a single YAML key, and most of them can also be set for one media item only through the Edit Media Settings dialog. A player that uses none of them behaves like a plain VideoJS player with a playlist.
Audio only
Some YouTube items are worth listening to but not worth watching — a concert recording, a podcast, an audio book. Audio only plays such an item without showing the video: the poster image stays on screen, the control bar keeps working, and YouTube is asked for the lowest video quality so far less data is loaded.
The feature applies to YouTube items only. Native mp3 and mp4 items are never touched — an mp3 has no video to hide anyway.
Three settings work together:
| Key | Type | Default | Description |
|---|---|---|---|
| boolean |
| The starting state of the switch. Once the viewer has used the switch, the viewer’s choice is remembered and wins over this value. |
| boolean | not set | A fixed state for players that show no switch. It is ignored while the switch is on the page, and ignored completely when the key is absent. |
| boolean |
| Show the switch at all. Set to |
settings:
# start with audio only, but let the viewer change it
audio_only: true
# ... or: no switch at all, always audio only
set_audio_only: true
ui_elements:
audioOnlySwitch: false| The switch appears in the playlist title bar and is self-hiding: it is only shown when the current list holds at least one YouTube item. |
Playing a section
Sometimes only a part of a media item is interesting — the song inside a long concert recording, or a chapter of a podcast. startAt and endAt define a playback window: playback begins at startAt and stops at endAt.
Both values accept several shapes, so you can write whatever is convenient:
| Value | Meaning |
|---|---|
| One minute and thirty seconds (hours:minutes:seconds). |
| One minute and thirty seconds (minutes:seconds). |
| Ninety seconds. |
`` (empty) | Not set — the feature is off for this item. |
Where the values may be set, most specific first:
-
the media item itself (
startAt/endAtin the playlist record, edited in the Edit Media Settings dialog or shipped in a playlist file); -
the YAML chain (
startAt/endAtas normal settings keys), which applies to every item of the player; -
not set — the whole item plays.
settings:
startAt: 00:00:12
endAt: 00:04:45| An |
Resuming still wins where it makes sense: when the viewer stopped inside the window last time, playback continues from there instead of jumping back to startAt. A stop position outside the window is discarded.
When the item is part of a playlist and endAt is reached, the player stops. With loop mode or the autoadvance option of the playlist plugin switched on, the next item takes over from there.
Fading the sound in
audioFadeIn ramps the sound up from silence to the player’s volume when an item starts, instead of starting at full volume. It works for all kinds of media — YouTube, mp3 and mp4.
| The fade follows the volume control of the player, so the viewer sees the volume rise. A player that is muted is left alone — no fade is applied. |
| Value | Steps | Description |
|---|---|---|
| 150 | The shortest, most subtle fade. |
| 250 | A slightly longer fade. |
| 350 | A long fade. |
| 500 | The longest and smoothest fade. |
| - | No fade. Used on one item to opt out when the player-wide setting is on. |
settings:
audioFadeIn: slowAs with the playback window, a value set on the media item wins over the YAML setting, and off on an item is a deliberate opt-out.
Expiry dates
Some media is only licensed for a limited period. The optional expiryDate field of a media item states the day access ends. An empty value — the default for every item — means unlimited.
| The expiry date is the moment access ends, so an item whose expiry date is today is already expired. Dates are compared at local midnight, so the state never flips because of the time of day or a timezone difference. |
The module colours the Expiry Date field of the edit dialog like a traffic light and blocks playback once the date is reached:
| Days left | State | What happens |
|---|---|---|
more than 28 |
| Nothing. No colouring. |
28 or fewer |
| The field turns yellow. Playback is normal. |
14 or fewer |
| The field turns red. Playback is normal. |
0 or fewer |
| The field stays red, playback is refused, and the viewer sees a notice naming the item and its expiry date. |
{
"videoId": "licensed-clip-01",
"title": "Licensed Clip",
"src": "/assets/video/licensed-clip.mp4",
"expiryDate": "2026-12-31"
}Downloading media
A viewer can save plain media files to the download folder — one item at a time with the download button on the item, or the whole list at once with the Download button in the playlist title bar.
What can be downloaded is decided by the file extension:
| Source | Downloadable |
|---|---|
| Yes — saved as |
| Yes — saved as |
YouTube items | No. There is no plain file behind them. |
Streaming addresses ( | No. |
The buttons are self-hiding: a YouTube-only playlist shows no download UI at all. They can also be switched off per player with ui_elements.playlist_download_button and ui_elements.playlist_download_all_button.
Every export shows a small toast notification: when it starts, for each file of a batch, and for the final result.
| Files hosted on another site can only be downloaded when that site allows it (it must send CORS headers). If it does not, the browser refuses, and MultiPlayer falls back to opening the file in a new tab so the viewer can save it manually. This is a browser rule, not a limitation of the module. Files on your own site always download without a problem. |
Poster images
A poster is the still image shown before an item plays. MultiPlayer fills it in for you:
- YouTube items
-
The poster is taken from YouTube’s own thumbnail service. Images are loaded from the cookieless host
i.ytimg.com, so no YouTube cookies are set on your page. The quality is chosen withvideoJS.players.youtube.poster. - Native items
-
If the item has no poster of its own, the module can create one by grabbing a frame from the video and shrinking it. This is controlled by
videoJS.poster.autoGenerate(see Automatic poster images). - No poster available
-
The fallback image
default_posteris shown.
| An |
The player on the page
You do not write the player’s HTML — J1 generates it from your configuration. Still, it helps to know the parts a viewer sees, because the settings of UI elements map directly onto them.
A player is built from four areas, stacked from top to bottom:
-
the header bar,
-
the media area,
-
the playlist panel (opens over the media area),
-
the edit panel (opens over the media area).
The playlist panel and the edit panel are mutually exclusive: opening one closes the other, and the button of the closed panel is greyed out while the other panel is open.
The header bar
Every player has a slim header bar with three slots:
- Show / Hide Playlist button (left)
-
Opens and closes the playlist panel. It is disabled and greyed out until the playlist has content. Hidden with
ui_elements.show_playlist_button: false. - Title (centre)
-
The title of the item that is currently loaded. It is empty until the first item plays. A click on the title opens or closes the playlist panel, exactly like a click on the button on the left.
- Manage Playlists button (right)
-
Opens the edit panel, where the viewer can add, import, export or clear items. Hidden with
ui_elements.edit_playlist_button: false.
| The header keeps its three-slot layout even when a button is hidden, so the title always stays centred. |
The media area
Below the header is the player itself. Before anything is loaded, a friendly placeholder image is shown in its place. As soon as an item is loaded, the poster appears; playback starts either automatically (videoJS.autoStart) or when the viewer presses play.
For audio items and for YouTube items played in audio only mode, the poster stays visible for the whole playback — there is no video to show.
The playlist panel
When opened, the playlist panel shows a title bar with the controls, and below it the items themselves.
The title bar can hold:
-
a search box to filter the list as you type (
ui_elements.search); -
a list / cards switch to change the layout (
ui_elements.modeSwitch); -
a merge switch that decides whether importing adds to or replaces the list (
ui_elements.mergeSwitch); -
an audio only switch for YouTube items (
ui_elements.audioOnlySwitch); -
a Download button that exports every downloadable item at once (
ui_elements.playlist_download_all_button); -
a loop switch to repeat the playlist (
ui_elements.loop); -
a sort control to reorder the list (
ui_elements.sort).
All of these hide themselves while the playlist is empty, and some hide themselves when they would have nothing to do — see the note in UI elements.
List and cards
The playlist itself is drawn in one of two layouts. The viewer switches between them with the mode switch; the starting layout comes from playlist.type.
| Layout | Description |
|---|---|
| Compact rows. Each row shows a small poster, the title, the author, the duration, the star rating, and the action buttons. Good for long lists. |
| A grid of preview cards with a large poster. The number of cards per row is set with |
The action buttons of an item
Every item carries a small set of buttons. Each one can be switched off per player:
| Button | YAML key | What it does |
|---|---|---|
Rate |
| Opens a dialog to give the item a rating from one to five stars. |
Edit |
| Opens the Edit Media Settings dialog (see The Edit Media Settings dialog). |
Download |
| Saves the media file to the download folder. Only shown for plain files. |
Delete |
| Removes the item from the playlist. |
Clicking an item anywhere else plays it. The item that is currently playing is highlighted.
The edit panel
The edit panel is where content is managed. It opens over the media area and offers:
-
a box to paste a link — a YouTube URL or the path of a local
mp3ormp4file — to add one item; -
an Import control to load a ready-made playlist from the server;
-
Export Playlist to save the current playlist to a
.jsonfile; -
Import Playlist to load a playlist from a file on the viewer’s computer;
-
Clear Playlist to empty the list.
Whether an import adds to or replaces the current list is decided by the merge switch in the playlist panel.
The Edit Media Settings dialog
The Edit button of an item opens a dialog with every field the viewer may change.
| Everything the viewer changes here is stored in the browser only. To make a change permanent for all visitors, edit it in the dialog, use Export Playlist, and replace the file in your |
| Field | Description |
|---|---|
| A category chosen from a list of common genres. |
| The text shown in the playlist. Both are searchable. |
| Series and episode number, used for the automatic episode sorting and for the loop switch. |
| A link to a page with more information. |
| The canonical address of the item. |
| The publish date. |
| The playback window of this item. See Playing a section. |
| The fade-in speed of this item. See Fading the sound in. |
| The day access ends. The field is coloured like a traffic light and a short hint below it says how many days are left. See Expiry dates. |
| Tag words, chosen from a list or typed in. |
| The media type of the item. |
Notifications
Two kinds of message can appear:
- Toasts
-
Small notes that slide in and disappear again. They report progress and results — a playlist that was imported, an export that started, a file that could not be fetched. Toasts come in three flavours: information, success and error.
- Expiry notice
-
A dialog that appears when the viewer tries to play an item whose expiry date has been reached. It names the item, says why it cannot be played, and shows the expiry date.
Methods
Most pages need no JavaScript at all — the configuration files already cover normal use. The methods below are for advanced users who want to build custom controls, load media after the page has loaded, or read the player’s state.
The module follows the same shape as VideoJS. You get a player through a factory function, and then work with that player object.
The module factory
videoPlayer(id, options) is the main entry point. It creates the player for id the first time you call it and returns the existing one on later calls (this is called "create-or-get"). Options given for a player that already exists are ignored — exactly as in VideoJS.
| Older code may use |
// create or fetch the player named 'player_1'
const mp = videoPlayer('player_1');| Function | Description |
|---|---|
| Create the player for |
| Return the player for |
| Return the registry object holding every live player, keyed by id. |
| A direct reference to the same registry |
| The module’s version string. |
| Do not use |
A player instance
The object returned by the factory represents one player. It carries a few accessors and, most importantly, its playlistManager.
| Member | Description |
|---|---|
| The player’s id. |
| The options the player was created with. |
| The active player id used internally to scope elements and storage. |
| The object that manages this player’s playlist (see below). |
| Close the playlist panel. |
| Close the edit panel. |
| Remove the player from the registry so its id can be created again. |
Two small helper objects are also exposed on every player:
| Member | Description |
|---|---|
| Show a notification. |
| Work with expiry dates: |
const mp = videoPlayer.getPlayer('player_1');
if (mp) {
mp.videoPlayerToast.show('Playlist loaded.', 'success');
}Working with playlists
The playlist is managed by mp.playlistManager. Its methods are grouped below by what they do. Anywhere you see videoId, it is the videoId field of a media item.
Adding and updating items
| Method | Description |
|---|---|
| Add an item to the playlist from an item object. |
| Create an item early (from what is known at load time) and store it. |
| Fill in details that only arrive later — the real title, author, poster, or measured length. By default it only fills empty fields; pass |
| Update several fields of an item at once. |
| Set the star rating ( |
| Remember where the viewer stopped, so playback can resume. |
| Store the measured length of an item. |
| Store the author or channel name. |
| Store a poster URL for an item. |
| Mark an item as watched now. |
Removing items
| Method | Description |
|---|---|
| Remove a single item from the playlist. |
| Remove every item from the playlist. |
Reading the playlist
| Method | Description |
|---|---|
| Return the whole playlist as an array of items. |
| Write a complete playlist back to the store. |
| Return one item. |
| Return the saved resume position (in seconds) for an item. |
| Return the id of the item that follows the given one. |
Playing items
| Method | Description |
|---|---|
| Load and play the given item. This is the method the playlist itself uses, so all gates — expiry, playback window, fade-in — apply. |
| Load an item into the player. The lower-level call |
| Load the first stored item in the paused state. Runs at most once per player and per page load. |
| Report whether the first-item load has already happened for this player. |
Importing, exporting and preloading
| Method | Description |
|---|---|
| Load a playlist file from a web address into the playlist. |
| Load a playlist from a file chosen on the viewer’s computer. |
| Save the current playlist to a |
| Save a backup copy of the current playlist to a file. |
| Merge one or more configured playlist files into the player on load. This is what the |
| Convert a stored playlist into the shape the VideoJS playlist plugin expects. |
Downloading media files
| Method | Description |
|---|---|
| Save the media file of one item to the download folder. Does nothing for YouTube items and streaming addresses. |
| Save every downloadable item of the active view. A second call is ignored while a batch is still running. |
Searching and sorting
| Method | Description |
|---|---|
| Build the search index for the current playlist. |
| Filter the playlist by a search term. |
| Clear the current search and show the whole list again. |
| Reorder the list, for example by |
| Apply the automatically chosen starting order. |
Posters
| Method | Description |
|---|---|
| Create a poster image for one native item by grabbing a frame. |
| Do the same for every native item of the playlist that has no poster yet. |
Rendering the panel
| Method | Description |
|---|---|
| Redraw the playlist panel to match the stored playlist. |
| Draw the playlist in the list layout. |
| Draw the playlist in the cards layout. |
| Mark an item as the one currently playing (it is highlighted). |
| Remove the "currently playing" highlight from all items. |
| Open the star-rating dialog for one item. |
| Open the Edit Media Settings dialog for one item. |
Setup methods
These are normally called by the adapter that starts the player. You rarely call them yourself.
| Method | Description |
|---|---|
| Hand the merged configuration to the playlist manager. |
| Tell the manager which player it belongs to, so it uses the right storage and the right HTML elements. |
A complete example
Adding an item from a custom button and playing it:
// get the running player named 'player_1'
const mp = videoPlayer.getPlayer('player_1');
if (mp) {
// add an item to the playlist
mp.playlistManager.addEntry({
videoId: 'abcXYZ12345',
title: 'Live at the Tiny Desk',
author: 'NPR Music',
type: 'video/youtube',
videoLink: 'https://www.youtube.com/watch?v=abcXYZ12345'
});
// then play it
mp.playlistManager.playEntry('abcXYZ12345');
}Adding a local audio track instead:
const mp = videoPlayer.getPlayer('player_1');
if (mp) {
mp.playlistManager.addEntry({
videoId: 'podcast-ep-07',
title: 'Episode 7 - Static Sites',
author: 'Our Team',
type: 'audio/mpeg',
src: '/assets/audio/podcast-07.mp3',
videoLink: '/assets/audio/podcast-07.mp3',
poster: '/assets/image/podcast/cover-07.jpg'
});
mp.playlistManager.playEntry('podcast-ep-07');
}| Use |
Notes on implementation
This section explains a few design choices. You do not need them for daily use, but they help when something behaves unexpectedly on a page with several players.
The multiPlayer adapter
The adapter is a small piece of J1 JavaScript located at ~/assets/theme/j1/adapter/js/multiPlayer.js. It is loaded on every page and does the following work for you:
-
Read the three YAML files: the defaults, your global settings, and your per-player control settings.
-
Merge them into the effective settings of every player. The merge is done per key, so a player only has to state what it wants to change.
-
Load the HTML portion of every enabled player into its placeholder on the page.
-
Wait until the page is fully loaded and the J1 core has finished its own initialization.
-
Start the module for each player: create the player instance, hand it its effective settings, wire up all buttons, and dispatch the configured preloads.
| The adapter waits at most 5 seconds for the page to become ready. If the page never reaches the ready state (for example, caused by an error in another module), the adapter stops waiting and writes a warning to the browser console. This protects the browser from wasting CPU time on a page that cannot finish loading. |
You normally never call the adapter yourself. It is started automatically by the J1 core at page load.
One instance per player
The module is built the same way as VideoJS. Calling videoPlayer(id) returns a separate instance for each id, kept in a shared registry. Each instance owns its own state, its own playlist manager, and its own event handlers.
There is no hidden "default" player that everything shares. An earlier design had one, and it caused several players on a page to fight over one set of settings — so it was removed.
Per-player settings win
Whenever a player needs one of its settings, it looks in a fixed order: first the options handed to its own factory call (that is, the merged per-player configuration), then the page-global options, and only then an internal fallback. In short: the setting closest to the player wins.
This is why two players on one page can start differently, offer different speed menus, or run different plugins.
| Lists (arrays) are replaced by the more specific layer, never mixed. A player that states |
Each player has its own storage
Because every player keeps its playlist in the browser’s local storage, the module gives each player its own storage name by adding the player id to it (for example playlist_player_1). This is why an item added to one player never appears in another.
The same scoping applies to the small display preferences — whether the playlist was last shown as a list or as cards, the merge switch, the loop switch, the audio-only switch, and the last search mode are all remembered per player.
All media kinds share one path
Audio, native video and YouTube all play through the same VideoJS player. The module looks at the item to decide which one it is: a YouTube link is played through YouTube; anything else is treated as a native file and loaded from its src.
Posters follow the same rule — YouTube posters come from YouTube, native posters are generated from the video or taken from the item. The settings, however, come from two different blocks: videoJS.players.youtube for YouTube and videoJS.players.native for native audio and video.
The cards view is a web component
The cards layout of the playlist is rendered by a small, modern web component (built with Lit). It draws each item as a card and updates only the card that changed — rating one item or deleting one entry never redraws the whole grid.
It renders into the normal page (light DOM), so all existing styles and click handling keep working. You never interact with it directly; the mode switch and playlist.type control it for you.
Preloading is non-destructive
Preloading always merges. It never overwrites the viewer’s existing list, and running it again on the next reload never creates duplicates. This is why it is safe to keep a preload list in your configuration permanently.
Player styles (CSS)
The look of the module is defined by three stylesheets:
| File | Purpose |
|---|---|
| The base stylesheet of the VideoJS library. It defines the layout of all player components: the control bar, the buttons, the progress bar, menus, captions, and the icon font. This file is part of the library and should not be changed. |
| The J1 player theme. It is loaded on top of the base stylesheet and defines the visual design of the J1 player — the control bar, the big play button and the progress bar. See the VideoJS manual. |
| The stylesheet of the MultiPlayer module ( |
Theme colours
player.css defines its colours as CSS variables on :root. To adapt them to your site, overwrite the variables in your own stylesheet — there is no need to modify the module file:
| Variable | Value | Used for |
|---|---|---|
|
| Background of the playlist cards and the panels. |
|
| Borders of cards, rows and input fields. |
|
| Background of a filled input field. |
|
| Normal text colour. |
|
| Secondary text, for example the author line. |
|
| Shadow under cards and panels. |
|
| Border of the item that is currently playing. |
|
| Background of the Expiry Date field from four weeks before the date. |
|
| Text colour on the yellow background. |
|
| Background of the Expiry Date field from two weeks before the date and once it is reached. |
|
| Text colour on the red background. |
| from | How many cards fit in one row. Set by the module, not by hand. |
:root {
--item-active-border-color: #e91e63; /* pink instead of blue */
}Examples
The examples below are complete, working configurations. Copy one into your multiPlayer_control.yml and adjust the ids and playlist file names.
| J1 MultiPlayer is mobile-friendly. On touch devices the player and its buttons respond to taps, and the layout follows the width of the page so it looks right on phones and tablets. |
A player with a preloaded playlist
The simplest useful setup: a single player that loads a ready-made playlist file on page load and shows it as a grid of cards, four per row.
settings:
players:
# --------------------------------------------------------------------------
# Tiny Desk Concerts
#
- player:
enabled: true
id: player_tiny_desk_concerts
description: NPR Tiny Desk Concerts
ui_elements:
edit_playlist_button: false
videoJS:
autoStart: true
playbackRates:
enabled: true
values: [ 0.5, 1, 1.5, 2 ]
plugins:
playlist:
enabled: true
playlist:
preload: [ tinydesk-concerts.json ]
cards:
perRow: 4Place it on a page with:
.Tiny Desk Concerts
multiPlayer::player_tiny_desk_concerts[role="mb-5"]An audio player for a podcast
A player for a series of mp3 episodes. The list starts in list layout, loops through the series, and fades the sound in on every episode.
settings:
players:
- player:
enabled: true
id: player_podcast
description: Our Podcast
audioFadeIn: slow
ui_elements:
loop: true
audioOnlySwitch: false
playlist:
type: list
preload: [ podcast-season-1.json ]
loop:
enabled: trueMultiple players on a page
Each player has its own id, its own preloaded playlist, and its own card layout. They do not share anything.
settings:
players:
- player:
enabled: true
id: player_1
description: NPR Tiny Desk Concerts
playlist:
preload: [ tinydesk-concerts.json ]
cards:
perRow: 4
- player:
enabled: true
id: player_2
description: Carpool Karaoke
playlist:
preload: [ carpool-karaoke.json ]
cards:
perRow: 3| When you place several players on one page, switch the inactive focus handling of the hotKeys plugin off in your global |
A read-only player
The viewer can watch and browse the preloaded playlist, but nothing can be changed: the Manage Playlists button is hidden and the item action buttons are switched off.
settings:
players:
- player:
enabled: true
id: player_showcase
description: Product Showcase
ui_elements:
edit_playlist_button: false
playlist_edit_button: false
playlist_delete_button: false
playlist_rate_button: false
playlist:
type: cards
preload: [ product-showcase.json ]
cards:
perRow: 3A player that plays one section only
Every item of this player starts twelve seconds in and stops after four minutes and forty-five seconds — useful for a preview player. Individual items can override the window in their own record.
settings:
players:
- player:
enabled: true
id: player_previews
description: Album Previews
startAt: 00:00:12
endAt: 00:04:45
audioFadeIn: default
playlist:
preload: [ album-previews.json ]Only one tab plays at a time
Switch cross-page synchronization on in your global multiPlayer.yml so it applies to every player of your site:
settings:
enabled: true
cross_page_sync:
enabled: true
on_play: true
on_pause: true
same_page: false