Skip to content

Camera组件因改严格模式,页面statechange会导致画面闪烁,添加设备ID缓存,只有真正修改设备才执行重置操作#8261

Closed
kkxkx wants to merge 5 commits into
dotnetcore:mainfrom
kkxkx:main
Closed

Camera组件因改严格模式,页面statechange会导致画面闪烁,添加设备ID缓存,只有真正修改设备才执行重置操作#8261
kkxkx wants to merge 5 commits into
dotnetcore:mainfrom
kkxkx:main

Conversation

@kkxkx

@kkxkx kkxkx commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Camera组件因改严格模式,页面statechange会导致画面闪烁,添加设备ID缓存,只有真正修改设备才执行重置操作

Summary by Sourcery

Prevent unnecessary camera device resets on state changes by caching the active device ID and only restarting the hardware when the selected device actually changes.

Enhancements:

  • Track the last used camera device ID in the Camera component to distinguish real device switches from page state changes.
  • Make camera playback and update logic asynchronous to better sequence device stop/start operations and reduce visual flicker.
  • Introduce a short delay between stopping and restarting the camera device to improve stability when switching devices.

kkxkx and others added 5 commits July 23, 2026 11:05
修改了Camera组件在Edge浏览器不能切换多个摄像头画面的问题deviceId: { exact: deviceId },严格匹配模式

Signed-off-by: kkxkx <63846879+kkxkx@users.noreply.github.com>
增加设备ID缓存,解决因严格模式ID改变 重启启停硬件导致UI闪屏问题

Signed-off-by: kkxkx <63846879+kkxkx@users.noreply.github.com>
@sourcery-ai

sourcery-ai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Camera component JS logic is refactored to avoid unnecessary device resets on state changes by caching the last-used device ID, making play/update async, and only reopening the media stream when the device actually changes.

Sequence diagram for updated Camera update/play device-switch logic

sequenceDiagram
    actor Page
    participant CameraJS as CameraJS
    participant MediaDevices as navigator.mediaDevices
    participant VideoElement as video_element
    participant DotNetInvoker as invoke

    Page->>CameraJS: update(id)
    CameraJS->>CameraJS: Data.get(id)
    CameraJS->>CameraJS: domDeviceId = el.getAttribute(data-device-id)
    CameraJS-->>Page: [if lastUsedDeviceId === domDeviceId] return

    alt camera.video exists
        CameraJS->>CameraJS: stopDevice(camera)
        CameraJS->>CameraJS: setTimeout(350ms)
        CameraJS->>CameraJS: openDevice(camera)
        CameraJS->>MediaDevices: getUserMedia(constrains)
        MediaDevices-->>CameraJS: stream
        CameraJS->>VideoElement: srcObject = stream
        CameraJS->>VideoElement: play()
        VideoElement-->>CameraJS: playback_started
        CameraJS->>CameraJS: lastUsedDeviceId = option.video.deviceId
        CameraJS->>DotNetInvoker: TriggerOpen()
    else camera.video does not exist
        CameraJS->>CameraJS: autoStart = el.getAttribute(data-auto-start)
        CameraJS-->>Page: [if !autoStart] return
        CameraJS->>CameraJS: openDevice(camera)
        CameraJS->>MediaDevices: getUserMedia(constrains)
        MediaDevices-->>CameraJS: stream
        CameraJS->>VideoElement: srcObject = stream
        CameraJS->>VideoElement: play()
        VideoElement-->>CameraJS: playback_started
        CameraJS->>CameraJS: lastUsedDeviceId = option.video.deviceId
        CameraJS->>DotNetInvoker: TriggerOpen()
    end
Loading

File-Level Changes

Change Details Files
Refactor play() into an async flow that tracks the currently running device ID and awaits video playback.
  • Change play() from promise chaining to async/await around getUserMedia and video.play()
  • Simplify default constraints object construction for video/audio options
  • Record lastUsedDeviceId on successful stream acquisition, supporting both exact and direct deviceId forms
  • Improve error handling using try/catch while preserving existing TriggerOpen/TriggerError callbacks
src/BootstrapBlazor/Components/Camera/Camera.razor.js
Introduce device ID caching and conditional restart logic in init/update to prevent unnecessary hardware resets and visual flicker.
  • Add lastUsedDeviceId field to camera object during init with initial null value
  • Streamline device enumeration filter when initializing camera devices
  • Make update() async and read current device ID from DOM data-device-id attribute
  • Skip hardware restart in update() when lastUsedDeviceId matches DOM device ID
  • On real device switch, stop the device, wait briefly, then reopen it to avoid flicker
  • Ensure auto-start behavior uses async openDevice with awaiting
src/BootstrapBlazor/Components/Camera/Camera.razor.js

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

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In play, accessing option.video.deviceId and option.video.deviceId.exact without guarding can throw if option.video or option.video.deviceId is missing; consider defaulting or short‑circuiting before using those properties.
  • The 350ms delay between stopDevice and openDevice in update is a magic number; consider extracting it into a named constant or tying it to documented behavior so future changes don’t inadvertently break the sequencing assumptions.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `play`, accessing `option.video.deviceId` and `option.video.deviceId.exact` without guarding can throw if `option.video` or `option.video.deviceId` is missing; consider defaulting or short‑circuiting before using those properties.
- The 350ms delay between `stopDevice` and `openDevice` in `update` is a magic number; consider extracting it into a named constant or tying it to documented behavior so future changes don’t inadvertently break the sequencing assumptions.

## Individual Comments

### Comment 1
<location path="src/BootstrapBlazor/Components/Camera/Camera.razor.js" line_range="82" />
<code_context>
-    }
-    else {
+        stopDevice(camera)
+        await new Promise(r => setTimeout(r, 350));
+        await openDevice(camera)
+    } else {
</code_context>
<issue_to_address>
**suggestion:** Consider extracting the 350ms delay into a named constant or configuration.

This hard-coded timeout is a magic number and likely tied to hardware or UX behavior. Defining it as a shared constant (e.g., `DEVICE_SWITCH_DELAY_MS`) makes its intent clear and ensures any future adjustments stay consistent across the codebase.

Suggested implementation:

```javascript
        stopDevice(camera)
        await new Promise(r => setTimeout(r, DEVICE_SWITCH_DELAY_MS));
        await openDevice(camera)

```

To fully implement this change, also:
1. Define a named constant near the top of `Camera.razor.js`, for example:
   `const DEVICE_SWITCH_DELAY_MS = 350;`
2. If there is an existing configuration or constants section in this file or a related module, prefer adding `DEVICE_SWITCH_DELAY_MS` there to keep all such values centralized.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

}
else {
stopDevice(camera)
await new Promise(r => setTimeout(r, 350));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion: Consider extracting the 350ms delay into a named constant or configuration.

This hard-coded timeout is a magic number and likely tied to hardware or UX behavior. Defining it as a shared constant (e.g., DEVICE_SWITCH_DELAY_MS) makes its intent clear and ensures any future adjustments stay consistent across the codebase.

Suggested implementation:

        stopDevice(camera)
        await new Promise(r => setTimeout(r, DEVICE_SWITCH_DELAY_MS));
        await openDevice(camera)

To fully implement this change, also:

  1. Define a named constant near the top of Camera.razor.js, for example:
    const DEVICE_SWITCH_DELAY_MS = 350;
  2. If there is an existing configuration or constants section in this file or a related module, prefer adding DEVICE_SWITCH_DELAY_MS there to keep all such values centralized.

@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (b30c523) to head (d872ef7).

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #8261   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          769       769           
  Lines        34436     34436           
=========================================
  Hits         34436     34436           
Flag Coverage Δ
BB 100.00% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 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.

@kkxkx kkxkx closed this by deleting the head repository Jul 24, 2026
@ArgoZhang ArgoZhang self-assigned this Jul 24, 2026
@ArgoZhang ArgoZhang added the enhancement New feature or request label Jul 24, 2026
@ArgoZhang ArgoZhang added this to the v10.8.0 milestone Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants