Playing Media from Walrus
You can play audio and video stored as Walrus blobs directly in a browser. Point an HTML media element, such as <audio> or <video>, at a Walrus aggregator URL, and the browser streams the blob as it plays. No download step or plugin is required.
Set $AGGREGATOR to an aggregator endpoint from the Network Reference.
How to play a blob in a media element
A blob read endpoint is an HTTP URL, so you can use it as the src of a <video> or <audio> element:
<video controls src="https://aggregator.walrus-mainnet.walrus.space/v1/blobs/<BLOB_ID>"></video>
The browser fetches the blob from the aggregator and plays it. For the read paths and their exact shapes, see Reading Blobs with the HTTP API.
Why does the browser download a blob instead of playing it?
When you read a blob by its blob ID, the aggregator response does not include a content type from your metadata. The browser infers the type instead. Browsers infer media types well in most cases, but the aggregator deliberately blocks inference of dangerous executable types, such as JavaScript, to protect users.
If a blob URL shows raw bytes or garbled text in the address bar instead of a player, the browser treated the response as generic data rather than a media type.
To make playback reliable, read the blob by object ID and set the content-type attribute when you store it. The aggregator returns recognized attributes, including content-type, as HTTP response headers on the by-object-id read path:
<video controls src="https://aggregator.walrus-mainnet.walrus.space/v1/blobs/by-object-id/<OBJECT_ID>"></video>
For the full list of recognized attributes and how to set them, see Reading by object ID and Managing blobs.
How to seek within media with byte ranges
Aggregators support the HTTP Range header, which is what lets a browser seek within a video without downloading the whole file first. When you send a Range header with a byte range, the aggregator returns 206 Partial Content with just that range:
$ curl -H "Range: bytes=0-999" "$AGGREGATOR/v1/blobs/<BLOB_ID>"
There is also a dedicated endpoint that takes the range as query parameters. Unlike a Range header request, a successful response from this endpoint uses status 200 OK rather than 206 Partial Content, so a CDN can cache it:
$ curl "$AGGREGATOR/v1/blobs/<BLOB_ID>/byte-range?start=0&length=1024"
A byte-range read verifies only the consistency of the bytes it fetches, not the whole blob. This gives you faster partial reads with weaker integrity guarantees than a full read. Use a full read when you need to verify the entire blob.
Does cross-origin playback need CORS?
A plain <video src> or <audio src> element loads media cross-origin without extra configuration. If your application instead fetches blob bytes with JavaScript (for example, fetch followed by URL.createObjectURL, or drawing a frame to a <canvas>), the request is subject to the aggregator's cross-origin resource sharing (CORS) policy, which depends on how that aggregator and any CDN in front of it are deployed. If a cross-origin fetch fails, prefer the direct-src approach, or read through an aggregator you control.
References
Byte-range query parameters
On the byte-range endpoint, start is a 0-indexed, inclusive byte position and length is the number of bytes to read, not an end offset. The aggregator reads the range [start, start + length), so start=0&length=1024 returns 1024 bytes starting from position 0.
The aggregator does not truncate a request that runs past the end of the blob. It rejects any of the following requests with an error:
lengthis 0.start + lengthoverflows.- The range
[start, start + length)extends beyond the blob's size. The error readsbyte range out of bounds.
Unlike a Range header request, a successful response from this endpoint uses status 200 OK rather than 206 Partial Content, so a CDN can cache it.