Rails example on GitHub
Rails 8 with import maps and
Net::HTTP. Run bin/dev and pick a file.Quickstart
1
Add your library credentials
.env
dotenv-rails in development.2
Create the Bunny Stream module
sign_upload hashes the library ID, API key, expiry, and video ID with SHA-256. The key never leaves this module.app/models/bunny_stream.rb
rescue_from turns its errors into JSON for every controller.app/controllers/application_controller.rb
3
Add the API routes
config/routes.rb
resumable? checks that we’re still waiting on the file before re-signing an existing video.app/controllers/api/uploads_controller.rb
app/controllers/api/videos_controller.rb
4
Upload from the browser
jsDelivr’s The tus-js-client sends the credentials as headers with every request. The video ID goes into
+esm build bundles tus-js-client into one file, which an import map can pin.config/importmap.rb
X-CSRF-Token header carries the token from csrf_meta_tags. Rails rejects the POST without it.app/javascript/components/video_uploader.js
localStorage against the file, which is how a reload finds its way back to the same upload.abort() pauses. start() picks up from the last chunk we acknowledged.A 401 from the TUS endpoint means the signature doesn’t match the headers. Check that the library ID and API key belong to the same library. Re-signing keeps the upload’s original expiry, as the TUS FAQ explains.Play it once it’s encoded
We start encoding when the last chunk arrives. Poll your status route untilstatus reaches 4 (finished), 5 or 6 (failed), then embed embedUrl.
encodeProgress gives you a percentage to show in the meantime. A webhook tells your server when encoding finishes.
Before you deploy
Add abefore_action that requires a signed-in user and records who owns each video ID. In production, set the two variables on the host.
Troubleshooting
/api/uploads returns 422 with InvalidAuthenticityToken
/api/uploads returns 422 with InvalidAuthenticityToken
The layout needs
<%= csrf_meta_tags %>, and the fetch needs the X-CSRF-Token header.