Skip to content

feat(HikVisionWebPlugin): add OnInitedAsync parameter#785

Merged
ArgoZhang merged 2 commits intomasterfrom
refactor-hik
Dec 5, 2025
Merged

feat(HikVisionWebPlugin): add OnInitedAsync parameter#785
ArgoZhang merged 2 commits intomasterfrom
refactor-hik

Conversation

@ArgoZhang
Copy link
Copy Markdown
Member

@ArgoZhang ArgoZhang commented Dec 5, 2025

Link issues

fixes #784

Summary By Copilot

Regression?

  • Yes
  • No

Risk

  • High
  • Medium
  • Low

Verification

  • Manual (required)
  • Automated

Packaging changes reviewed?

  • Yes
  • No
  • N/A

☑️ Self Check before Merge

⚠️ Please check all items below before review. ⚠️

  • Doc is updated/provided or not needed
  • Demo is updated/provided or not needed
  • Merge the latest code from the main branch

Summary by Sourcery

Add a new HikVision web plugin component with initialization callback and richer state management for login and real-time preview, while tightening the JavaScript SDK’s lifecycle and status handling.

New Features:

  • Introduce HikVisionWebPlugin Blazor component that wraps the HikVision WebSDK plugin with explicit init, login, logout, and real-time preview control methods.
  • Add an OnInitedAsync callback and JS-invokable TriggerInited method to notify callers when the WebSDK plugin initialization completes and whether it succeeded.
  • Expose Inited, IsLogined, and IsRealPlaying properties on the component to allow consumers to track plugin and session state.

Bug Fixes:

  • Ensure login is only attempted when the plugin is initialized and credentials are valid, returning a boolean success result instead of raw state.
  • Fix window index parsing and default XML value handling to avoid nulls and type issues when selecting windows and channels.
  • Prevent unnecessary logout and stop operations when not logged in or not currently playing, and ensure internal flags are updated consistently.

Enhancements:

  • Refine hikvision.js to return explicit success flags from init, login, startRealPlay, and stopRealPlay using promise-based completion tracking.
  • Make real-time preview configurable by stream type and channel ID, and track playback state in both JS and .NET for safer control.
  • Improve disposal logic to stop preview and logout only when needed, then destroy the plugin and clean up stored state.

Copilot AI review requested due to automatic review settings December 5, 2025 02:45
@bb-auto bb-auto Bot added the enhancement New feature or request label Dec 5, 2025
@bb-auto bb-auto Bot added this to the v9.2.0 milestone Dec 5, 2025
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Dec 5, 2025

Reviewer's Guide

Refactors the HikVision Web plugin into a dedicated HikVisionWebPlugin component with richer state management, adds an OnInitedAsync callback and JS-to-.NET init flow, and tightens the JS SDK integration to return explicit success flags and guard operations based on initialization, login, and real-play state.

Sequence diagram for HikVisionWebPlugin initialization with OnInitedAsync callback

sequenceDiagram
    participant BlazorPage
    participant HikVisionWebPlugin
    participant JSRuntime
    participant HikVisionWebPlugin_js
    participant Hikvision_js
    participant WebVideoCtrl

    BlazorPage->>HikVisionWebPlugin: Render component
    HikVisionWebPlugin->>JSRuntime: InvokeAsync init(Id, DotNetObjectRef)
    JSRuntime->>HikVisionWebPlugin_js: init(id, invoke)
    HikVisionWebPlugin_js->>Hikvision_js: initVision(id)
    Hikvision_js->>WebVideoCtrl: I_InjectPlugin(id)
    WebVideoCtrl-->>Hikvision_js: Plugin window inited or failed
    Hikvision_js-->>HikVisionWebPlugin_js: inited (true or false)
    HikVisionWebPlugin_js->>JSRuntime: invoke.invokeMethodAsync TriggerInited(inited)
    JSRuntime->>HikVisionWebPlugin: TriggerInited(inited)
    HikVisionWebPlugin->>HikVisionWebPlugin: Inited = inited
    alt OnInitedAsync assigned
        HikVisionWebPlugin->>BlazorPage: OnInitedAsync(inited)
    end
Loading

Sequence diagram for login and real-play operations with state guards

sequenceDiagram
    participant BlazorPage
    participant HikVisionWebPlugin
    participant JSRuntime
    participant HikVisionWebPlugin_js
    participant Hikvision_js
    participant WebVideoCtrl

    rect rgb(230,230,255)
        BlazorPage->>HikVisionWebPlugin: Login(ip, port, userName, password, loginType)
        HikVisionWebPlugin->>HikVisionWebPlugin: ThrowIfNotInited()
        HikVisionWebPlugin->>JSRuntime: InvokeAsync<bool?> login(Id, ip, port, userName, password, loginType)
        JSRuntime->>HikVisionWebPlugin_js: login(Id, ip, port, userName, password, loginType)
        HikVisionWebPlugin_js->>Hikvision_js: login(Id, ip, port, userName, password, loginType)
        Hikvision_js->>Hikvision_js: validate inited and parameters
        alt not inited or invalid
            Hikvision_js-->>HikVisionWebPlugin_js: false
        else already logined
            Hikvision_js-->>HikVisionWebPlugin_js: true
        else perform login
            Hikvision_js->>WebVideoCtrl: I_Login(szDeviceIdentify, options)
            WebVideoCtrl-->>Hikvision_js: success or error
            Hikvision_js-->>HikVisionWebPlugin_js: vision.logined (true or false)
        end
        HikVisionWebPlugin_js-->>JSRuntime: bool
        JSRuntime-->>HikVisionWebPlugin: bool
        HikVisionWebPlugin->>HikVisionWebPlugin: IsLogined = result
        HikVisionWebPlugin-->>BlazorPage: bool
    end

    rect rgb(230,255,230)
        BlazorPage->>HikVisionWebPlugin: StartRealPlay(streamType, channelId)
        HikVisionWebPlugin->>HikVisionWebPlugin: check IsLogined && !IsRealPlaying
        alt allowed to start
            HikVisionWebPlugin->>JSRuntime: InvokeAsync<bool?> startRealPlay(Id, streamType, channelId)
            JSRuntime->>HikVisionWebPlugin_js: startRealPlay(Id, streamType, channelId)
            HikVisionWebPlugin_js->>Hikvision_js: startRealPlay(Id, streamType, channelId)
            Hikvision_js->>WebVideoCtrl: I_StartRealPlay(szDeviceIdentify, options)
            WebVideoCtrl-->>Hikvision_js: success or error
            Hikvision_js->>Hikvision_js: set vision.realPlaying and completed
            Hikvision_js-->>HikVisionWebPlugin_js: completed (true or false)
            HikVisionWebPlugin_js-->>JSRuntime: bool
            JSRuntime-->>HikVisionWebPlugin: bool
            HikVisionWebPlugin->>HikVisionWebPlugin: IsRealPlaying = result
        else guard blocks call
            HikVisionWebPlugin-->>BlazorPage: no-op
        end
    end

    rect rgb(255,230,230)
        BlazorPage->>HikVisionWebPlugin: StopRealPlay()
        HikVisionWebPlugin->>HikVisionWebPlugin: check IsLogined && IsRealPlaying
        alt allowed to stop
            HikVisionWebPlugin->>JSRuntime: InvokeAsync<bool?> stopRealPlay(Id)
            JSRuntime->>HikVisionWebPlugin_js: stopRealPlay(Id)
            HikVisionWebPlugin_js->>Hikvision_js: stopRealPlay(Id)
            Hikvision_js->>WebVideoCtrl: I_Stop(options)
            WebVideoCtrl-->>Hikvision_js: success or error
            Hikvision_js-->>HikVisionWebPlugin_js: completed (true or false)
            HikVisionWebPlugin_js-->>JSRuntime: bool
            JSRuntime-->>HikVisionWebPlugin: bool
            HikVisionWebPlugin->>HikVisionWebPlugin: update IsRealPlaying
        else guard blocks call
            HikVisionWebPlugin-->>BlazorPage: no-op
        end
    end
Loading

Updated class diagram for HikVisionWebPlugin component

classDiagram
    class HikVisionWebPlugin {
        +string? IP
        +int Port
        +string? UserName
        +string? Password
        +LoginType LoginType
        +string? Width
        +string? Height
        +Func~bool, Task~ OnInitedAsync
        +bool Inited
        +bool IsLogined
        +bool IsRealPlaying
        -string? ClassString
        -string? StyleString
        +Task<bool> Login(string ip, int port, string userName, string password, LoginType loginType)
        +Task Logout()
        +Task StartRealPlay(int streamType, int channelId)
        +Task StopRealPlay()
        -void ThrowIfNotInited()
        +Task TriggerInited(bool inited)
        #override void OnParametersSet()
        #override Task OnAfterRenderAsync(bool firstRender)
    }

    class BootstrapModuleComponentBase {
        +string Id
        +Dictionary~string, object?~ AdditionalAttributes
        +ValueTask InvokeVoidAsync(string identifier, object? arg1, object? arg2, object? arg3, object? arg4, object? arg5, object? arg6)
        +ValueTask<T> InvokeAsync<T>(string identifier, object? arg1, object? arg2, object? arg3, object? arg4, object? arg5, object? arg6)
    }

    class LoginType {
        <<enum>>
        Http
        Https
        Other
    }

    class HikVisionWebPlugin_js {
        +Task init(string id, object invoke)
        +Task login(string id, string ip, int port, string userName, string password, int loginType)
        +Task logout(string id)
        +Task startRealPlay(string id, int streamType, int channelId)
        +Task stopRealPlay(string id)
    }

    class Hikvision_js {
        +Task<bool> init(string id)
        +Task<bool> login(string id, string ip, int port, string userName, string password, int loginType)
        +Task logout(string id)
        +Task<bool> startRealPlay(string id, int iStreamType, int iChannelID)
        +Task<bool> stopRealPlay(string id)
        +void dispose(string id)
    }

    HikVisionWebPlugin --|> BootstrapModuleComponentBase
    HikVisionWebPlugin ..> LoginType
    HikVisionWebPlugin ..> HikVisionWebPlugin_js
    HikVisionWebPlugin_js ..> Hikvision_js
Loading

State diagram for HikVisionWebPlugin initialization, login, and real-play lifecycle

stateDiagram-v2
    [*] --> NotInited

    NotInited --> Inited : TriggerInited(true)
    NotInited --> InitFailed : TriggerInited(false)

    Inited --> LoggedIn : Login success
    Inited --> Inited : Login failure

    LoggedIn --> Playing : StartRealPlay success
    LoggedIn --> LoggedIn : StartRealPlay failure

    Playing --> LoggedIn : StopRealPlay success
    Playing --> Playing : StopRealPlay failure

    LoggedIn --> Inited : Logout
    Playing --> Inited : Logout

    InitFailed --> [*]
    Inited --> [*] : Dispose
    LoggedIn --> [*] : Dispose
    Playing --> [*] : Dispose
Loading

File-Level Changes

Change Details Files
Make core hikvision.js API stateful with explicit boolean results and safer guards for init, login, logout, real-play, and disposal.
  • Change init to return false when DOM element or window init fails, store an inited flag in the Data map, and return true on success.
  • Parse SelectWnd values as integers and let getTagNameFirstValue accept a default value instead of returning null.
  • Harden login to validate inputs and init state, short-circuit if already logged in, and resolve to a boolean logined flag instead of the vision object.
  • Refactor logout to be async/await, stop real play before logout, and maintain a logined=false state without polling.
  • Extend startRealPlay to accept streamType and channelId, track realPlaying state, and resolve a boolean indicating success via polling of completion flags.
  • Update stopRealPlay to be idempotent, gate on realPlaying, and asynchronously return a boolean based on WebVideoCtrl.I_Stop callbacks.
  • Adjust dispose to remove Data entry and only stop real play/logout when their flags are true.
src/components/BootstrapBlazor.HikVision/wwwroot/hikvision.js
Introduce HikVisionWebPlugin Blazor component with initialization callback, internal state tracking, and updated JS interop surface.
  • Rename HikVision component to HikVisionWebPlugin, point JSModuleAutoLoader to HikVisionWebPlugin.razor.js, and enable JSObjectReference.
  • Add OnInitedAsync Func<bool,Task> parameter, Inited/IsLogined/IsRealPlaying properties, and a ThrowIfNotInited guard.
  • Update Login to return a bool based on JS login result and to throw if plugin not initialized.
  • Make Logout no-op if not logged in and always clear IsLogined after JS logout.
  • Change StartRealPlay to accept streamType and channelId, only start when logged in and not already playing, and update IsRealPlaying from the JS return value.
  • Update StopRealPlay to only act when logged in and playing, and to clear IsRealPlaying when JS stop succeeds.
  • Expose JSInvokable TriggerInited to receive init completion from JS, update Inited, and invoke OnInitedAsync if provided.
src/components/BootstrapBlazor.HikVision/Components/HikVisionWebPlugin.razor.cs
src/components/BootstrapBlazor.HikVision/Components/HikVisionWebPlugin.razor
Wire JS interop for the new HikVisionWebPlugin component initialization and keep existing operation exports.
  • Rename HikVision.razor.js to HikVisionWebPlugin.razor.js and adjust the init function signature to accept a .NET invoke object.
  • Call initVision(id) and then invoke the .NET TriggerInited method with the boolean init result.
  • Re-export login, logout, startRealPlay, and stopRealPlay bindings unchanged for use by the component.
src/components/BootstrapBlazor.HikVision/Components/HikVisionWebPlugin.razor.js

Assessment against linked issues

Issue Objective Addressed Explanation
#784 Add an OnInitedAsync parameter to the HikVision Web plugin component that is invoked (with a boolean indicating success) when the underlying WebSDK plugin initialization completes.

Possibly linked issues


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

@ArgoZhang ArgoZhang merged commit 9ea8f6f into master Dec 5, 2025
6 of 7 checks passed
@ArgoZhang ArgoZhang deleted the refactor-hik branch December 5, 2025 02:46
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • In hikvision.js#dispose, Data.remove(id) is called before stopRealPlay(id)/logout(id), but those functions rely on Data.get(id) and destructuring, which will throw if the entry has already been removed; you should delay Data.remove(id) until after these operations complete.
  • The new OnInitedAsync parameter on HikVisionWebPlugin is declared as non-nullable but is used with a null check, so either mark it as Func<bool, Task>? or provide a non-null default to keep nullability consistent.
  • Consider removing the console.log(oWndInfo); debug statement in startRealPlay to avoid unnecessary console noise in production.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `hikvision.js#dispose`, `Data.remove(id)` is called before `stopRealPlay(id)`/`logout(id)`, but those functions rely on `Data.get(id)` and destructuring, which will throw if the entry has already been removed; you should delay `Data.remove(id)` until after these operations complete.
- The new `OnInitedAsync` parameter on `HikVisionWebPlugin` is declared as non-nullable but is used with a null check, so either mark it as `Func<bool, Task>?` or provide a non-null default to keep nullability consistent.
- Consider removing the `console.log(oWndInfo);` debug statement in `startRealPlay` to avoid unnecessary console noise in production.

## Individual Comments

### Comment 1
<location> `src/components/BootstrapBlazor.HikVision/wwwroot/hikvision.js:180` </location>
<code_context>
 }

-export function logout(id) {
+export async function logout(id) {
     const vision = Data.get(id);
-    const { szDeviceIdentify } = vision;
+    const { szDeviceIdentify, logined } = vision;
+    if (logined !== true) {
+        vision.logined = false;
+        return;
+    }

-    let completed = null;
-    WebVideoCtrl.I_Logout(szDeviceIdentify).then(() => {
-        completed = true;
-    }, () => {
-        completed = false;
-    });
+    stopRealPlay(id);

-    return new Promise((resolve, reject) => {
-        const handler = setInterval(() => {
-            if (completed !== null) {
-                clearInterval(handler)
-                resolve(vision);
-            }
-        }, 16);
-    });
+    await WebVideoCtrl.I_Logout(szDeviceIdentify);
+    vision.logined = false;
 }
</code_context>

<issue_to_address>
**suggestion (bug_risk):** `logout` ignores the asynchronous result of `stopRealPlay`, which may cause races.

`stopRealPlay(id)` now returns a `Promise`, but `logout` does not `await` it. This allows `logout` to continue (including clearing `logined`) before playback has actually stopped, and any rejection from `stopRealPlay` will be lost. If the goal is to stop playback before logout completes, `await stopRealPlay(id);` here and consider handling its boolean result.

```suggestion
    await stopRealPlay(id);
```
</issue_to_address>

### Comment 2
<location> `src/components/BootstrapBlazor.HikVision/wwwroot/hikvision.js:186` </location>
<code_context>
 }

-export async function startRealPlay(id) {
+export async function startRealPlay(id, iStreamType, iChannelID) {
     const vision = Data.get(id);
     const { iWndIndex, szDeviceIdentify } = vision;
</code_context>

<issue_to_address>
**issue (complexity):** Consider replacing the polling-based async handling in `startRealPlay`/`stopRealPlay` with direct Promise wrappers around the existing callbacks and returning a consistent Promise<boolean> API.

The new async handling in `startRealPlay` / `stopRealPlay` adds avoidable complexity via polling + shared flags and inconsistent return types. You can keep all current behavior while simplifying by directly wrapping the callback API in Promises and making the API consistently async.

### 1. Simplify `startRealPlay` async handling

You don’t need `completed` + `setInterval` because `I_StartRealPlay` already exposes `success`/`error` callbacks:

```js
export async function startRealPlay(id, iStreamType, iChannelID) {
    const vision = Data.get(id);
    const { iWndIndex, szDeviceIdentify } = vision;

    vision.devicePort = await WebVideoCtrl.I_GetDevicePort(vision.szDeviceIdentify);
    await getChannelInfo(vision);

    const oWndInfo = WebVideoCtrl.I_GetWindowStatus(iWndIndex);
    const iRtspPort = vision.devicePort.iRtspPort;
    const bZeroChannel = false;

    const startRealPlay = () => new Promise(resolve => {
        WebVideoCtrl.I_StartRealPlay(szDeviceIdentify, {
            iWndIndex,
            iStreamType,
            iChannelID,
            bZeroChannel,
            iPort: iRtspPort,
            success: () => {
                vision.realPlaying = true;
                resolve(true);
            },
            error: () => {
                vision.realPlaying = false;
                resolve(false);
            }
        });
    });

    if (oWndInfo !== null) {
        await new Promise(resolve => {
            WebVideoCtrl.I_Stop({ success: resolve, error: resolve });
        });
    }

    // preserve boolean success/failure result
    return startRealPlay();
}
```

This keeps:

- `vision.realPlaying` updates
- boolean result indicating success/failure
- `I_Stop` before restart

but removes the polling loop and shared `completed` flag.

### 2. Make `stopRealPlay` consistently async and remove polling

Currently it sometimes returns `true` synchronously and sometimes a `Promise`, and it polls `completed`. You can keep behavior while returning a `Promise<boolean>` in all cases:

```js
export function stopRealPlay(id) {
    const vision = Data.get(id);
    const { iWndIndex, realPlaying } = vision;

    if (realPlaying !== true) {
        // already stopped
        return Promise.resolve(true);
    }

    const oWndInfo = WebVideoCtrl.I_GetWindowStatus(iWndIndex);
    if (oWndInfo === null) {
        vision.realPlaying = false;
        return Promise.resolve(true);
    }

    return new Promise(resolve => {
        WebVideoCtrl.I_Stop({
            success: () => {
                vision.realPlaying = false;
                resolve(true);
            },
            error: () => {
                resolve(false);
            }
        });
    });
}
```

This preserves:

- `vision.realPlaying` set to `false` on success
- boolean success/failure result
- “already stopped” fast-path

but removes the interval polling and type ambiguity (`boolean` vs `Promise<boolean>`).

If you want to keep `dispose` synchronous, you can still call `stopRealPlay(id)` without `await`; the above returns a Promise but callers that don’t care about completion can ignore it.
</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.

}, () => {
completed = false;
});
stopRealPlay(id);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (bug_risk): logout ignores the asynchronous result of stopRealPlay, which may cause races.

stopRealPlay(id) now returns a Promise, but logout does not await it. This allows logout to continue (including clearing logined) before playback has actually stopped, and any rejection from stopRealPlay will be lost. If the goal is to stop playback before logout completes, await stopRealPlay(id); here and consider handling its boolean result.

Suggested change
stopRealPlay(id);
await stopRealPlay(id);

}

export async function startRealPlay(id) {
export async function startRealPlay(id, iStreamType, iChannelID) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (complexity): Consider replacing the polling-based async handling in startRealPlay/stopRealPlay with direct Promise wrappers around the existing callbacks and returning a consistent Promise API.

The new async handling in startRealPlay / stopRealPlay adds avoidable complexity via polling + shared flags and inconsistent return types. You can keep all current behavior while simplifying by directly wrapping the callback API in Promises and making the API consistently async.

1. Simplify startRealPlay async handling

You don’t need completed + setInterval because I_StartRealPlay already exposes success/error callbacks:

export async function startRealPlay(id, iStreamType, iChannelID) {
    const vision = Data.get(id);
    const { iWndIndex, szDeviceIdentify } = vision;

    vision.devicePort = await WebVideoCtrl.I_GetDevicePort(vision.szDeviceIdentify);
    await getChannelInfo(vision);

    const oWndInfo = WebVideoCtrl.I_GetWindowStatus(iWndIndex);
    const iRtspPort = vision.devicePort.iRtspPort;
    const bZeroChannel = false;

    const startRealPlay = () => new Promise(resolve => {
        WebVideoCtrl.I_StartRealPlay(szDeviceIdentify, {
            iWndIndex,
            iStreamType,
            iChannelID,
            bZeroChannel,
            iPort: iRtspPort,
            success: () => {
                vision.realPlaying = true;
                resolve(true);
            },
            error: () => {
                vision.realPlaying = false;
                resolve(false);
            }
        });
    });

    if (oWndInfo !== null) {
        await new Promise(resolve => {
            WebVideoCtrl.I_Stop({ success: resolve, error: resolve });
        });
    }

    // preserve boolean success/failure result
    return startRealPlay();
}

This keeps:

  • vision.realPlaying updates
  • boolean result indicating success/failure
  • I_Stop before restart

but removes the polling loop and shared completed flag.

2. Make stopRealPlay consistently async and remove polling

Currently it sometimes returns true synchronously and sometimes a Promise, and it polls completed. You can keep behavior while returning a Promise<boolean> in all cases:

export function stopRealPlay(id) {
    const vision = Data.get(id);
    const { iWndIndex, realPlaying } = vision;

    if (realPlaying !== true) {
        // already stopped
        return Promise.resolve(true);
    }

    const oWndInfo = WebVideoCtrl.I_GetWindowStatus(iWndIndex);
    if (oWndInfo === null) {
        vision.realPlaying = false;
        return Promise.resolve(true);
    }

    return new Promise(resolve => {
        WebVideoCtrl.I_Stop({
            success: () => {
                vision.realPlaying = false;
                resolve(true);
            },
            error: () => {
                resolve(false);
            }
        });
    });
}

This preserves:

  • vision.realPlaying set to false on success
  • boolean success/failure result
  • “already stopped” fast-path

but removes the interval polling and type ambiguity (boolean vs Promise<boolean>).

If you want to keep dispose synchronous, you can still call stopRealPlay(id) without await; the above returns a Promise but callers that don’t care about completion can ignore it.

Copy link
Copy Markdown

Copilot AI left a comment

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 adds an OnInitedAsync callback parameter to the HikVisionWebPlugin component, allowing consumers to be notified when the HikVision Web SDK plugin initialization completes. The changes improve state management by tracking initialization, login, and real-play states, and refactor several JavaScript functions to return promises with boolean results for better error handling.

Key Changes:

  • Added OnInitedAsync callback parameter and Inited, IsLogined, IsRealPlaying public properties to expose component state
  • Refactored JavaScript functions (init, login, logout, startRealPlay, stopRealPlay) to return boolean values indicating success/failure
  • Added parameter validation in JavaScript and state checks in C# methods to prevent invalid operations

Reviewed changes

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

File Description
HikVisionWebPlugin.razor.cs Added OnInitedAsync callback parameter, state properties (Inited, IsLogined, IsRealPlaying), updated method signatures to return Task<bool>, and added validation logic
HikVisionWebPlugin.razor.js Updated init function to accept invoke parameter and trigger the TriggerInited callback with initialization result
hikvision.js Refactored multiple functions to return boolean success indicators, added state tracking (inited, logined, realPlaying), improved validation, and simplified promise handling
HikVisionWebPlugin.razor Minor formatting change (whitespace consolidation)
Comments suppressed due to low confidence (4)

src/components/BootstrapBlazor.HikVision/Components/HikVisionWebPlugin.razor.cs:165

  • The documentation comment "触发 回调方法由 JavaScript 调用" is missing proper punctuation. It should be "触发 回调方法,由 JavaScript 调用" (add a comma after 回调方法).
    src/components/BootstrapBlazor.HikVision/Components/HikVisionWebPlugin.razor.cs:132
  • The StartRealPlay method now requires two new parameters (streamType and channelId), but the documentation does not describe them. Add XML documentation for the new parameters:
/// <param name="streamType">Stream type (e.g., main stream or sub stream)</param>
/// <param name="channelId">Channel ID to play</param>

src/components/BootstrapBlazor.HikVision/Components/HikVisionWebPlugin.razor.cs:61

  • The OnInitedAsync parameter should be nullable (Func<bool, Task>?) to match the pattern used consistently across the codebase. Other similar callback parameters in the codebase like OnLockChangedCallbackAsync in DockView, OnConnect in WebSerial, and OnIsBusy in WebSpeech all use nullable callback types.
    src/components/BootstrapBlazor.HikVision/Components/HikVisionWebPlugin.razor.js:2
  • Unused import EventHandler.

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

};

if (oWndInfo != null) {
console.log(oWndInfo);
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

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

This console.log statement should be removed before merging to production. Debug logging statements should not be left in production code.

Suggested change
console.log(oWndInfo);

Copilot uses AI. Check for mistakes.
const { iWndIndex, realPlaying } = vision;

if (realPlaying !== true) {
return true;
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

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

The stopRealPlay function is not declared as async but returns a Promise. When the condition realPlaying !== true is met on line 241-243, it returns a boolean true directly instead of a Promise. This creates an inconsistent return type. Either make the function async and use return Promise.resolve(true) for the early return, or wrap the synchronous return in Promise.resolve(true).

Suggested change
return true;
return Promise.resolve(true);

Copilot uses AI. Check for mistakes.
Comment on lines 257 to +266
}

return new Promise((resolve, reject) => {
const handler = setInterval(() => {
if (completed !== null) {
clearInterval(handler)
resolve(completed);
}
}, 16);
});
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

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

If oWndInfo === null on line 247, the completed variable remains null and this Promise will never resolve, causing an infinite polling loop. Consider setting completed = true when oWndInfo === null to handle this case, or return early with a resolved value.

Suggested change
}
return new Promise((resolve, reject) => {
const handler = setInterval(() => {
if (completed !== null) {
clearInterval(handler)
resolve(completed);
}
}, 16);
});
return new Promise((resolve, reject) => {
const handler = setInterval(() => {
if (completed !== null) {
clearInterval(handler)
resolve(completed);
}
}, 16);
});
} else {
// If oWndInfo is null, resolve immediately (not playing)
return Promise.resolve(true);
}

Copilot uses AI. Check for mistakes.
Comment on lines 269 to 282
export function dispose(id) {
stopRealPlay(id);
logout(id);
const vision = Data.get(id);
Data.remove(id);

const { realPlaying, logined } = vision;
if (realPlaying === true) {
stopRealPlay(id);
}
if (logined === true) {
logout(id);
}
WebVideoCtrl.I_DestroyPlugin();

Data.remove(id);
}
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

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

The dispose function calls Data.remove(id) on line 271 before using vision properties on lines 273-279. This could cause issues if other operations need the data. Additionally, stopRealPlay(id) and logout(id) are called after the data is removed, which may cause errors since they both call Data.get(id). Move Data.remove(id) to the end of the function, after all cleanup operations are complete.

Copilot uses AI. Check for mistakes.
}, () => {
completed = false;
});
stopRealPlay(id);
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

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

The stopRealPlay(id) call on line 180 is not awaited, but stopRealPlay returns a Promise. This could cause the logout to proceed before the real play is fully stopped. Add await before stopRealPlay(id) to ensure proper cleanup order.

Suggested change
stopRealPlay(id);
await stopRealPlay(id);

Copilot uses AI. Check for mistakes.
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.

feat(HikVisionWebPlugin): add OnInitedAsync parameter

2 participants