| No full-page client-side navigation in core, and none for a classic theme | Where the site turns client navigation on, Navigation::region() marks a block theme's .wp-site-blocks a router region (riff/page) and carries over what the router leaves as the first page had it: the body's classes, the canonical and short links and the admin bar's edit links. A classic theme has no template to mark, so its links still load the next page, and the player picks the queue up where it was. | Core's router swaps only the regions a page marks, and core's own full-page module sends every link through it but marks none. The player sits outside the template, so marking the template keeps the one <audio> playing across a link. Once core navigates the whole page, Riff can stop marking the region itself. | Gutenberg #60951: full-page client-side navigation, closed into the Interactivity API's release iterations (#83078 for 7.2) and still experimental; Gutenberg #80298: keeping an element across a navigation | In Riff |
| The media route renders an attachment's empty caption from its description, as a post's excerpt is made from its content, so an audio file without one has a caption of its own address | On wp/v2/media, an audio file without a caption has an empty caption.rendered, through rest_prepare_attachment | The About panel ends a track's credits with its caption, the credit line an archive's recording is given; a made-up caption would credit every other track with its file's address. | None filed | In Riff |
| No link between a song and its video | _riff_video post meta on the audio attachment, set from the Media Library's Video version field | Core has no relation between two attachments. | None filed | In Riff |
| No measure of a song's loudness over time | riff_levels in the song's _wp_attachment_metadata: digits 0 to 9, at most 240, measured by wp riff levels with ffmpeg where the server has it, as the demo albums' are | A drawn cover puts the music in its groove. WordPress reads a file's tags and length on upload but nothing of its sound, and keeps what it read about a file in its metadata, so the levels sit beside it. | None filed | In Riff |
| No smaller version of a song to stream | _riff_stream post meta on the audio attachment, the ID of another audio attachment the player streams in its place, set from the Media Library's Streaming version field or made by wp riff transcode (an MP3 by ffmpeg, where the server has it, uploaded through media_handle_sideload() to the song's post and without its tags). A file named as another's version is left out of Tracks, search and the catalogue. A service that streams from elsewhere sets the track's src in riff_track instead. | A lossless upload is several times the size of what a listener on a phone can tell from it, and the streaming services send a smaller copy and keep the original. Core keeps one file per audio upload, has no relation between two attachments, and no transcoder; image sizes are the nearest thing, and are for images only. | None filed | In Riff |
| No captions for a video outside a post | _riff_captions post meta on a video attachment, pointing at a WebVTT file in the Media Library, set from the Media Library's Captions field | The video block keeps its text tracks on the block, so a video version chosen in the Media Library, which no block holds, has nowhere to name one. A video block's own tracks are read as they are. | None filed | In Riff |
| No chord chart for a song | _riff_chords post meta on the audio attachment, registered with register_post_meta(): the chart in ChordPro, with LRC's times on its lines and chords, set from the Media Library's Chords field and read as riff_chords on wp/v2/media, by Riff Chords, a plugin beside Riff (#685). The song's parts are the chart's own sections ({start_of_verse} and the rest), not anything stored apart from it. | A file's tags have no chord frame for WordPress to read, as they have the lyrics Riff Lyrics shows, so the chart is kept on the file it belongs to, as _riff_video is, and needs no table. | None filed | In Riff Chords |
| The file's language tag is read and dropped | language in _wp_attachment_metadata, kept from getID3's tags on upload through the wp_read_audio_metadata filter | A title in another script needs a lang attribute for a screen reader to pronounce it, and the file's tag says what it is. wp_add_id3_tag_data() keeps an MP3's TLAN as written, a three-letter code such as fra where lang wants fr, and reads only ID3, so a FLAC, Ogg or M4A file's tags, the language among them, are not kept at all. | Not yet | In Riff |
| A FLAC, Ogg or M4A file's tags are read and dropped | Media::tags() on the wp_read_audio_metadata filter keeps them in _wp_attachment_metadata under the names ID3 gives the same tags (albumartist as band, date as year, tracknumber as track_number, discnumber as part_of_a_set), leaving any WordPress kept | wp_add_id3_tag_data() reads only ID3, so these files keep no title, artist or album, and the catalog has nothing to file them by. | Core #66417 | In Riff |
| No waveform for an upload | Levels from the demo seed only; uploads have none yet | Drawing a waveform otherwise means fetching the whole file in the browser, as the Playlist block does. | Gutenberg #84712: measure peaks once at upload | Proposed upstream |
Albums and artists can't be browsed or queried; they sit in serialized _wp_attachment_metadata | riff_album and riff_artist taxonomies on attachments, filed from the file's album, artist and band tags whenever its metadata is saved; an album's artist is riff_album_artist term meta; _riff_catalog marks a file filed, and a background job files a library from before, walking it by ID from where its last batch stopped, which riff_catalog_backfill_from keeps (wp riff catalog does it at once) | Taxonomies on attachments are native: the Media Library shows them as columns and filters, and REST and WP_Query can query them. A track's edit screen has no box for either: core's tag box splits names on commas, which artist and album names are full of, so the core Metadata box's Artist and Album are where a track is filed. Search and browsing need something queryable (#343, #366). | Not yet | In Riff |
| No place for an image a plugin generates | A drawn cover, once drawn, is kept as an SVG file in uploads/riff-art/{version}/, and pages link to the file; riff_art_stamp names the drawing, and moves when a post, a track's tags or levels, or an album changes. The folder before stays for a page cached a moment ago; a post that stops being public takes every folder. | Drawn at ?riff-art, every cover started WordPress for each request, and a signed-in listener passes every page cache, so one Home view queued about twenty requests on the host's few PHP workers. Core keeps an attachment's image sizes as files beside it for the same reason, but a drawn cover is no attachment. | Not yet | In Riff |
| No way for a plugin to ship parts of itself that a site can turn off one by one | Extensions: plugins of the monorepo that plugins/riff/extensions.json names, copied into riff/extensions/<name>/ by a release and loaded by Riff as it loads, each unless it is active as its own plugin, off, or needs a newer Riff; riff_extensions keeps each site's on or off, and on a network riff_extensions_network keeps the network's, with a lock that makes it every site's. Turned on and off on Settings › Riff, and Network Admin › Settings › Riff (Extensions) | Riff's own features are plugins that use Riff's public APIs, as anyone's can, and a site that does not want one should not load it. WordPress's plugins screen would show each as a plugin of its own, to install and update apart from the Riff it was made for. | None filed | In Riff |
| No colour for an image | _riff_tint attachment meta on a cover image: its most colourful shade, read from the thumbnail's pixels the first time an album page shows it (#396) | Standalone mode tints an album's header from its cover, as the streaming apps do. Reading the pixels on every page view would be slow, and the colour belongs to the image. | Not yet | In Riff |
| No artwork for an album | Planned: term meta on riff_album pointing to an image attachment | The cover stays an image in the Media Library. | Not yet | Planned |
| No picture for an artist | riff_artist_image term meta on a riff_artist term, pointing at an image in the Media Library, set from an Image field on the artist's edit screen (#397) | The artist page leads with a picture, as the apps' do. The picture stays an image in the Media Library; without one, the artist's newest album cover is used. | Not yet | In Riff |
| Playlists that belong to a listener, not to the site | The riff_playlist post type: private, out of archives, feeds and search, listed under Media in the admin, opening in the block editor, with its tracks in core's Playlist block; made and changed through riff/v1/users/me/playlists (#401) | A playlist is a post holding a Playlist block, so it needs no table, keeps its order and duplicates as the block does, and edits in the editor. Its own type keeps listeners' private lists out of the blog and its Posts grid, and lets a listener own one without the right to publish posts. | Not yet | In Riff |
| No likes or listening history for a user | The riff_likes and riff_history user options (user meta under the site's table prefix, so each site on a network keeps its own), read and written through riff/v1/users/me/likes and riff/v1/users/me/history; a visitor's stay in their browser as riff-likes and riff-history, and join their account when they sign in (#400) | Likes belong to a user and are a short list of IDs, which user meta holds without a table of its own. Visitors have no user, so their browser keeps theirs until they have one. | Not yet | In Riff |
| No way to install a site's player as an app | A web app manifest at a Listen page's app/ address, and that address rendering the player outside the theme (#379) | Browsers install a page that names a manifest; WordPress has no manifest of its own. | Not yet | In Riff |
| No private site: a post can be private, but the site and its files stay public | Private site under Settings › Reading: every front-end and REST address asks that the visitor can read the site, its audio and video go into Protect's folder, and core's own search-engine setting is turned on. Images keep their public addresses (Private_Site) | An artist's library shouldn't be public until they say so. | Not yet | In Riff |
| Every upload is a public file, whatever post it belongs to | An opt-in Protected box that moves an audio or video file into a folder the web server refuses, and serves it at ?riff-file=ID to whoever may play it, with byte ranges, or, with a CDN or storage plugin answering riff_protected_url, sends them on to a signed address there once they are checked; a file kept off this server, in object storage or after an offload plugin removed it, is protected only when the plugin that keeps it answers riff_protect_storage by making its copy there private; _riff_protected attachment meta keeps the file's original place, riff_protected_dir names the folder, and the riff_protected_open transient keeps for a day whether the web server was found sending it, so the Media Library can warn without asking again. A host can keep the folder outside the web root with RIFF_PROTECTED_PATH or riff_protected_path, as WordPress has no private place for uploads, and core's own _wp_attached_file then holds the file's absolute path, as it does for any file outside uploads; the riff_protected_folders option lists the folders files may still be in while the riff_protected_move cron event moves them, riff_protected_move_state keeps how far it got, riff_protected_move_lock keeps cron and WP-CLI from moving at once, and _riff_protected_moving attachment meta names where one file is going, so a move cut short is finished rather than lost (#406) | Hiding a private post's tracks means little while the file plays for anyone with its address. Core serves uploads straight from disk and has no permission check on a file. | Core #17255: statuses for media, which restricted access was closed as a duplicate of | In Riff |
| No passkeys, and no sign-in without a password | Riff Sign In, a plugin beside Riff: riff_passkeys user meta holding each passkey's public key, riff_passkey_id for the lookup and riff_passkey_handle for the authenticator's handle; a code sent to an email as a transient for ten minutes (#431) | Core signs in with passwords, and application passwords are for apps. Listeners of a music site should not need a password to keep their likes. | Core #55950: passwordless sign-in with passkeys | In Riff Sign In |
| No way to see who else on the site is listening, or to listen along | With the Presence API feature plugin active, a row for each signed-in listener who is playing, riff-{user ID}, in its riff/listening room: their queue as track IDs, which of them plays, and a schedule: where the music is at a time on the server's clock and whether it plays from then, a change by a listener someone follows due a moment ahead so every follower makes it at once. Read and written through riff/v1/together, which turns the IDs back into the tracks each reader may see. Who follows a listener is a room of its own, riff/session:{user ID}, one row for each follower while they follow (#687) | Presence is written every few seconds and gone in minutes. In options or user meta that would invalidate caches site-wide, which is why the Presence API keeps it in a table of its own with an expiry. Its own REST routes need edit_posts, so a listener can't call them, and Riff calls its PHP API behind a route of its own. | The Presence API, proposed for core | In Riff, with the Presence API |
| No bulk import from a folder | wp riff import, a thin layer over media_handle_sideload() (#367), which takes a zip too; a zip uploaded to the Media Library and attached to no post is imported the same way, its albums found again by its attachment rather than its path (#821) | Every file goes through core's own upload path, so its tags and artwork are read the way an upload's are. Most music arrives as a zip, and core keeps an uploaded zip as a file to download. | Not yet | In Riff |
| No import from the archives that publish recordings for reuse | wp riff archive, which reads the Library of Congress's and Europeana's JSON APIs, reads each item's own rights statement and takes only the public domain mark, CC0, CC BY and CC BY-SA, and from the Library of Congress, which says it in words, what it knows of no U.S. copyright in, then downloads what it takes with wp_safe_remote_get() and imports it through Import::folder(). Each track's credit is its caption, and its source, licence and credit its description, the attachment's own fields; the album post opens with the credit, and the About panel ends its credits with the caption (#823) | Rights are per item, and a site made from an archive has to credit it. WordPress's importers bring in another site's posts; none reads an archive's catalogue or its rights. | None filed | In Riff |
unzip_file() checks for disk space only during WP-Cron, sets no limit on what an archive unpacks to, reads each file whole into memory, and falls back to PclZip, which inflates the whole archive into memory, for a zip ZipArchive can't read | Before unpacking a zip, Riff opens it with ZipArchive and refuses one that can't be read whole, that declares more than ten times its own size or more than the disk has free, or that holds a file larger than PHP's memory has left, and has unzip_file() use ZipArchive; on a server without PHP's zip extension a zip is refused | Music hardly compresses, so a real album unpacks to about its own size; a zip that unpacks to far more is built to fill the disk or the memory. A lossless track can be larger than PHP's memory, and reading it whole would end the request with a fatal error rather than say why. | Not yet | In Riff |
| No tune for a song | _riff_sing_melody post meta on the audio attachment: the melody in UltraStar's text format, the open one karaoke games read (each syllable with its start, length and pitch), set from Riff Sing's Melody field in the Media Library and read through riff_sing_melody on wp/v2/media; or _riff_sing_midi, the ID of a MIDI file in the Media Library whose vocal track is the melody, set from the Melody file field and read as the file's address through riff_sing_midi | Riff Sing scores singing against the song's melody. Core keeps what it reads from a file in its metadata, and a file's tags have no melody; post meta is where an attachment keeps what someone wrote about it, as the Video version and Captions fields do. A melody from a score stays the MIDI file it was exported as, uploaded like any other file, as captions stay a WebVTT file. | None filed | In Riff Sing |
| No notion of a site's Listen page | The riff_listen_page transient: the ID of the oldest published page holding the Riff block or the Listen template, kept for a day and forgotten whenever a page changes | About links an album and an artist to their pages in the Listen app from any page with the player, and finding the page takes two queries; core keeps a page for a purpose only for its own (the front page, the posts page, the privacy policy). | None filed | In Riff |
| No way to tell a file was already uploaded | _riff_import_hash attachment meta (the file's SHA-1), and _riff_import_folder post meta on an album post | Running an import again must not duplicate the library, and new tracks must join their album. Core has no content hash on attachments. | Not yet | In Riff |
A real AAC .m4a is refused: core lists m4a as audio/mpeg, then the file's sniffed type, audio/mp4, doesn't match; .opus isn't listed at all | m4a is allowed as audio/mp4 and opus as audio/ogg on any upload (Import::mimes()). | Both are common in music libraries, and the player adds music through the same route as any upload. On a network, its list of allowed files still has the last word. | Not yet | In Riff |