Skip to content

fix(server): classify RTSP/streaming URLs as streaming, not webpage#3071

Merged
vpetersson merged 4 commits into
masterfrom
fix/rtsp-streaming-url-classification
Jun 11, 2026
Merged

fix(server): classify RTSP/streaming URLs as streaming, not webpage#3071
vpetersson merged 4 commits into
masterfrom
fix/rtsp-streaming-url-classification

Conversation

@vpetersson

@vpetersson vpetersson commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Issues Fixed

Not tied to a tracked issue. Adding an RTSP (or HLS/DASH) URL through the Add asset → From URL tab created the asset as mimetype='webpage', so the viewer handed it to QtWebEngine instead of the video player and the stream never played — even though the modal advertises "streaming URLs are all supported." Regression from the React→Django frontend migration (#2818), whose new assets_create handler dropped the scheme check that the old React getMimetype had.

Description

  • Add is_streaming_uri() to anthias_common.remote_video, reusing the existing stream-scheme and manifest-extension (.m3u8/.mpd/.m3u/.ism, http(s) only) sets so it can't drift from is_downloadable_remote_video.
  • assets_create now classifies stream URLs as mimetype='streaming' (after YouTube, before the extension heuristic) and applies the dedicated default_streaming_duration window — previously dead config.
  • Reject rtmp:// at validate_url (gates the UI form and the API) with a clear "use RTSP/HLS/DASH" message, and drop it from _STREAM_SCHEMES. On-device testing showed RTMP renders black on every board: Qt6's QMediaPlayer sets a timeout option the rtmp protocol misreads as TCP listen mode, so the open fails (libavformat itself plays rtmp fine). Rather than let operators add a stream that never shows, we don't accept it.
  • Regression tests for the classifier, the rtmp rejection, and the form handler.

Testing

Unit: classifier + form-handler + rtmp-rejection tests; full suite green, ruff clean.

On-device, full stack, across the entire testbed. An ffmpeg-fed feed (Big Buck Bunny) was streamed from a LAN host via mediamtx (RTSP) and nginx (HLS/DASH); playback was verified from playback-stats.log (position-ms advancing + frames-rendered climbing), HW-decoder fds, and screenshots.

Board (decode) RTSP H.264 RTSP H.265 HLS DASH RTMP
x86 (SW) ✅ visual+reader ❌ QtMultimedia FormatError¹ ✅ played to EOF ✅ progressive+visual ❌ rejected
Pi 5 ✅ frames advancing HW (/dev/video19, Hantro G2) ❌ rejected
Pi 4 HW (/dev/video10) ❌ rejected
Rock Pi 4, 1 GB (SW, 360p) ✅ no OOM ❌ rejected

RTSP — the regression target — plays on every board, with hardware decode on the Pi 4 (H.264) and Pi 5 (H.265). HLS/DASH play on x86 (HLS needs a Range-capable HTTP origin — any real server/CDN).

¹ x86 H.265 over RTSP fails inside QtMultimedia (FormatError, 0 frames) even though x86's own libavcodec decodes the same stream fine over both UDP and TCP, and H.264-over-RTSP works on x86. It's an x86 QtMultimedia HEVC limitation, unrelated to this change.

Known follow-up (pre-existing, out of scope)

  • srt/udp/mms are recognised by is_streaming_uri but rejected by validate_url, so they're unreachable end-to-end (untouched here).

Checklist

  • I have performed a self-review of my own code.
  • New and existing unit tests pass locally and on CI with my changes.
  • I have done an end-to-end test for Raspberry Pi devices. (RTSP H.264 on Pi 5/Pi 4/Rock Pi 4; H.265 HW-decoded on Pi 5)
  • I have tested my changes for x86 devices. (RTSP, HLS, DASH)
  • I added a documentation for the changes I have made (when necessary).

🤖 Generated with Claude Code

The React→Django migration (#2818) reimplemented the URL→mimetype
classifier in the assets_create form handler but dropped the
scheme check, so rtsp://, rtmp:// and HLS/DASH manifest URLs fell
through to mimetype='webpage'. The viewer then routed them to
QtWebEngine instead of the video player and the stream never played.

- add `is_streaming_uri()` to remote_video, reusing the existing
  stream-scheme / manifest-extension sets so it can't drift from
  `is_downloadable_remote_video`
- assets_create now stamps stream URLs as mimetype='streaming' and
  applies the default_streaming_duration window
- regression tests for the classifier and the form handler

Validated end-to-end on the x86 testbed: an ffmpeg-fed RTSP feed
played live in the viewer (decoded and progressing on screen).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vpetersson vpetersson requested a review from a team as a code owner June 11, 2026 19:20
@vpetersson vpetersson self-assigned this Jun 11, 2026
@vpetersson vpetersson requested a review from Copilot June 11, 2026 19:21

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes URL-based asset creation so live-stream URIs (RTSP/RTMP/etc and HLS/DASH manifests) are classified as mimetype='streaming' rather than webpage, ensuring the viewer routes them through the video playback path instead of QtWebEngine.

Changes:

  • Added is_streaming_uri() in anthias_common.remote_video to detect streaming schemes and streaming manifest extensions.
  • Updated assets_create to classify streaming URLs as mimetype='streaming' (after YouTube detection) and apply default_streaming_duration.
  • Added regression tests covering the new classifier and the template view handler for RTSP and HLS URLs.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
src/anthias_common/remote_video.py Adds the is_streaming_uri() helper used to classify streaming URIs.
src/anthias_server/app/views.py Updates assets_create to stamp streaming URLs as mimetype='streaming' and set the streaming default duration.
tests/test_remote_video.py Adds unit tests for is_streaming_uri() behavior across schemes and manifest extensions.
tests/test_template_views.py Adds integration-style tests ensuring assets_create persists streaming mimetype/duration for RTSP and HLS.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/anthias_common/remote_video.py Outdated
Comment thread tests/test_remote_video.py
vpetersson and others added 2 commits June 11, 2026 20:14
On-device testing showed rtmp:// streams render black on every board.
Root cause is in Qt6's FFmpeg backend: it sets a `timeout` AVFormatContext
option the rtmp protocol misreads as TCP listen mode, so the open fails
(libavformat itself plays rtmp fine). Rather than let operators add a
stream that never shows:

- drop rtmp from validate_url's allowed schemes (gates the UI form and
  the API)
- drop rtmp from remote_video._STREAM_SCHEMES so is_streaming_uri never
  classifies a legacy rtmp row as a playable stream
- assets_create returns a clear "use RTSP/HLS/DASH" message instead of
  the generic "Invalid URL"
- tests updated

url_fails keeps probing rtmp defensively for any pre-existing DB rows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses Copilot review on #3071: a manifest extension (.m3u8/.mpd/…)
was treated as streaming for any scheme, so file:///x/index.m3u8 would
classify as mimetype='streaming'. Manifests are HTTP-delivered — only
match them over http(s). Adds file:// manifest regression cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vpetersson

Copy link
Copy Markdown
Contributor Author

The x86 H.265/HEVC playback failure noted in the testing section is now tracked separately in #3072 (Qt6 VAAPI-under-cage HEVC decode — not caused by this PR; HEVC hardware-decodes fine on Pi 5).

The DASH-over-http URL in the is_streaming_uri parametrize is a
deliberate test input — it exercises the http (not https) manifest
branch. It's a fixture, not real traffic, so annotate the SonarCloud
clear-text-protocol hotspot with # NOSONAR rather than changing the
test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

@vpetersson vpetersson merged commit 10f273b into master Jun 11, 2026
9 checks passed
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.

2 participants