Developers

PHP

Riff is built the way a feature plugin is: one track shape everywhere, read through WordPress's own APIs. Everything here is public and keeps working within a major version; anything that has to go is deprecated for a release first.

Hooks

Every filter and action has a page of its own, generated from the docblock above the line it fires on. All hooks, PHP and JavaScript, are searchable in one list.

Filters

FilterDescription
riff_about_sectionsFilters the sections of the About panel, Riff's own included: track, the cover, the title and the artist and album, and credits, what the file's tags say about who made it.
riff_can_playFilters whether the current visitor may play a track.
riff_enabledFilters whether Riff's player loads on the current front-end request.
riff_extensionsFilters the extensions Riff loads, by name: a host can leave one out, or name another plugin it has placed in Riff's extensions/ folder.
riff_protect_storageFilters whether the storage a file is kept in elsewhere was made private, when it is protected, or public again, when it isn't. A plugin that keeps uploads in object storage or on a CDN answers for its own files: true once the copy there is private (or public again), or a WP_Error to say why it couldn't be, which the Media Library shows. Without an answer, a file only on this server is moved into the protected folder, and one kept elsewhere is not protected, as moving it would leave it public there. Riff can't send a file it doesn't have, so a plugin that protects its files signs their addresses through riff_protected_url too.
riff_protected_sendFilters how a protected file is handed over: 'php' sends it from PHP; 'x-sendfile' asks Apache's mod_xsendfile or LiteSpeed to send it; 'x-accel-redirect' asks Nginx, which then needs an internal location for the protected folder (see the readme).
riff_protected_urlFilters the address a protected file is sent from instead of WordPress: a signed address on a CDN or in object storage, such as a CloudFront or S3 presigned URL or a Bunny or Cloudflare token, that stops working at $expires. Riff has checked that the visitor may have the file before it asks, and sends them on with a 302 nothing caches. Empty, the default, sends the file as riff_protected_send says.
riff_sourcesFilters the sources tracks are read from, keyed by id.
riff_together_shareFilters whether a listener who hasn't chosen shares what they play with the site's other listeners. A site where listeners shouldn't see each other unless they ask, a class of children say, returns false.
riff_trackFilters a track before the player reads it.
riff_turntableFilters whether Now Playing shows the record and the turntable. Off, it shows the cover on its own, and a touch screen on its side shows the cover beside the controls.
riff_unlock_urlFilters where a locked track sends the visitor to unlock it: a sign-up, membership or checkout page.
riff_video_firstWhether a song that has a video opens as the video. A listener's own choice of the song or the video, and a browser that saves data, come first.

Actions

ActionDescription
riff_before_playerFires before the player's markup.
riff_registerFires once a request, before the player's controls and views are first read: register them here with riff_register_control() and riff_register_view(), or take one of Riff's own away.

Functions

The bar's controls and Now Playing's views are registered on the server, as WordPress registers blocks, so the page has every button and every view before any script runs. Register them on the riff_register action; Riff registers its own there too, and any of them can be taken away. In the browser, window.riff.view() and window.riff.control() give each what it does, by its id. How the player works has the whole of it.

add_action( 'riff_register', function () {
	riff_register_view( 'credits', array(
		'label' => __( 'Credits', 'my-plugin' ),
		'icon'  => '<svg viewBox="0 0 24 24">…</svg>',
		'order' => 32,
	) );
} );

riff_register_control( string $id, array $args = array() ): bool

Registers a control in the player's bar. Call it on the riff_register action.

Parameters

$idstring
Lowercase letters, digits and dashes.
$argsarray
The control.
  • labelstring
    What it is called, for a screen reader and a pointer's tooltip.
  • iconstring
    An icon of Riff's own by name, or SVG markup.
  • orderint
    Where it sits among the others, lowest first. Default 50.
  • widebool
    Only on a wide screen; a phone's bar keeps to the track, Play and what it needs. Default false.
  • rendercallable
    Markup of its own in place of a button, for an element that is not one; it is given the id and returns one element.
  • scriptstring
    The script module that brings it to life, which Riff enqueues wherever the player is. Register the module yourself.

Return

bool Whether it was registered.

riff_unregister_control( string $id ): bool

Takes a control away, Riff's own included.

Parameters

$idstring
The control's id.

Return

bool Whether there was one.

riff_register_view( string $id, array $args = array() ): bool

Registers a view the listener opens from its button in the bar: a screen of Now Playing, or a panel beside the page. Call it on the riff_register action.

Parameters

$idstring
Lowercase letters, digits and dashes.
$argsarray
The view.
  • labelstring
    Its name: its button's, its chip's and its region's.
  • iconstring
    An icon of Riff's own by name, or SVG markup.
  • orderint
    Where its button sits among the bar's controls, lowest first. Default 50.
  • placestring
    screen to take the whole screen above the bar, as Now Playing does; side for a panel beside the page. Default screen.
  • stagebool
    For a screen that is Now Playing's own stage, the track's art or its record, rather than a region of its own. Default false.
  • widebool
    Its button only on a wide screen; on a phone its chip in Now Playing opens it. Default true.
  • rendercallable
    Markup for its region as the page is drawn; the script fills it as it opens. Given the id.
  • scriptstring
    The script module that brings it to life, which Riff enqueues wherever the player is. Register the module yourself.

Return

bool Whether it was registered.

riff_unregister_view( string $id ): bool

Takes a view away, Riff's own included.

Parameters

$idstring
The view's id.

Return

bool Whether there was one.

Sources

A source answers three questions: which posts it can read, what tracks a post holds, and how those tracks render as blocks the player takes over. The library asks every source; the first that claims a post reads it.

A plugin adds its own post type or catalogue by implementing Riff\Source and registering it on init; the library, the REST routes and the player then read it like any other.

add_action( 'init', function () {
	Riff\Sources::register( new My_Catalogue_Source() );
} );

The Riff\Source interface

public function id(): string

A short id, such as "blocks".

Return

string

public function supports( WP_Post $post ): bool

Whether this source can read the post.

Parameters

$postWP_Post
Post.

Return

bool

public function tracks( WP_Post $post ): array

The tracks in the post, in order, in the shape Tracks::schema() describes.

Parameters

$postWP_Post
Post.

Return

array[]

public function html( WP_Post $post ): string

The tracks rendered as figures carrying data-riff-track, which riff.js turns into rows.

Parameters

$postWP_Post
Post.

Return

string

public function where(): string

A WHERE clause fragment (without a leading AND) matching posts this source can read, or an empty string when the source cannot say from SQL alone.

Return

string

Riff\Sources::register( Source $source ): void

Registers a source. The core blocks source registers itself at boot; a plugin registers its own on init.

Parameters

$sourceSource
Source.

Riff\Sources::unregister( string $id ): void

Removes a source.

Parameters

$idstring
Source id.

The track schema

One shape for a track: the REST field and routes, the block markup and the player script all use it, and WPGraphQL carries all of it. It is Riff\Tracks::schema(), version 1, and a track still matches it after the riff_track filter.

PropertyTypeDescription
idstringThe attachment ID, or "url:" and a hash for a file outside the Media Library.
srcstring
typestring One of audio, video.
titlestring
artiststring
albumstring
yearintegerThe year from the file's tags, or 0.
discintegerWhich disc of a set the track is on, from the file's disc number tag (1 for "1/2"), or 0 when the tags do not say.
durationnumberSeconds, 0 when unknown.
langstringThe language of the title and artist, as a lang attribute takes it, from the file's tags; empty when the file does not say.
formatstringThe container, lower case, as WordPress read it from the file: mp3, flac, m4a. Empty when unknown.
bitrateintegerKilobits per second, 0 when unknown.
losslessbooleanWhether the encoding is lossless.
bitsintegerBits per sample, as WordPress read it from the file; 0 when the format does not say, as MP3 does not.
rateintegerSamples per second, 0 when unknown.
levelsstringThe track's loudness over time as digits 0 to 9, one per sample, when something has measured it; the drawn cover puts it in the groove. Empty otherwise.
artarrayArtwork, smallest first.
art[].srcstring
art[].sizesstring
art[].altstringWhat the picture shows, for a screen reader: the image's alt text, or the album and artist a drawn cover names.
captionsarrayText tracks for a video: the WebVTT file its Captions field names, or the tracks its video block carries.
captions[].srcstring
captions[].labelstring
captions[].srclangstring
captions[].kindstring One of captions, subtitles, descriptions.
videoobject | null
video.srcstring
video.posterstring
video.captionsarray
video.captions[].srcstring
video.captions[].labelstring
video.captions[].srclangstring
video.captions[].kindstring One of captions, subtitles, descriptions.
postobject | null
post.idinteger
post.titlestring
post.urlstring
lockedbooleanWhether the current visitor may not play the track, as riff_can_play decided. A locked track has no src.
unlockstringWhere a locked track sends the visitor to unlock it, from riff_unlock_url. Empty otherwise.

Block Bindings

riff/track (“Riff track”) is a Block Bindings source: a Heading or Paragraph bound to it shows a track's details, and an Image bound to it shows the cover, with no Riff block on the page. The binding's key picks the detail and id names the attachment; with no id, it is the post's first track. Every value is the track as the track schema has it, after riff_track, and a named track shows only where the media route would show it. In the editor the block shows that it is bound; the value is filled on the front end.

<!-- wp:heading {"metadata":{"bindings":{"content":{"source":"riff/track","args":{"key":"title","id":123}}}}} -->
<h2 class="wp-block-heading"></h2>
<!-- /wp:heading -->

<!-- wp:image {"metadata":{"bindings":{"url":{"source":"riff/track","args":{"key":"cover","id":123}}}}} -->
<figure class="wp-block-image"><img src="" alt=""/></figure>
<!-- /wp:image -->
KeyShows
titleThe track’s title.
artistThe artist.
albumThe album.
yearThe year, or nothing when the file has none.
durationThe length as the player shows it, such as 4:32, or nothing when unknown.
qualityThe format and rate as the player’s badge shows them, such as MP3 · 128 kbps or FLAC · 24-bit 96 kHz.
coverThe address of the largest artwork: the file’s own, else its post’s, else the cover Riff draws. For an Image block’s url.

Abilities

Through the Abilities API, AI tools and other plugins list the library and keep a listener's likes and playlists. Each ability runs its riff/v1 route as a REST request, so it answers what the route answers and is allowed what the route allows; the ones about likes, history and playlists need a signed-in user. Nothing plays through an ability: playback happens in a browser.

$albums = wp_get_ability( 'riff/albums' )->execute( array( 'search' => 'Compositions' ) );
AbilityWhat it doesInput
riff/tracksLists the tracks the requester can see, searchable by title, artist or album, or those on one album or by one artist, a page at a time.search: Words to search for.
album: Only the tracks on this album, by term ID, in album order.
artist: Only the tracks by this artist, by term ID.
orderby: By title, or the most recently added first.
page: Page of results, from 1.
per_page: Tracks per page, up to 100.
Read only
riff/albumsLists the albums with a track the requester can see, by name, each with its artist, artwork and track count, searchable by name.search: Words to search for.
page: Page of results, from 1.
Read only
riff/artistsLists the artists with a track the requester can see, by name, each with a track count, searchable by name.search: Words to search for.
page: Page of results, from 1.
Read only
riff/libraryLists the posts with music the requester can see, newest first, each with its title, artwork, track count and length, a page at a time.page: Page of results, from 1.Read only
riff/likesLists what the signed-in user has liked: tracks, albums and artists, newest first.–Read only
riff/historyLists the tracks the signed-in user has played, most recent first.–Read only
riff/playlistsLists the signed-in user's playlists, each with its tracks.–Read only
riff/likeAdds a track, album or artist to the signed-in user's likes.kind (required): What to like: a track, an album or an artist.
id (required): Its ID: an audio attachment, an album term or an artist term.
Writes
riff/unlikeTakes a track, album or artist out of the signed-in user's likes.kind (required): What to like: a track, an album or an artist.
id (required): Its ID: an audio attachment, an album term or an artist term.
Writes
riff/add-to-playlistAdds tracks, by audio attachment ID, to the end of one of the signed-in user's playlists.playlist (required): The playlist's ID.
tracks (required): Audio attachment IDs, in the order to add them.
Writes
riff/remove-from-playlistTakes the track at a place in one of the signed-in user's playlists out, counting from 0.playlist (required): The playlist's ID.
index (required): The track's place in the playlist, from 0.
Writes

The Listen block

riff/app: Your music library and the player, on this page. Every post with audio or video is listed, and the music keeps playing while you browse. One per page. It supports align, color, typography, spacing, __experimentalBorder, shadow.

<!-- wp:riff/app {"align":"full"} /-->

Anywhere blocks are not an option, the [riff] shortcode does the same.

[riff]

[riff], the same as the block.

Return

string