#4739·srs

Feature request: Support multi-level app paths for HTTP remux (multi-camera/multi-tenant streams)

Author: tinhien11Created Sep 14, 2026Updated Sep 14, 2026

Summary

SRS HTTP remux (http_remux) rejects stream paths whose slash count differs from the mount template. This prevents multi-level app paths like tenant-1/live/0/0/AZR-001 from serving HTTP-TS/FLV/HLS — even though SRT ingest and WebRTC playback handle them fine. We request an opt-in config to support multi-level app paths.

Use Case

Multi-tenant streaming platforms with multi-camera drones need structured stream paths:

{tenant}/live/{camera_id}/{slot}/{device_id}

Example:

  • tenant-1/live/0/0/AZR-001 — drone AZR-001, camera 0, slot 0
  • tenant-1/live/1/0/AZR-001 — drone AZR-001, camera 1, slot 0
  • tenant-1/live/0/1/AZR-001 — drone AZR-001, camera 0, slot 1

The camera_id and slot segments are required to:

  • Route each camera to a separate stream
  • Segment DVR recordings per camera
  • Allow the frontend to construct playback URLs per camera

Collapsing to 2 levels (tenant-1/AZR-001-cam0-slot0) loses the structural separation that SRS routing, DVR segmentation, and stream hooks rely on.

Current Behavior

SrsLiveStream::serve_http_impl in src/app/srs_app_http_stream.cpp (lines ~1307–1316) rejects paths whose slash count differs from the mount template:

cpp
//      not-matched for "/live/show/livestream.flv"
if (srs_strings_count(upath, "/") != srs_strings_count(entry->mount_, "/")) {
    return err;  // reject
}

This is documented as intentional design — the mount template [vhost]/[app]/[stream].ext expects exactly 2 slashes, and multi-level app paths like tenant-1/live/0/0/AZR-001.ts have 5 slashes → rejected → 404.

What Works Today

Protocol Multi-level path Status
SRT ingest Works — SRT streamid is parsed independently
WebRTC/WHEP play Works — SDP negotiation uses the full stream URL
HTTP-TS remux 404 — slash count mismatch
HTTP-FLV remux 404 — same restriction
HLS 404 — same restriction

Proposed Solution

Add an opt-in config directive:

nginx
http_vhost {
    http_remux {
        mount [vhost]/[app]/[stream].ts;
        allow_multilevel_app on;  # default off — backward compatible
    }
}

When allow_multilevel_app is on, split the request path at the last slash to resolve app and stream, instead of requiring an exact slash count match:

cpp
// When allow_multilevel_app is enabled:
// Split at last slash: stream = after last slash, app = before last slash
std::string::size_type slash = upath.rfind("/");
if (slash != std::string::npos) {
    req->stream_ = upath.substr(slash + 1);
    req->app_ = srs_strings_trim_start(upath.substr(0, slash), "/");
}

This is backward compatible:

  • allow_multilevel_app off (default): existing behavior unchanged
  • allow_multilevel_app on: multi-level paths work, 2-level paths still work

Why Not Just Use 2-Level Paths?

Encoding camera/slot into the stream name (tenant-1/AZR-001-cam0-slot0) is possible but:

  • Loses structural separation for SRS on_publish/on_play hooks
  • Makes DVR segment naming ambiguous (camera/slot buried in stream name)
  • Requires custom parsing in every consumer (frontend, analytics, DVR uploader)
  • Breaks existing SRT streamid conventions that already use multi-level paths

Scope

This request is for the C++ SRS media server (src/app/srs_app_http_stream.cpp). The restriction applies to all HTTP remux outputs (TS, FLV, MP4, HLS).