Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<Version>10.0.5</Version>
<Version>10.0.6</Version>
</PropertyGroup>

<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ public async Task<bool> OpenSound()
if (IsLogin && IsRealPlaying)
{
var code = await InvokeAsync<int>("openSound", Id);
ret = code == 0;
ret = code == 100;
}
return ret;
}
Expand All @@ -295,7 +295,7 @@ public async Task<bool> CloseSound()
if (IsLogin && IsRealPlaying)
{
var code = await InvokeAsync<int>("closeSound", Id);
ret = code == 0;
ret = code == 100;
}
return ret;
}
Expand All @@ -311,8 +311,60 @@ public async Task<bool> SetVolume(int value)
if (IsLogin && IsRealPlaying)
{
var code = await InvokeAsync<int>("setVolume", Id, Math.Max(0, Math.Min(100, value)));
ret = code == 0;
ret = code == 100;
}
return ret;
}

/// <summary>
/// 抓图方法返回 Url
/// </summary>
/// <returns></returns>
Comment on lines +319 to +322
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

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

The comment incorrectly states "抓图方法返回 Url" (Capture method returns URL), but this method triggers a download and returns void, not a URL. The comment should be updated to accurately describe that it captures and downloads the picture.

Copilot uses AI. Check for mistakes.
public async Task CapturePictureAndDownload()
{
if (IsLogin && IsRealPlaying)
{
await InvokeVoidAsync("capturePictureAndDownload", Id);
}
}

private TaskCompletionSource<IJSStreamReference?>? _captureTaskCompletionSource = null;

/// <summary>
/// 抓图方法返回 Url
/// </summary>
Comment on lines +333 to +335
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

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

The comment incorrectly states "抓图方法返回 Url" (Capture method returns URL), but the method actually returns an IJSStreamReference, not a URL. The comment should be updated to reflect that it returns a stream reference.

Copilot uses AI. Check for mistakes.
/// <returns></returns>
public async Task<IJSStreamReference?> CapturePicture(CancellationToken token = default)
Copy link

Choose a reason for hiding this comment

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

issue (complexity): Consider simplifying the new capture APIs by returning the JS stream directly from CapturePicture instead of using a shared TaskCompletionSource and JS-invokable callback, and by fixing or removing the unnecessary CancellationToken parameter.

The new capture APIs add avoidable state and complexity. You can keep the functionality while simplifying the flow and removing concurrency hazards.

1. Remove the shared TaskCompletionSource and callback

Instead of a field-level TaskCompletionSource plus [JSInvokable] callback, you can directly await the JS result. This avoids:

  • global mutable state (_captureTaskCompletionSource)
  • race conditions if CapturePicture is called concurrently
  • extra JS → .NET callback plumbing

Assuming your JS capturePicture function can return a stream reference (as the other reviewer mentioned), you can simplify to:

// Remove this field entirely
// private TaskCompletionSource<IJSStreamReference?>? _captureTaskCompletionSource = null;

// Remove this method entirely
// [JSInvokable]
// public Task TriggerReceivePictureStream(IJSStreamReference stream) { ... }

public async Task<IJSStreamReference?> CapturePicture(CancellationToken token = default)
{
    if (!IsLogin || !IsRealPlaying)
    {
        return null;
    }

    // Directly return the JS stream reference
    return await InvokeAsync<IJSStreamReference?>("capturePicture", token, Id);
}

On the JS side, ensure capturePicture returns the stream directly, e.g.:

// Example shape
export function capturePicture(id) {
  const bytes = /* capture bytes for id */;
  return DotNet.createJSStreamReference(bytes.buffer);
}

2. Use the CancellationToken correctly (or drop it)

Currently:

await InvokeVoidAsync("capturePicture", token, Id);

passes the token as a JS argument, not as the cancellation parameter. If you keep cancellation support, wire it through the interop correctly:

public Task<IJSStreamReference?> CapturePicture(CancellationToken token = default)
{
    if (!IsLogin || !IsRealPlaying)
    {
        return Task.FromResult<IJSStreamReference?>(null);
    }

    // token is passed as the cancellation token parameter, not as JS argument
    return InvokeAsync<IJSStreamReference?>("capturePicture", token, Id);
}

If you don’t actually need cancellation here, consider removing the CancellationToken from the signature entirely to reduce cognitive overhead:

public Task<IJSStreamReference?> CapturePicture()
{
    if (!IsLogin || !IsRealPlaying)
    {
        return Task.FromResult<IJSStreamReference?>(null);
    }

    return InvokeAsync<IJSStreamReference?>("capturePicture", Id);
}

These changes preserve the feature (returning an IJSStreamReference from JS) while significantly reducing statefulness and complexity.

{
IJSStreamReference? ret = null;
if (IsLogin && IsRealPlaying)
{
_captureTaskCompletionSource = new();

try
{
await InvokeVoidAsync("capturePicture", token, Id);
ret = await _captureTaskCompletionSource.Task;
Comment on lines +346 to +347
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

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

The async pattern implemented here is incorrect. The JavaScript function capturePicture returns a value directly (either a JSStreamReference, empty string, or null), so this should use InvokeAsync<IJSStreamReference?> instead of InvokeVoidAsync followed by awaiting a TaskCompletionSource. The current implementation will never resolve because JavaScript never calls TriggerReceivePictureStream. Change line 346 to: ret = await InvokeAsync<IJSStreamReference?>("capturePicture", token, Id); and remove the need for TaskCompletionSource and TriggerReceivePictureStream method.

Copilot uses AI. Check for mistakes.
}
catch (Exception ex)
{
_captureTaskCompletionSource.SetException(ex);
}
Comment on lines +344 to +352
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

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

The exception handling logic is incorrect. When an exception occurs, the code sets the exception on the TaskCompletionSource (line 351), but then continues execution and returns ret (which is null). However, since the TaskCompletionSource already has an exception set, awaiting it on line 347 will throw. This creates inconsistent behavior. Either remove the try-catch and let exceptions propagate naturally, or handle them properly without setting the exception on TaskCompletionSource. Note: This issue will be resolved when fixing the async pattern as mentioned in the other comment.

Suggested change
try
{
await InvokeVoidAsync("capturePicture", token, Id);
ret = await _captureTaskCompletionSource.Task;
}
catch (Exception ex)
{
_captureTaskCompletionSource.SetException(ex);
}
await InvokeVoidAsync("capturePicture", token, Id);
ret = await _captureTaskCompletionSource.Task;

Copilot uses AI. Check for mistakes.
}
return ret;
}

/// <summary>
/// 抓图返回文件流方法 由 Javascript 调用
/// </summary>
/// <param name="stream"></param>
/// <returns></returns>
[JSInvokable]
public async Task TriggerReceivePictureStream(IJSStreamReference stream)
{
if (_captureTaskCompletionSource != null)
{
_captureTaskCompletionSource.SetResult(stream);
}
}
Comment on lines +357 to +369
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

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

The TriggerReceivePictureStream method is never called from JavaScript, making it dead code. Since the capturePicture JavaScript function returns a value directly rather than using a callback pattern, this method should be removed along with the _captureTaskCompletionSource field (line 331).

Copilot uses AI. Check for mistakes.
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { init as initVision, login as loginVision, logout, startRealPlay, stopRealPlay, openSound, closeSound, setVolume, dispose as disposeVision } from '../hikvision.js';
import { init as initVision, login as loginVision, logout, startRealPlay, stopRealPlay, openSound, closeSound, setVolume, capturePicture, capturePictureAndDownload, dispose as disposeVision } from '../hikvision.js';
import Data from '../../BootstrapBlazor/modules/data.js';

export async function init(id, invoke) {
Expand Down Expand Up @@ -29,7 +29,7 @@ export async function login(id, ip, port, userName, password, loginType) {
return logined;
}

export { logout, startRealPlay, stopRealPlay, openSound, closeSound, setVolume }
export { logout, startRealPlay, stopRealPlay, openSound, closeSound, setVolume, capturePicture, capturePictureAndDownload }

export function dispose(id) {
disposeVision(id);
Expand Down
63 changes: 60 additions & 3 deletions src/components/BootstrapBlazor.HikVision/wwwroot/hikvision.js
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ export async function openSound(id) {
return 101;
}

let code = 0;
let code = 100;
try {
await WebVideoCtrl.I_OpenSound(iWndIndex);
}
Expand All @@ -374,7 +374,7 @@ export async function closeSound(id) {
return 101;
}

let code = 0;
let code = 100;
try {
await WebVideoCtrl.I_CloseSound(iWndIndex);
}
Expand All @@ -398,7 +398,7 @@ export async function setVolume(id, value) {
v = 50;
}

let code = 0;
let code = 100;
try {
await WebVideoCtrl.I_SetVolume(Math.min(100, Math.max(0, v)));
}
Expand All @@ -409,6 +409,63 @@ export async function setVolume(id, value) {
return code;
}

export async function capturePicture(id) {
const vision = Data.get(id);
const { realPlaying } = vision;

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

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

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

When realPlaying is not true, the function returns an empty string, but when other conditions fail it returns null. For consistency and to match the C# signature which expects IJSStreamReference?, this should also return null instead of an empty string when not playing.

Suggested change
return "";
return null;

Copilot uses AI. Check for mistakes.
}

try {
const base64 = await WebVideoCtrl.I_CapturePicData();
if (base64) {
const bytes = base64ToArray(base64);
return DotNet.createJSStreamReference(bytes.buffer);
}
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

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

The function doesn't explicitly return a value when base64 is falsy (line 422-425). This results in an implicit undefined return. For clarity and consistency with error handling (line 428), explicitly return null after the if block to handle the case where I_CapturePicData() returns a falsy value.

Suggested change
}
}
return null;

Copilot uses AI. Check for mistakes.
}
catch (ex) {
return null;
}
}

const base64ToArray = base64String => {
const base64Data = base64String.split(',')[1] || base64String;
const binaryString = atob(base64Data);
const length = binaryString.length;
const bytes = new Uint8Array(length);

for (let i = 0; i < length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}

return bytes;
}

export async function capturePictureAndDownload(id) {
const vision = Data.get(id);
const { realPlaying } = vision;

if (realPlaying !== true) {
return;
}

try {
const base64 = await WebVideoCtrl.I_CapturePicData();
if (base64) {
const anchorElement = document.createElement('a');
anchorElement.href = `data:image/jpg;base64,${base64}`;
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

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

The MIME type specified in the data URI is hardcoded as 'image/jpg', but the correct MIME type for JPEG images is 'image/jpeg'. While some browsers may accept 'image/jpg', using the standard 'image/jpeg' ensures better compatibility.

Suggested change
anchorElement.href = `data:image/jpg;base64,${base64}`;
anchorElement.href = `data:image/jpeg;base64,${base64}`;

Copilot uses AI. Check for mistakes.
anchorElement.download = `capture_${new Date().getTime()}.jpg`;
document.body.appendChild(anchorElement);
anchorElement.click();
document.body.removeChild(anchorElement);
}
}
catch (ex) {

}
}

export function dispose(id) {
const vision = Data.get(id);
const { realPlaying, logined, observer } = vision;
Expand Down