Skip to content

Media Upload

The "Add content" upload sheet in PlayoutAdmin โ€” file select โ†’ transcoding profile pick โ†’ TUS upload โ†’ processing status.

  • Component: PlayoutAdmin/src/components/media/sections/AddContentSheet.jsx
  • Backend reference: domain/media โ€” full API schema, pipeline behavior, and category/passthrough rules live there, not here

API calls used

Call Used for Notes / limitation
GET /media/transcoding-profiles?category=<video\|image\|document> Populate the profile picker once a file is selected Category is derived client-side from the file extension (detectMediaType โ†’ categoryFor), not from the backend. audio/other never call this โ€” no category exists for them. See domain/media ยง Passthrough for what the returned profiles mean.
POST /media/prepare Issue the upload token; also where tenant, folder, transcodingProfileId are sent tenant/folder are hardcoded constants in this component today, not derived from the logged-in user โ€” see Limitations. transcodingProfileId is only sent if a real numeric profile id was picked; "Custom..." sends none.
TUS upload to /tusd/ The actual file bytes Suite-wide shared path, not app-namespaced โ€” must match nginx's /tusd/ block and tusd's -base-path. Not a CoreAPI route.
GET /media/?sortBy=created_at&sortDir=desc&limit=5 Find the Media row created for this upload (TUS itself doesn't return one) Matches by fileName/originalName, most-recent-first, retried for ~30s. Racy if two uploads with a colliding name finish at the same moment.
GET /media/:id (polled every 2s) Track transcodingStage until done/failed Only meaningful when a real transcodingProfileId was sent โ€” otherwise stays at whatever default the backend leaves it. Full pipeline stages/stage semantics: domain/media.

For request/response shapes, error codes, and everything about what a profile actually does (renditions vs. settings, passthrough, category rules) see domain/media and its OpenAPI spec.


Limitations

  • Tenant and folder are hardcoded (HARDCODED_TENANT = "jastv", HARDCODED_FOLDER = "marketing") โ€” every upload lands in the same place regardless of who's logged in. Should come from the authenticated user's org instead โ€” AuthAPI's access token already carries an orgs[] claim (org_id, org_slug, role, permissions) that's the natural source for tenant, it's just not decoded/used anywhere in this flow yet. Folder likely still needs a real picker.
  • "Custom..." in the profile picker isn't a real custom-config path โ€” it just means "send no transcodingProfileId." There's no way to submit ad-hoc, unsaved transcode settings from this form; a real profile has to be created first via Media Transcoding Presets.
  • Frontend media-type detection is extension-based and independent of the backend's own MIME-based classifier (media.InferMediaType) โ€” the two can disagree on an edge case; the backend's classification is the one that's actually persisted and drives behavior.
  • Media-record lookup after upload is a match-by-recency race, not an id returned directly from the upload response.
  • One file per trip through the wizard โ€” no batch upload.

Full upload flow

sequenceDiagram
    participant User
    participant Sheet as AddContentSheet
    participant API as CoreAPI
    participant tusd

    User->>Sheet: pick file
    Sheet->>Sheet: detectMediaType(filename) โ†’ categoryFor()
    Sheet->>API: GET /media/transcoding-profiles?category=<detected>
    API-->>Sheet: profiles (+ "Custom..." appended client-side)
    User->>Sheet: name + pick profile โ†’ Start upload
    Sheet->>API: POST /media/prepare {tenant: HARDCODED, folder: HARDCODED, transcodingProfileId?}
    API-->>Sheet: {token}
    Sheet->>tusd: TUS upload to /tusd/ (metadata.uploadToken = token)
    tusd->>API: POST /hooks/tusd (server-side, not seen by Sheet)
    Sheet->>API: GET /media/?sortBy=created_at (find the new record)
    loop every 2s until done/failed
        Sheet->>API: GET /media/:id
        API-->>Sheet: transcodingStage
    end
  1. Type โ€” user drops/selects a file. No network call.
  2. Configure โ€” detectMediaType + categoryFor resolve a category; GET /media/transcoding-profiles populates the picker; user names the asset and picks a profile.
  3. Upload โ€” POST /media/prepare issues a token, then a TUS upload streams the file directly to storage via /tusd/.
  4. Processing โ€” the sheet locates the resulting Media row and polls its transcodingStage every 2s until done/failed. The pipeline itself (normalize/transcode/optimize/rasterize) runs entirely server-side โ€” see domain/media for what actually happens at each stage.
  5. Done โ€” local summary only, no further calls.