Skip to content

frontend: video-manager: Add block list support for MCM sources#3846

Draft
joaoantoniocardoso wants to merge 8 commits intobluerobotics:masterfrom
joaoantoniocardoso:master-add-block-list-mcm-sources
Draft

frontend: video-manager: Add block list support for MCM sources#3846
joaoantoniocardoso wants to merge 8 commits intobluerobotics:masterfrom
joaoantoniocardoso:master-add-block-list-mcm-sources

Conversation

@joaoantoniocardoso
Copy link
Member

@joaoantoniocardoso joaoantoniocardoso commented Mar 23, 2026

master counterpart of the 1.4 PR: #3837

This branch includes the commits from the thumbnail rework and MCM Idle master PRs so the UI and types stay consistent and CI can pass.

Suggested merge order: merge the thumbnail and Idle PRs to master first, then rebase this branch onto master so this PR collapses to a single commit (the block-list change) before final merge.

Related master PRs:

Block-list commit: 33e42773 (equivalent to the rebased 1.4 head).

Summary by Sourcery

Add video source block list support and enhanced thumbnail controls to the video manager UI and store.

New Features:

  • Allow blocking and unblocking video sources from the video manager, including visual status and controls.
  • Expose per-stream options to disable lazy mode, mavlink, thumbnails, and Zenoh in the stream creation and editing UI.
  • Provide user-facing controls to fetch single or continuous thumbnails per device, with idle/disabled states and debug overlay in pirate mode.

Enhancements:

  • Refine device and stream status handling to distinguish running, idle, and stopped states and update corresponding UI text and colors.
  • Centralize thumbnail fetch scheduling in a shared, window-scoped state, tracking round-trip latency and preventing overlapping requests.
  • Improve thumbnail placeholder and layout styling and ensure blob URLs are cleaned up when thumbnails are updated or components destroyed.

…d fix blob URL cleanup

Debounce stopGetThumbnailForDevice by 15 seconds to ride through
transient backend restarts without interrupting thumbnail polling.
Move blob URL revocation to beforeDestroy to prevent premature cleanup.
…state

Move thumbnail sources, busy set, and OneMoreTime task to a
window-global singleton that persists across hot module reloads.
Add reentrant guard to prevent duplicate fetchThumbnails execution
from the double @module decorator. Reduce polling delay to 100ms
and preserve last valid thumbnail on 503 errors.
…nail controls

Default to paused thumbnail fetching to avoid unnecessary resource
usage. Add snapshot (single-fetch) and play/pause (continuous 1s
polling) controls. Show a warning that thumbnails use stream resources
and may affect video quality. Replace v-avatar with a 16:9 aspect
ratio frame for consistent placeholder and image dimensions.
…xtended config booleans and fix form state reset

Add disable_lazy, disable_thumbnails, and disable_zenoh checkboxes
to the extra configuration panel. Fix stale form state on dialog
reopen by re-syncing all fields from the stream prop via a watcher.
…amStatus

Add support for the new stream status state field from MCM with three
possible values: 'running', 'idle', and 'stopped'. This enables the
frontend to distinguish between healthy idle streams and stopped streams.
…s handling for new state field

Replace binary running check with state-based logic:
- Thumbnails now fetch when state is running or idle (not stopped)
- Status indicator shows green for running, blue for idle, red for stopped
- Add computed properties for has_healthy_streams, has_active_streams,
  has_idle_streams, and status_color
…ay for new state field

- Replace Running/Not running text with state-based display (Running, Idle, Stopped)
- Only show errors when stream state is stopped (not when idle)
- Add stream_state_text computed property for status text mapping
Integrate the block list API from mavlink-camera-manager PR bluerobotics#561.
Pirate-mode users can toggle source blocking via a switch; all users
see the blocked state reflected in the status indicator and tooltip.
@sourcery-ai
Copy link

sourcery-ai bot commented Mar 23, 2026

Reviewer's Guide

Implements video source block-list support and richer thumbnail/stream controls in the video manager, including shared thumbnail polling state, new extended configuration flags, and UI wiring for blocking sources and disabling thumbnails per stream.

Sequence diagram for blocking and unblocking a video source

sequenceDiagram
  title Block list sequence for video sources
  actor User
  participant VideoDeviceComponent
  participant VideoStore
  participant Backend

  User->>VideoDeviceComponent: toggleBlocked()
  alt device.blocked is true (user unblocks)
    VideoDeviceComponent->>VideoStore: unblockSource(device.source)
    VideoStore->>Backend: POST /unblock_source
  else device.blocked is false (user blocks)
    VideoDeviceComponent->>VideoStore: blockSource(device.source)
    VideoStore->>Backend: POST /block_source
  end

  Backend-->>VideoStore: 200 OK
  VideoStore->>VideoStore: fetchDevices()
  VideoStore->>Backend: GET /devices
  Backend-->>VideoStore: devices list (with blocked flag)
  VideoStore->>VideoStore: fetchStreams()
  VideoStore->>Backend: GET /streams
  Backend-->>VideoStore: streams list
  VideoStore-->>VideoDeviceComponent: updated device and streams
  VideoDeviceComponent-->>User: updated status text, icon color, block switch
Loading

Sequence diagram for shared thumbnail polling and controls

sequenceDiagram
  title Shared thumbnail polling and controls
  actor User
  participant VideoDeviceComponent
  participant VideoThumbnailComponent
  participant VideoStore
  participant Backend

  Note over VideoStore: Global thumbnailState with task, sources, busy, inProgress

  User->>VideoDeviceComponent: open controls / view device
  VideoDeviceComponent->>VideoThumbnailComponent: set register=true (if streams healthy and thumbnails enabled)
  VideoThumbnailComponent->>VideoStore: startGetThumbnailForDevice(source)
  VideoStore->>VideoStore: thumbnailState.sources.add(source)
  VideoStore->>VideoStore: start or resume thumbnailState.task

  loop every 1s (thumbnailState.task)
    VideoStore->>VideoStore: fetchThumbnails()
    alt thumbnailState.inProgress
      VideoStore-->>VideoStore: return (skip cycle)
    else not inProgress
      VideoStore->>Backend: GET /thumbnail?source=... for each non busy source
      Backend-->>VideoStore: image blob or error
      VideoStore->>VideoStore: update thumbnails map with Thumbnail{source,status,roundtripMs}
    end
  end

  VideoStore-->>VideoThumbnailComponent: thumbnails map updated
  VideoThumbnailComponent->>VideoThumbnailComponent: updateThumbnail()
  VideoThumbnailComponent->>VideoThumbnailComponent: set last_fetch_ms and thumbnail

  User->>VideoThumbnailComponent: click single snapshot
  VideoThumbnailComponent->>VideoThumbnailComponent: snapshot_in_progress=true
  VideoThumbnailComponent->>VideoStore: startGetThumbnailForDevice(source)
  VideoStore->>Backend: GET /thumbnail
  Backend-->>VideoStore: image blob
  VideoStore-->>VideoThumbnailComponent: updated Thumbnail
  VideoThumbnailComponent->>VideoThumbnailComponent: snapshot_in_progress=false, snapshot_cooldown=true
  VideoThumbnailComponent->>VideoStore: stopGetThumbnailForDevice(source)

  User->>VideoThumbnailComponent: toggleContinuous()
  alt continuous_mode becomes true
    VideoThumbnailComponent->>VideoStore: startGetThumbnailForDevice(source)
  else continuous_mode becomes false
    VideoThumbnailComponent->>VideoStore: stopGetThumbnailForDevice(source)
  end

  alt all sources removed from thumbnailState.sources
    VideoStore->>VideoStore: thumbnailState.task.stop()
  end
Loading

Class diagram for updated video manager types and store

classDiagram
  direction LR

  class Thumbnail {
    +string source
    +number status
    +number roundtripMs
  }

  class ThumbnailFetchState {
    +OneMoreTime task
    +Set~string~ sources
    +Set~string~ busy
    +boolean inProgress
  }

  class Device {
    +string id
    +string name
    +string source
    +Format[] formats
    +Control[] controls
    +boolean blocked
  }

  class ExtendedConfiguration {
    +boolean thermal
    +boolean disable_lazy
    +boolean disable_mavlink
    +boolean disable_thumbnails
    +boolean disable_zenoh
  }

  class StreamStatus {
    +string id
    +boolean running
    +StreamStatusState state
    +string error
    +VideoAndStreamInformation video_and_stream
  }

  class StreamPrototype {
    +string name
    +string encode
    +FrameDimensions dimensions
    +FrameInterval interval
    +string[] endpoints
    +boolean thermal
    +boolean disable_lazy
    +boolean disable_mavlink
    +boolean disable_thumbnails
    +boolean disable_zenoh
  }

  class VideoStore {
    +string API_URL
    +Map~string, Thumbnail~ thumbnails
    +fetchThumbnails()
    +blockSource(source)
    +unblockSource(source)
    +startGetThumbnailForDevice(source)
    +stopGetThumbnailForDevice(source)
    +fetchDevices()
    +fetchStreams()
  }

  class VideoThumbnailComponent {
    <<component>>
    +string source
    +string width
    +boolean register
    +boolean disabled
    +Thumbnail thumbnail
    +boolean continuous_mode
    +boolean snapshot_in_progress
    +boolean snapshot_cooldown
    +number last_fetch_ms
    +updateThumbnail()
    +fetchSingleThumbnail()
    +toggleContinuous()
  }

  class VideoDeviceComponent {
    <<component>>
    +Device device
    +boolean show_stream_creation_dialog
    +boolean show_controls_dialog
    +boolean are_video_streams_available
    +boolean has_healthy_streams
    +boolean thumbnails_disabled
    +boolean is_pirate_mode
    +string status_color
    +openStreamCreationDialog()
    +toggleBlocked()
  }

  class VideoStreamCreationDialogComponent {
    <<component>>
    +StreamPrototype stream
    +boolean is_thermal
    +boolean is_disable_lazy
    +boolean is_disable_mavlink
    +boolean is_disable_thumbnails
    +boolean is_disable_zenoh
    +resetFormFromStream()
  }

  VideoStore "1" --> "*" Thumbnail : manages
  VideoStore "1" --> "1" ThumbnailFetchState : uses
  StreamStatus "1" --> "1" VideoAndStreamInformation : wraps
  VideoAndStreamInformation "1" --> "1" ExtendedConfiguration : has
  StreamPrototype "1" --> "1" ExtendedConfiguration : builds_from

  VideoDeviceComponent "1" --> "1" Device : displays
  VideoDeviceComponent "1" --> "*" StreamStatus : derives_state
  VideoDeviceComponent "1" --> "1" VideoStore : uses
  VideoDeviceComponent "1" --> "1" VideoThumbnailComponent : owns

  VideoThumbnailComponent "1" --> "1" VideoStore : requests_thumbnails

  VideoStreamCreationDialogComponent "1" --> "1" Device : configures_for
  VideoStreamCreationDialogComponent "1" --> "1" StreamPrototype : edits
  VideoStreamCreationDialogComponent "1" --> "1" ExtendedConfiguration : sets_flags
  VideoStreamCreationDialogComponent "1" --> "1" VideoStore : creates_streams
Loading

File-Level Changes

Change Details Files
Refactor VideoThumbnail UI/logic to support on-demand and continuous fetching, disabled state, and debug overlay.
  • Replace VAvatar-based layout with a custom 16:9 thumbnail frame, overlay, and controls styling.
  • Add disabled and idle placeholder states, plus pirate-mode overlay showing LIVE/idle and last fetch roundtrip time.
  • Introduce continuous_mode, snapshot_in_progress, snapshot_cooldown, and last_fetch_ms state with corresponding fetchSingleThumbnail/toggleContinuous handlers.
  • Debounce unregister stop calls and avoid revoking blob URLs until component destroy, revoking the current one on teardown.
  • Update fetching logic to depend on continuous/snapshot state and use OneMoreTime update task to refresh thumbnails.
core/frontend/src/components/video-manager/VideoThumbnail.vue
Centralize thumbnail polling state and add roundtrip timing, plus actions for blocking/unblocking video sources in the video store.
  • Extend Thumbnail interface with roundtripMs timing data and propagate it from fetchThumbnails responses and errors.
  • Create a global ThumbnailFetchState singleton on window to manage OneMoreTime task, sources set, busy set, and inProgress flag.
  • Refactor fetchThumbnails to use global state, track busy sources, and guard against concurrent execution.
  • Add blockSource and unblockSource actions that call backend endpoints, refresh devices/streams on success, and notify on failure.
  • Change startGetThumbnailForDevice/stopGetThumbnailForDevice to use shared thumbnailState.task and sources set instead of per-module fields.
core/frontend/src/store/video.ts
Expose block-list and thumbnail-disable controls in the VideoDevice UI and wire them to video store actions and stream state.
  • Add computed status_color, has_healthy_streams, thumbnails_disabled, and is_pirate_mode for richer device status and thumbnail gating.
  • Swap hardcoded status colors/texts for dynamic status_color and new tooltip messages, including blocked state messaging.
  • Gate Add stream button and thumbnail register flag on device.blocked and thumbnails_disabled, and pass disabled state down to VideoThumbnail and VideoControlsDialog.
  • Add pirate-mode-only v-switch to block/unblock a source, calling new video.blockSource/unblockSource actions.
core/frontend/src/components/video-manager/VideoDevice.vue
core/frontend/src/components/video-manager/VideoControlsDialog.vue
Extend stream creation/editing flows and types with new extended configuration flags and stream state.
  • Add checkboxes in VideoStreamCreationDialog for disable_lazy, disable_thumbnails, and disable_zenoh, and plumb their values into extended_configuration when constructing the prototype.
  • Initialize dialog form state from incoming stream (including new flags) and add a watcher to reset the form when reopened via resetFormFromStream.
  • Update VideoStream component to show human-readable stream state text from new StreamStatus.state and include new extended flags in the stream_prototype.
  • Extend Device, ExtendedConfiguration, StreamStatus, and StreamPrototype interfaces with device.blocked, state, and the new disable_* boolean flags.
core/frontend/src/components/video-manager/VideoStreamCreationDialog.vue
core/frontend/src/components/video-manager/VideoStream.vue
core/frontend/src/types/video.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

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