Skip to content

Video Upload & Streaming

Overview

Exercise videos are stored on a CDN and delivered via adaptive bitrate streaming (HLS). The platform uses a provider-agnostic design to support multiple CDN providers without schema changes.

Supported Providers

Bunny Stream is an EU-first video hosting service with built-in HLS transcoding.

Flow:

  1. Admin uploads video via POST /v1/exercises/{id}/video
  2. Backend uploads to Bunny Stream via their Upload API
  3. Bunny automatically transcodes to multiple resolutions (360p, 480p, 720p, 1080p)
  4. Bunny generates HLS manifest (.m3u8) + thumbnail
  5. Backend stores the HLS URL, thumbnail URL, and duration

Advantages:

  • EU-first (GDPR-friendly data residency)
  • Automatic HLS transcoding (no pipeline to build)
  • Built-in CDN delivery
  • DRM support available
  • Webhook for transcoding completion

Configuration:

bash
BUNNY_STREAM_API_KEY=...
BUNNY_STREAM_LIBRARY_ID=...
BUNNY_STREAM_CDN_HOSTNAME=...

S3 + CloudFront (Alternative)

For organizations preferring AWS infrastructure:

Flow:

  1. Admin uploads video via POST /v1/exercises/{id}/video
  2. Backend uploads original to S3
  3. AWS MediaConvert transcodes to HLS (triggered by S3 event)
  4. Transcoded files stored in S3, served via CloudFront
  5. Backend stores the CloudFront HLS URL

Configuration:

bash
AWS_S3_BUCKET=...
AWS_MEDIACONVERT_ROLE=...
AWS_CLOUDFRONT_DOMAIN=...

Upload Flow

Step 1: Client Upload

Client                    Core API                    CDN Provider
  │                          │                            │
  │ POST /exercises/{id}/video                            │
  │ Content-Type: multipart  │                            │
  │─────────────────────────>│                            │
  │                          │                            │
  │                          │ Upload video file           │
  │                          │───────────────────────────>│
  │                          │                            │
  │                          │ Upload ID / Video ID        │
  │                          │<───────────────────────────│
  │                          │                            │
  │  202 Accepted            │                            │
  │  { "status": "processing" }                           │
  │<─────────────────────────│                            │

Step 2: Transcoding (Async)

CDN Provider                Core API (Webhook)
  │                            │
  │ Transcoding complete       │
  │ POST /webhooks/video-ready │
  │───────────────────────────>│
  │                            │
  │                            │ Update exercise:
  │                            │   video_url = HLS URL
  │                            │   video_thumbnail_url = thumb URL
  │                            │   video_duration_seconds = duration
  │                            │
  │      200 OK                │
  │<───────────────────────────│

Step 3: Client Playback

The frontend uses the HLS URL for adaptive bitrate playback:

javascript
// Using hls.js (browser) or native HLS (Safari/iOS)
const video = document.getElementById('exercise-video');
const hls = new Hls();
hls.loadSource(exercise.video_url); // .m3u8 URL
hls.attachMedia(video);

The player automatically selects the best quality based on the patient's network bandwidth.

S3 Key Structure

Org-Scoped Exercises

s3://{bucket}/{org_id}/exercises/{exercise_id}/
├── video/
│   └── original.mp4              # uploaded file (archived)
├── thumbnail/
│   └── poster.jpg                # auto-generated or uploaded
└── instructions/
    ├── 0/image.jpg               # instruction step 0 image
    ├── 1/image.jpg               # instruction step 1 image
    └── 2/image.jpg               # instruction step 2 image

Global Exercises (Superadmin)

s3://{bucket}/global/exercises/{exercise_id}/
├── video/
│   └── original.mp4
├── thumbnail/
│   └── poster.jpg
└── instructions/
    └── ...

Video Processing Status

The exercise tracks video processing state via the API response:

json
{
  "video_url": null,
  "video_status": "processing",
  "video_provider": "bunny_stream"
}

Possible states:

  • null — No video uploaded
  • processing — Upload complete, transcoding in progress
  • Ready — video_url is populated with the HLS manifest URL

The frontend polls or receives a push notification when transcoding completes.

File Size Limits

TypeMax SizeFormats
Exercise video500 MBMP4, MOV, WebM
Video thumbnail5 MBJPG, PNG, WebP
Instruction image10 MBJPG, PNG, WebP, SVG

Bandwidth Considerations for Patients

Exercise videos are watched during telerehab sessions, often on mobile networks. The HLS adaptive streaming ensures:

  • Low bandwidth: 360p stream (~500 Kbps)
  • Medium bandwidth: 720p stream (~2 Mbps)
  • High bandwidth: 1080p stream (~5 Mbps)

The Telemetry service monitors bandwidth during sessions via GET /v1/media/bandwidth/stats to detect quality issues and alert the patient or specialist.

Security

  • Signed URLs: Videos are served via signed CDN URLs with expiration (1 hour)
  • Token-based access: For Bunny Stream, pull zone token authentication ensures only authenticated users can access videos
  • No public URLs: Video URLs are never exposed publicly; they require an authenticated API call to obtain
  • CORS: CDN configured to allow requests only from the platform's frontend domains