Jekyll One

Fulltext Search

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

Configuration (YAML)

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.

Adapter

A small piece of J1 JavaScript (~/assets/theme/j1/adapter/js/multiPlayer.js). On page load it reads the YAML files, merges them into the effective settings of every player, loads the HTML portion of each player, and starts the module. See The multiPlayer adapter.

Player core

The module itself (~/assets/theme/j1/modules/multiPlayer/js/player.js). It starts VideoJS, wires up all buttons, and manages the playlist. Advanced users can call it directly; see Methods.

Cards component

A small modern web component (~/assets/theme/j1/modules/multiPlayer/js/playlistCards.mjs, built with Lit) that draws the playlist as a grid of preview cards. It is used automatically when the playlist is shown in cards mode.

Playlist store

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 (mp3, mp4) can be downloaded by the viewer. YouTube items and streaming addresses (.m3u8, .mpd) never show a download button. See Downloading media.

Native audio (mp3)

An audio file that you host on your own web server. The address goes into the item’s src field. 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 src field. 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 videoLink field 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 videoJS.players.youtube, native audio and video read theirs from videoJS.players.native. See Player options (videoJS).

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 videoJS: block only needs the keys you want to change; every missing key falls back to the global settings and then to the defaults. You never have to copy a whole block.

File What it is for

_data/modules/defaults/multiPlayer.yml

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.

_data/modules/multiPlayer.yml

Your global settings. Anything you put here overrides the defaults for all players. This is where you switch the module on with enabled: true.

_data/modules/multiPlayer_control.yml

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 [ 0.25, 0.5, 1, 1.5, 2 ] and your player states [ 1, 2 ], the result is [ 1, 2 ] — not a mixture of both.

General options

These live at the top of the settings: block and control the module as a whole.

Key Type Default Description

enabled

boolean

false

Master on/off switch for the module. Set it to true in your global multiPlayer.yml to turn the player on.

playlist_url_base

string

/assets/data/apps/multiPlayer/playlists

The folder where your ready-made playlist files live. A short file name in a preload list is looked up in this folder.

xhr_data_path

string

/assets/data/multiPlayer

The folder the module reads its generated HTML from.

hideDisabled

boolean

false

Hide the placeholder of a player that is switched off (enabled: false) instead of leaving an empty box on the page.

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

show_playlist_button

boolean

true

The Show/Hide Playlist button on the left of the header bar. When set to false the button is not rendered for this player.

edit_playlist_button

boolean

true

The Manage Playlists button on the right of the header bar. When set to false the viewer cannot open the edit panel of this player.

Playlist panel controls

Key Type Default Description

search

boolean

true

The search box above the playlist.

sort

boolean

true

The sort control of the playlist.

modeSwitch

boolean

true

The switch that flips the playlist between the list and the cards layout.

mergeSwitch

boolean

true

The merge switch. When on, importing another playlist adds its items to the current one instead of replacing it.

loop

boolean

false

The loop switch that repeats the playlist from the top when the last item finishes.

audioOnlySwitch

boolean

true

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

playlist_rate_button

boolean

true

The rate button that opens the star-rating dialog of one item.

playlist_edit_button

boolean

true

The edit button that opens the Edit Media Settings dialog of one item.

playlist_delete_button

boolean

true

The delete button that removes one item from the playlist.

playlist_download_button

boolean

true

The download button of one item. See Downloading media.

playlist_download_all_button

boolean

true

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:

  • the loop switch only appears when every item belongs to a series, because looping only makes sense for a complete series;

  • the audio only switch only appears when the list holds at least one YouTube item;

  • the download buttons only appear for plain files (mp3, mp4) — a YouTube-only playlist shows no download UI at all;

  • sort options that would do nothing (for example sorting by rating when no item is rated yet) are hidden as well.

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

enabled

boolean

true

Show the playlist panel. Turn it off for a plain single-media player.

close_on_play

boolean

false

Close the playlist panel automatically as soon as an item starts playing.

type

string

list

The starting layout of the playlist: list (compact rows) or cards (a grid of preview cards).

cards.perRow

number

2

How many cards to show per row in cards mode. Each player can have its own value.

loop.enabled

boolean

false

Repeat the playlist from the top when the last item has finished.

loop.pip

boolean

false

Allow picture-in-picture: a small floating video window that keeps playing while the viewer scrolls or switches tabs.

preload

array

[]

A list of ready-made playlist files loaded into this player on page load. See Preloading a playlist.

preload_attempts

number

2

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

autoStart

boolean

true

Start playing as soon as an item is loaded.

preloadVideo

boolean

true

Let the browser fetch the media ahead of time so playback starts quickly.

hideControlBar

boolean

false

Hide the VideoJS control bar. Only useful for a player that is driven completely from the playlist.

playbackRates.enabled

boolean

true

Offer a speed menu on the player.

playbackRates.values

array

[ 0.25, 0.5, 1, 1.5, 2 ]

The speeds available in that menu (1 is normal speed).

players.native

Settings for native media — your own mp3 and mp4 files.

Key Type Default Description

autoplay

boolean

true

Play as soon as the media is ready.

controls

boolean

true

Show the player’s control bar.

fluid

boolean

true

Make the player responsive so it fills the width of its container.

preload

string

auto

How eagerly to load the media: auto, metadata, or none.

playsinline

boolean

true

On iPhones, play inside the page instead of forcing full screen.

default_poster

string

/assets/image/icon/videojs/videojs-poster.png

The image shown before an item plays, when no other poster is available.

sourceOrder

array

[ mp4 ]

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

autoplay

number

0

Do not start playing automatically (0 = off, 1 = on).

cc_load_policy

number

0

Captions (subtitles) are initially hidden. MultiPlayer always forces this value to 0 and switches the YouTube caption module off afterwards.

controls

number

0

Hide the YouTube controls. The VideoJS control bar is used instead.

disablekb

number

1

Disable the YouTube keyboard control. Keyboard support comes from the J1 hotKeys plugin instead.

enablejsapi

number

1

Allow VideoJS to control the YouTube player. Required, do not change.

fs

number

0

Hide the YouTube fullscreen button. Fullscreen is handled by VideoJS.

iv_load_policy

number

3

Video annotations are not shown.

loop

number

0

Do not restart the video automatically when it ends.

modestbranding

number

1

Reduce the YouTube branding (logo) in the player.

rel

number

0

Do not show related videos from other channels when playback ends.

showinfo

number

0

Do not show the video title and uploader before playback starts.

fluid

boolean

true

Make the player responsive so it fills the width of its container.

enablePrivacyEnhancedMode

boolean

true

Run the embed from the privacy-friendly host www.youtube-nocookie.com instead of youtube.com. Set to false to restore the classic host.

default_poster

string

/assets/image/icon/videojs/videojs-poster.png

Fallback poster image, used if no poster can be loaded from YouTube.

poster

string

hqdefault.jpg

Name of the poster image taken from YouTube. Posters are loaded from the cookieless image host i.ytimg.com.

Keeping enablePrivacyEnhancedMode switched on avoids the third-party cookies that Chrome and Lighthouse report under Best Practices. Poster images are already loaded cookieless.

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

enabled

boolean

true

Turn automatic poster creation on or off.

capturePosition

number

5

Which second of the video to grab the frame from. If this is 0 or less, captureFraction is used instead.

captureFraction

number

10

A percentage of the video’s length to use as the capture point (used only when capturePosition is 0 or less).

maxWidth

number

540

The poster is shrunk to at most this many pixels wide (the shape is kept).

mimeType

string

webp

The image type to save: jpeg, png, or webp.

quality

number

0.8

Image quality from 0 to 1 (used for jpeg and webp).

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

playlist

The playlist engine. autoadvance plays the next item when one ends, and autoadvance_delay sets the pause (in seconds) between them.

skipButtons

Adds skip forward and skip back buttons around the play button. forward and backward set how many seconds each skip jumps.

nextPrevButtons

Adds next and previous buttons to jump between playlist items.

zoomButtons

Adds zoom, move and rotate controls for the video image.

hotKeys

Keyboard shortcuts (for example the arrow keys to seek). Off by default.

When you place several players on one page, set videoJS.plugins.hotKeys.enableInactiveFocus: false. Otherwise every player tries to take the keyboard focus back when its control bar fades out.

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

enabled

Whether this player is built at all.

id

The player’s unique name (for example player_1). It must be unique on the page and is used to build all of the player’s element ids and its storage name.

description

A human-friendly label for the player.

playlist.preload

A list of playlist files to load into this player on page load. See Preloading a playlist.

ui_elements

Show or hide this player’s controls, for example ui_elements.edit_playlist_button: false to remove the Manage Playlists button from this player only. See UI elements.

videoJS, playlist, audio_only, startAt, …​

Any option from the sections above, applied to this player only.

Per-player settings really are per-player. Every player resolves its own autoStart, its own playbackRates, its own plugins, and so on. Two players on the same page can offer different speed menus or run different plugins. You only write the keys you want to change; everything else is inherited.

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:                       4

Placing 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 id of an enabled player in multiPlayer_control.yml. If no player with that id is found, the page shows an empty box and the browser console reports the missing player.

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

videoId

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.

title

text

The title, shown in the playlist and in the player header while the item plays.

author

text

The channel, artist or author name.

description

text

A longer description. It is searchable.

category

text

A category label used for grouping and search.

tags

list

A list of tag words, for example [ music, live ].

Where the media lives

Field Type Description

type

text

The media’s MIME type, for example audio/mpeg, video/mp4 or video/youtube. Defaults to video/mp4.

src

text

The address of the native audio or video file. Leave empty for YouTube items.

videoLink

text

The canonical link. For YouTube this is the YouTube URL; for native media it is usually the same as src.

infoLink

text

An optional link to a page with more information.

poster

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

duration

number

The length of the media in seconds. If it is missing or wrong, the module measures it the next time the item plays.

startAt

text

Start playback at this point instead of at the beginning, for example 00:01:30. See Playing a section.

endAt

text

Stop playback at this point, for example 00:04:45. See Playing a section.

audioFadeIn

text

Fade the sound in when the item starts: default, slow, slower, slowest, or off. See Fading the sound in.

lastPosition

number

The second the viewer last stopped at, so playback can resume there. Set automatically.

Dates, series and rating

Field Type Description

issueDate

text

The publish date, normalised to YYYY-MM-DD.

expiryDate

text

The date access ends. An empty value means unlimited. See Expiry dates.

episode

number

An episode number, if the item is part of a series.

series

number

A series number.

rating

number

A star rating from 0 to 5. The viewer can set this in the panel.

watchDate

text

When the item was last watched. Set automatically.

createDate

text

When the item was added. Set automatically.

creator

text

Always multiPlayer for items the module creates itself.

Older playlist files stay valid. When a file is read, every missing field is filled in with its default, so a file written before expiryDate, startAt, endAt or audioFadeIn existed keeps working exactly as 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 ([ { …​ }, { …​ } ]) or an object with a playlist key ({ "playlist": [ { …​ } ] }). The module reads both shapes.

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 playlist_url_base folder and referenced from a preload list.

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

tinydesk-concerts.json

A short file name is looked up in playlist_url_base.

/assets/data/my-lists/rock.json

A path starting with / is used as it is.

https://example.com/lists/rock.json

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 preload list in your configuration permanently.

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

title

10

The strongest match.

author

5

Channel or artist name.

category, tags

3

Grouping labels.

description

2

Free text.

infoLink, videoLink, issueDate, type

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

Date (newest) / Date (oldest)

When the item was last watched.

Issue Date (newest) / Issue Date (oldest)

The publish date of the item.

Duration (longest) / Duration (shortest)

The length of the item.

Title

The title, A to Z.

Author

The author or channel, A to Z.

Category

The category label.

Description

The description text.

Rating

The star rating, best first.

Episode

The episode number. Useful for a series.

Type

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 Rating option, and a list without episode numbers offers no Episode option.

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

audio_only

boolean

false

The starting state of the switch. Once the viewer has used the switch, the viewer’s choice is remembered and wins over this value.

set_audio_only

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.

ui_elements.audioOnlySwitch

boolean

true

Show the switch at all. Set to false to hide it.

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

00:01:30

One minute and thirty seconds (hours:minutes:seconds).

01:30

One minute and thirty seconds (minutes:seconds).

90

Ninety seconds.

`` (empty)

Not set — the feature is off for this item.

Where the values may be set, most specific first:

  1. the media item itself (startAt / endAt in the playlist record, edited in the Edit Media Settings dialog or shipped in a playlist file);

  2. the YAML chain (startAt / endAt as normal settings keys), which applies to every item of the player;

  3. not set — the whole item plays.

settings:
  startAt:                              00:00:12
  endAt:                                00:04:45

An endAt that is earlier than or equal to startAt would define a window that can never play. Such a value is treated as a mistake and ignored; the item plays to its normal end.

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

default

150

The shortest, most subtle fade.

slow

250

A slightly longer fade.

slower

350

A long fade.

slowest

500

The longest and smoothest fade.

off

-

No fade. Used on one item to opt out when the player-wide setting is on.

settings:
  audioFadeIn:                          slow

As 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

ok

Nothing. No colouring.

28 or fewer

warning

The field turns yellow. Playback is normal.

14 or fewer

critical

The field turns red. Playback is normal.

0 or fewer

expired

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

.mp3

Yes — saved as audio/mpeg.

.mp4

Yes — saved as video/mp4.

YouTube items

No. There is no plain file behind them.

Streaming addresses (.m3u8, .mpd) and other extensions

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 with videoJS.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_poster is shown.

An mp3 has no picture to grab, so give audio items a poster of their own — an album cover or a podcast logo. Without one they fall back to the default poster image.

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:

  1. the header bar,

  2. the media area,

  3. the playlist panel (opens over the media area),

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

list

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.

cards

A grid of preview cards with a large poster. The number of cards per row is set with playlist.cards.perRow. Good for short, visual lists.

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

ui_elements.playlist_rate_button

Opens a dialog to give the item a rating from one to five stars.

Edit

ui_elements.playlist_edit_button

Opens the Edit Media Settings dialog (see The Edit Media Settings dialog).

Download

ui_elements.playlist_download_button

Saves the media file to the download folder. Only shown for plain files.

Delete

ui_elements.playlist_delete_button

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 mp3 or mp4 file — 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 .json file;

  • 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 playlist_url_base folder.

Field Description

Category

A category chosen from a list of common genres.

Title, Description

The text shown in the playlist. Both are searchable.

Series, Episode

Series and episode number, used for the automatic episode sorting and for the loop switch.

Info Link

A link to a page with more information.

Video Link

The canonical address of the item.

Issue Date

The publish date.

Start At, End At

The playback window of this item. See Playing a section.

Audio Fade In

The fade-in speed of this item. See Fading the sound in.

Expiry Date

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.

Tags

Tag words, chosen from a list or typed in.

Type

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 videoPlayer.createInstance(id), getInstance(id), hasInstance(id), listInstances() or removeInstance(id). These still work but are deprecated aliases. New code should use the factory, getPlayer(), getPlayers() and dispose().

// create or fetch the player named 'player_1'
const mp = videoPlayer('player_1');
Function Description

videoPlayer(id, options)

Create the player for id, or return it if it already exists.

videoPlayer.getPlayer(id)

Return the player for id, or null if it does not exist. Never creates one.

videoPlayer.getPlayers()

Return the registry object holding every live player, keyed by id.

videoPlayer.players

A direct reference to the same registry getPlayers() returns.

videoPlayer.VERSION

The module’s version string.

Do not use new to build a player. Creating a second player for an id that already exists throws an error. Always go through the factory, which returns the existing player instead.

A player instance

The object returned by the factory represents one player. It carries a few accessors and, most importantly, its playlistManager.

Member Description

mp.id()

The player’s id.

mp.options()

The options the player was created with.

mp.getPlayerID()

The active player id used internally to scope elements and storage.

mp.playlistManager

The object that manages this player’s playlist (see below).

mp.closePlaylist()

Close the playlist panel.

mp.closeEditPlaylist()

Close the edit panel.

mp.dispose()

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

mp.videoPlayerToast

Show a notification. show(message, type) with type info, success or error; progress(message) returns a toast you can update and dismiss yourself.

mp.videoPlayerExpiryNotice

Work with expiry dates: isExpired(entry), state(date), hintText(date), and show(entry, videoId) to display the notice dialog.

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

addEntry(entry)

Add an item to the playlist from an item object.

createEntry(entry)

Create an item early (from what is known at load time) and store it.

enrichEntry(videoId, meta, force)

Fill in details that only arrive later — the real title, author, poster, or measured length. By default it only fills empty fields; pass force = true to overwrite.

updateEntryFields(videoId, fields)

Update several fields of an item at once.

updateEntryRating(videoId, rating)

Set the star rating (0-5) for an item.

updateEntryPosition(videoId, positionSeconds)

Remember where the viewer stopped, so playback can resume.

updateEntryDuration(videoId, durationSeconds)

Store the measured length of an item.

updateEntryAuthor(videoId, author)

Store the author or channel name.

updateEntryPoster(videoId, poster, force)

Store a poster URL for an item.

updateWatchDate(videoId)

Mark an item as watched now.

Removing items

Method Description

deleteEntry(videoId)

Remove a single item from the playlist.

clearPlaylist()

Remove every item from the playlist.

Reading the playlist

Method Description

load()

Return the whole playlist as an array of items.

save(playlistArray)

Write a complete playlist back to the store.

getEntry(videoId)

Return one item.

getEntryPosition(videoId)

Return the saved resume position (in seconds) for an item.

getNextVideoId(currentVideoId)

Return the id of the item that follows the given one.

Playing items

Method Description

playEntry(videoId)

Load and play the given item. This is the method the playlist itself uses, so all gates — expiry, playback window, fade-in — apply.

embedRunVideo(videoId, mode)

Load an item into the player. The lower-level call playEntry() uses. Pass 'pause' as mode to load without playing.

autoLoadFirstEntryOnReload()

Load the first stored item in the paused state. Runs at most once per player and per page load.

autoLoadFirstDone()

Report whether the first-item load has already happened for this player.

Importing, exporting and preloading

Method Description

importFromUrl(url)

Load a playlist file from a web address into the playlist.

importFromFile()

Load a playlist from a file chosen on the viewer’s computer.

exportToFile(filename)

Save the current playlist to a .json file.

backupToFile(filename)

Save a backup copy of the current playlist to a file.

preloadPlaylists(preloadList, baseUrl, playerId)

Merge one or more configured playlist files into the player on load. This is what the playlist.preload setting triggers behind the scenes.

convertVideoPlayerPlaylist(rawPlaylist, poster)

Convert a stored playlist into the shape the VideoJS playlist plugin expects.

Downloading media files

Method Description

downloadEntry(videoId)

Save the media file of one item to the download folder. Does nothing for YouTube items and streaming addresses.

downloadAllEntries()

Save every downloadable item of the active view. A second call is ignored while a batch is still running.

Searching and sorting

Method Description

buildSearchIndex()

Build the search index for the current playlist.

searchPlaylist(query)

Filter the playlist by a search term.

clearSearch()

Clear the current search and show the whole list again.

sortPlaylist(criterion)

Reorder the list, for example by watchDate, rating or episode. See Sorting for all criteria.

applyDefaultSortCriterion(data, respectUserChoice)

Apply the automatically chosen starting order.

Posters

Method Description

generatePosterForEntry(videoId)

Create a poster image for one native item by grabbing a frame.

generateMissingNativePosters()

Do the same for every native item of the playlist that has no poster yet.

Rendering the panel

Method Description

renderCurrent()

Redraw the playlist panel to match the stored playlist.

renderPlaylist()

Draw the playlist in the list layout.

renderCards()

Draw the playlist in the cards layout.

setActiveItem(videoId)

Mark an item as the one currently playing (it is highlighted).

clearActiveItem()

Remove the "currently playing" highlight from all items.

openRatingModal(videoId)

Open the star-rating dialog for one item.

openEditModal(videoId)

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

setAdapterOptions(options)

Hand the merged configuration to the playlist manager.

setPlayerID(id)

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 getPlayer(id) — not the factory — when you only want to reach a player that already exists. The factory would silently create an empty player if the id is wrong, and the mistake would be hard to spot.

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:

  1. Read the three YAML files: the defaults, your global settings, and your per-player control settings.

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

  3. Load the HTML portion of every enabled player into its placeholder on the page.

  4. Wait until the page is fully loaded and the J1 core has finished its own initialization.

  5. 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 playbackRates.values: [ 1, 2 ] really offers two speeds, not the two plus the five from the defaults.

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

videojs.css

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.

uno.css

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.

player.css

The stylesheet of the MultiPlayer module (modules/multiPlayer/css/player.css). It styles everything around the player: the header bar, the playlist panel in both layouts, the edit panel, the dialogs and the toasts.

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

--card-background

#ffffff

Background of the playlist cards and the panels.

--border-color

#d2d2d7

Borders of cards, rows and input fields.

--input-background

#E1F5FE

Background of a filled input field.

--text-color

#1d1d1f

Normal text colour.

--secondary-color

#86868b

Secondary text, for example the author line.

--shadow-color

rgba(0,0,0,.1)

Shadow under cards and panels.

--item-active-border-color

#0d6efd

Border of the item that is currently playing.

--expiry-warning-background

yellow

Background of the Expiry Date field from four weeks before the date.

--expiry-warning-color

#1d1d1f

Text colour on the yellow background.

--expiry-critical-background

red

Background of the Expiry Date field from two weeks before the date and once it is reached.

--expiry-critical-color

#ffffff

Text colour on the red background.

--playlist-cards-per-row

from playlist.cards.perRow

How many cards fit in one row. Set by the module, not by hand.

Example: change the highlight colour of the playing item
: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:                       4

Place 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:                      true

Multiple 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 multiPlayer.yml, otherwise the players compete for the keyboard:

settings:
  videoJS:
    plugins:
      hotKeys:
        enabled:                        true
        enableInactiveFocus:            false

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:                       3

A 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