fix(server): classify RTSP/streaming URLs as streaming, not webpage#3071
Conversation
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>
There was a problem hiding this comment.
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()inanthias_common.remote_videoto detect streaming schemes and streaming manifest extensions. - Updated
assets_createto classify streaming URLs asmimetype='streaming'(after YouTube detection) and applydefault_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.
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>
|
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>
|



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 newassets_createhandler dropped the scheme check that the old ReactgetMimetypehad.Description
is_streaming_uri()toanthias_common.remote_video, reusing the existing stream-scheme and manifest-extension (.m3u8/.mpd/.m3u/.ism, http(s) only) sets so it can't drift fromis_downloadable_remote_video.assets_createnow classifies stream URLs asmimetype='streaming'(after YouTube, before the extension heuristic) and applies the dedicateddefault_streaming_durationwindow — previously dead config.rtmp://atvalidate_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 atimeoutoption the rtmp protocol misreads as TCP listen mode, so the open fails (libavformatitself plays rtmp fine). Rather than let operators add a stream that never shows, we don't accept it.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-msadvancing +frames-renderedclimbing), HW-decoder fds, and screenshots./dev/video19, Hantro G2)/dev/video10)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 ownlibavcodecdecodes 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/mmsare recognised byis_streaming_uribut rejected byvalidate_url, so they're unreachable end-to-end (untouched here).Checklist
🤖 Generated with Claude Code