Skip to content

rtsp source: add rtspScale option to inject RFC 2326 Scale header on PLAY#5800

Open
Adiel-Sharabi wants to merge 1 commit into
bluenviron:mainfrom
Adiel-Sharabi:feature/rtsp-scale-header
Open

rtsp source: add rtspScale option to inject RFC 2326 Scale header on PLAY#5800
Adiel-Sharabi wants to merge 1 commit into
bluenviron:mainfrom
Adiel-Sharabi:feature/rtsp-scale-header

Conversation

@Adiel-Sharabi
Copy link
Copy Markdown

Summary

Adds a new per-path option rtspScale for the RTSP static source. When set, the value is sent as the Scale header in the PLAY request, per RFC 2326 §12.34.

  • Negative values play in reverse: -1.0 = realtime reverse, -2.0 = 2x reverse
  • Values > 1 fast-forward; values 0 < x < 1 play in slow motion
  • Default empty = header omitted = current behavior (backward-compatible)

Why

I'm integrating MediaMTX as the broker between a Windows access-control system and a Nx Meta VMS server. The VMS honors Scale: -1.0 to play archive recordings in reverse, which the customer needs for incident review (operator scrubs backwards through footage).

gortsplib.Client.Play(*headers.Range) doesn't expose a Scale parameter and doPlay() is unexported, so the cleanest way to inject the header is through the existing OnRequest hook on the static-source Client. When the request method is PLAY and rtspScale is non-empty, we set the header before the request leaves the client.

Other VMS servers (Pelco, Genetec, some Milestone deployments) follow the same RFC and would benefit too.

What's in the change

File Change
internal/conf/path.go New RTSPScale string field on Path (json: rtspScale)
internal/staticsources/rtsp/source.go OnRequest hook injects Scale header on PLAY when RTSPScale != "" (10 lines)
internal/staticsources/rtsp/source_test.go TestScale — verifies the header lands on the server side
mediamtx.yml Documents the new field next to rtspRangeStart

Total diff: 4 files, 107 insertions, 0 deletions. No deprecations, no behavior change for users who don't set the field.

Example

paths:
  reverse-archive:
    source: rtsp://user:pass@vms-server:7001/camera-uuid?pos=2026-05-25T12:00:00.000Z
    sourceOnDemand: yes
    rtspScale: "-1.0"   # play backwards in realtime

Verification

  • TestScale passes (go test ./internal/staticsources/rtsp/ -run TestScale)
  • Verified end-to-end against a real Nx Meta 6.1.1 server + Axis M5013 camera: server returns 200 OK to the PLAY with Scale: -1.0 and streams the (server-side-interpreted) reverse playback.
  • Backward compatibility: omitted/empty rtspScale produces identical RTSP traffic to current behavior.

Open question for maintainers

Naming: I went with rtspScale to match the existing rtspRange{Type,Start} family. Happy to rename to rtspPlayScale or similar if you prefer.

Test plan

  • go test ./...
  • Manual: configure a path with rtspScale: "-1.0" against any RTSP server that supports Scale; observe the header in the PLAY exchange (e.g. via tcpdump -A or the server's debug log)
  • Confirm empty/omitted field produces identical traffic to current behavior

@codecov
Copy link
Copy Markdown

codecov Bot commented May 25, 2026

Codecov Report

❌ Patch coverage is 60.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 62.59%. Comparing base (5367afa) to head (2b38dd4).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
internal/staticsources/rtsp/source.go 60.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #5800   +/-   ##
=======================================
  Coverage   62.59%   62.59%           
=======================================
  Files         220      220           
  Lines       18779    18783    +4     
=======================================
+ Hits        11754    11758    +4     
- Misses       6060     6061    +1     
+ Partials      965      964    -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…PLAY

Adds a new per-path option `rtspScale` for the RTSP static source. When
set, the value is sent as the `Scale` header in the PLAY request, per
RFC 2326 §12.34. Negative values play in reverse (-1.0 = realtime
reverse, -2.0 = 2x reverse), values > 1 fast-forward, 0 < x < 1 plays
in slow motion. Default empty = header omitted = current behavior.

Useful when the upstream RTSP server is a VMS (Nx Witness, Pelco,
Genetec, etc.) that honors Scale for archive playback. gortsplib's
Client.Play(*headers.Range) doesn't accept a Scale parameter, so the
header is injected via the existing OnRequest hook when Method == PLAY.

Backward-compatible: no behavior change when rtspScale is empty.

- internal/conf/path.go:                  add RTSPScale string field to Path
- internal/staticsources/rtsp/source.go:  inject Scale header in OnRequest
- internal/staticsources/rtsp/source_test.go: TestScale verifies the header is set
- api/openapi.yaml:                       add rtspScale to PathConf schema (keeps go2api lint in sync)
- mediamtx.yml:                           document the new field

Verified against a Nx Meta 6.1.1 server with a real Axis IP camera —
the server returns 200 OK to the PLAY with Scale: -1.0 and streams
reverse playback.
@Adiel-Sharabi Adiel-Sharabi force-pushed the feature/rtsp-scale-header branch from 7430bf3 to 2b38dd4 Compare May 26, 2026 04:33
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.

1 participant