Diffuse: web components that read music from wherever you keep it
A music player that connects to your cloud/distributed storage.
At a glance
- What is it?
- Diffuse is a JavaScript library of framework-agnostic custom elements for building audio players, plus an interface loader that can pull HTML from a local cache, the project's own default set, HTTP(S) URLs or AT Protocol URIs. The build runs on Deno, and the data schemas are atproto lexicons.
- Who is it for?
- Use Diffuse if you are building a browser-based player that should read a music library from a source you control, including one reached through an AT Protocol URI, and you want the player to be assembled from HTML parts rather than from a single bundled app. Do not adopt it as a finished music player for end users, because what the README describes is a library of elements and a loader, and the user-facing shell is a separate concern.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Diffuse is four things that happen to fit together
The README opens by saying that Diffuse is a few things, and the list is the most useful description of the project anywhere. It is a collection of custom DOM elements, described as framework-agnostic web components, that let you build an audio player, do audio metadata processing and browsing, list audio files and streams from various sources, APIs and servers, and define how to save or sync user data.
It is also an HTML loader that loads interfaces from the local user-data cache, from the default set that ships with Diffuse, from HTTP(S) URLs, or from AT Protocol URIs. When an interface is loaded it is supplied with optional features, which are also pieces of HTML, called facets, and those are loaded from the local user-data cache as well.
Third is a default configuration of the custom elements called the foundation. It is used throughout the default set of facets, and it is configured so that facets can communicate across tabs and frames, which is done by setting the element's group attribute.
Fourth is a collection of data schemas in the form of atproto lexicons, which live in the lexicons directory.
Put those four together and you get what the README calls cooperative and malleable software, a phrase borrowed from a linked essay on the subject. The practical meaning is that a player is not one file. It is a set of interchangeable pieces that can be replaced, re-hosted, or shared between windows, and the pieces agree on a vocabulary because that vocabulary is a published lexicon rather than an internal interface.
Interfaces, facets, and a loader that treats HTML as data
The loader is the unusual part of the design, and it is worth spending a paragraph on because it explains why the project is not just a component library.
An interface is a piece of HTML that renders a view, and it can come from four places: the local user-data cache, the default set that comes with Diffuse, an HTTP(S) URL, or an AT Protocol URI. That last option is what makes the project feel different from a typical web component package. Because the loader can fetch an interface by an atproto URI, the view can live in a repository that the player does not own, and two people pointing at the same URI can see the same interface.
Facets are the second layer. When an interface is loaded it is handed optional features, and those features are themselves pieces of HTML, so a facet can be a small element that an interface chooses to include. The default facets come from the project's own default set, while additional facets are loaded from the local user-data cache.
The consequence is that the pieces have to communicate without a shared application object. That is what the foundation is for, and the mechanism is the group attribute on an element. Setting a group makes the elements in that group able to talk to each other across tabs and frames, which is the piece of glue that lets an interface, a facet and a queue engine cooperate without the page that hosts them knowing anything about the others.
Lexicons carry the vocabulary, and they are published schemas
The fourth component of the design is the one that makes the rest coherent. Diffuse stores its data schemas as atproto lexicons, the schema format used by the AT Protocol, and the repository keeps them in a lexicons directory with a lexicon.config.js file at the root and a specs/ directory alongside.
The point of using a published schema format rather than a private JSON shape is that both sides of a boundary can be described. If an interface comes from an AT Protocol URI, the data it renders still has to mean something, and a lexicon is a way to say what that something is. It also means the schemas are reviewable, versionable artefacts instead of a changelog entry that says the field called artist was renamed.
The repository layout backs this reading up. Alongside src/, tasks/, tests/ and docs/ there is deno.jsonc, deno.lock, a _config.ts, a Caddyfile for serving, a .env file, a .zed/ directory, and CHANGELOG.md and CONTRIBUTING.md. A project with a lexicon directory, a specs directory and a build configuration file is a project that intends its data shapes to be contracts.
Nothing in the README documents the individual lexicons, so a developer cannot learn the exact schema names from the documentation alone. The files themselves are the specification, and the website at diffuse.sh is where the fuller description lives.
Consuming the elements through a script tag or the JSR package
There are two documented ways to get the elements, and the README shows both with the same queue element.
The first is the deployed instance, where the listed elements link to JavaScript files, loaded straight from the site:
<script src="https://diffuse.sh/latest/components/engine/queue/element.js"></script>
<de-queue></de-queue>The second is the JavaScript package on JSR, imported as a module and used like any other custom DOM element, either by writing HTML or by creating a Class instance:
import QueueEngine from "@toko/diffuse/components/engine/queue/element.js"
const queue = new QueueEngine()
queue.setAttribute("group", "facets")
document.body.append(queue)That second example is worth reading twice, because it shows the whole model in five lines. The element is imported from a path that mirrors the file layout of the deployed version, instantiated in code rather than written as markup, given a group attribute so it can talk to other elements across frames, and then appended to the document. The group value in the example, facets, is the same name the README uses for the default facet group.
The two routes have a practical difference. The script tag points at a path ending in latest, so the version you get is the one the site currently serves, while the JSR package can be pinned to a specific release. For a project that cares about reproducibility, the package route is the one to take.
A Deno build, with signals and workers underneath
Building it yourself needs Deno, and the README gives three commands:
deno run gen:defs:types
deno run build
deno run serveThe first generates definitions and types, the second builds, and the third serves the result locally, which is the loop you would keep open while working on a facet or an interface.
The stack list explains the shape of the code. Deno is the runtime. The user interface is built from web components, that is, custom elements. Work is shared out across web workers, and the list names shared and service workers explicitly, which is how audio work and metadata parsing stay off the main thread. State is handled with signals, currently alien-signals, with a hopeful note that the TC39 signals proposal may eventually replace it, and templates come from lit-html. Audio metadata parsing is music-metadata, and the site is built with Lume and bundled with ESBuild.
That signals remark is a small but telling detail. The README says currently alien-signals, but hopefully the TC39 proposal in the future, which means the reactive layer is a dependency the author expects to swap out. It also means the current dependency is a smaller library rather than a standardised platform API, so the version you consume today may not be the one you consume after the platform catches up.
Releases every year, then twenty months of source changes
The release history is short and regular for a while: version 3.3.0 on 2023-01-15, 3.4.0 on 2024-02-15, and 3.5.0 on 2025-02-17, one release in each of three consecutive years.
Then the pattern stops. The last push to the repository was on 2026-10-16 and the repository is not archived, so development has not stopped, but no release after 3.5.0 is published, which leaves roughly twenty months of work between the newest tag and the current source. For a library that publishes to JSR, that gap matters more than it would for an application, because the version a consumer gets from the package registry is not the same as the state of the code in the repository.
Two things are worth checking before you depend on it. The first is which version you are actually pinning, given the 3.5.0 tag and the newer commits in the tree. The second is the licence.
The licence is the one field here that is not standard. The project's licence is a custom one that GitHub cannot classify, and the details live in the LICENSE file rather than in the README. For a permissive MIT or Apache-2.0 library this question is easy to answer, and here it is not answered anywhere in the documentation you would normally read. A legal reader, not this article, has to tell you what the terms allow, and until that happens the conservative assumption for a component library is that the permissive licences people expect are not in force.
Editorial conclusion
Use Diffuse if you are building a browser-based player that should read a music library from a source you control, including one reached through an AT Protocol URI, and you want the player to be assembled from HTML parts rather than from a single bundled app. Do not adopt it as a finished music player for end users, because what the README describes is a library of elements and a loader, and the user-facing shell is a separate concern. Before you build on it, read the LICENSE file, since the licence is a custom one that GitHub cannot classify, and check the version you consume, because the newest published release is 3.5.0 from 2025-02-17 while the repository itself was pushed on 2026-10-16.
Frequently asked questions
What is Diffuse?
It is a set of framework-agnostic custom DOM elements for building an audio player, doing audio metadata processing and browsing, listing audio files and streams from various sources, and defining how user data is saved or synced. It also ships an interface loader, a default configuration called the foundation, and a set of atproto lexicons.
How do I use the Diffuse web components?
Either load an element from the deployed instance with a script tag pointing at a file under https://diffuse.sh/latest/components/, or install the JavaScript package from JSR and import it, for example QueueEngine from @toko/diffuse/components/engine/queue/element.js. You then use it like any other custom element, by writing HTML or by instantiating the class.
What does the group attribute do in Diffuse?
The foundation, which is the default configuration of the custom elements, is set up so that facets can communicate across tabs and frames, and that is done by setting the element's group attribute. Elements sharing a group can therefore cooperate without the hosting page knowing about them.
How do I build Diffuse myself?
Install Deno, then run deno run gen:defs:types to generate definitions and types, deno run build to build the project, and deno run serve to serve it locally. The build uses Deno, web components, web workers including shared and service workers, signals, lit-html, music-metadata, Lume and ESBuild.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/icidasset-diffuse)Was this useful?