Skip to content

Store cache and inline playback headers on MP4 outputs - #11

Open
bluebird42 wants to merge 1 commit into
mainfrom
feat/mp4-cache-headers
Open

bluebird42 wants to merge 1 commit into
mainfrom
feat/mp4-cache-headers

Conversation

@bluebird42

@bluebird42 bluebird42 commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Public MP4 and fMP4 outputs (progressive renditions, HLS init.mp4 and .m4s segments) are served directly from object storage or the per-space CDN. Until now they were written without a Cache-Control header, so browsers and the CDN had to fall back to heuristic caching.

MaveCore.Media.Storage now adds two headers when it writes a public object with content type video/mp4 or audio/mp4:

Key Cache-Control Content-Disposition
Versioned ({embed}/v{n}/…) public, max-age=31536000, immutable inline
Unversioned (legacy {embed}/…) public, max-age=86400 inline

Versioned keys are never rewritten with different bytes, so they can be cached for a year without revalidation. Unversioned legacy keys can be replaced in place. A short lifetime keeps a stale or mixed byte-range cache from outliving a replacement.

The headers are applied in every public write path:

  • put/6 and put_file/6 (and their *_public wrappers)
  • multipart initiation, used by direct booster uploads and cross-profile copies
  • presigned_put_url/6 for booster-published HLS files. These headers are signed, and the booster already sends the returned headers unchanged.

Private writes (public: false) and non-MP4 objects such as manifests, playlists and captions are unchanged.

Private playback

Private media redirects to a signed storage URL. Those URLs now pass response-cache-control=private, max-age=<ttl>, which replaces the stored public policy for that response. This way a shared cache can't keep a signed response longer than the signature, and the response never gets public caching.

Notes

  • Only objects written from now on get the headers. Existing objects keep their current metadata. Same-profile server-side copies keep the source object's metadata.
  • Accept-Ranges and CORS are unchanged: storage handles range requests itself, and CORS is already configured through bucket rules.
  • MP4 outputs already use +faststart.

Testing

  • mix test (1197 passed)
  • mix credo --strict
  • mix precommit
  • New storage tests cover versioned and unversioned keys, audio/mp4 segments, private writes, multipart initiation and signed PUT headers. The playback controller test checks the private response-cache-control override.

Public MP4 and fMP4 objects are served straight from object storage or a
CDN, so the caching policy has to live on the object itself. Versioned
output keys get a one-year immutable lifetime; unversioned legacy keys can
be rewritten in place and get 24 hours. Private signed media URLs override
the stored policy so a shared cache never outlives the signature.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@davidvanleeuwen

Copy link
Copy Markdown
Member

Depending on your CDN provider and configuration, caching may already be handled through fallback rules. Explicit cache headers can still provide useful defaults for both CDN and browser caching. I'll take a closer look at the proposed cache durations. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants