Skip to content

Commit a6e14bd

Browse files
refactor(IVideoDevice): add bool result from open method (#5945)
* refactor: 防止重复添加 * doc: 更新示例 * doc: 更新示例 * refactor: 移除 Flip 方法 * doc: 更新示例 * test: 更新单元测试 * doc: 移除调试信息 * refactor: open 方法增加返回值 * doc: 增加打开设备逻辑 * test: 更新单元测试 * refactor: close 方法增加返回值 * doc: 更新文档 * test: 更新单元测试 * doc: 增加自动选择设备逻辑 * doc: 增加自动关闭视频逻辑 * refactor: 移除水平翻转样式 * chore: bump version 9.6.1-beta02 Co-Authored-By: ChenHan819 <[email protected]> --------- Co-authored-by: ChenHan819 <[email protected]>
1 parent fde404c commit a6e14bd

File tree

10 files changed

+81
-90
lines changed

10 files changed

+81
-90
lines changed

src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@ private IBluetooth? BluetoothService { get; set; }</Pre>
1414
<div class="row form-inline g-3">
1515
<div class="col-12">
1616
<Button Text="@Localizer["VideoDeviceRequestText"]" Icon="fa-solid fa-photo-film" OnClick="OnRequestDevice"></Button>
17-
<Button Text="@Localizer["VideoDeviceOpenText"]" Icon="fa-solid fa-play" OnClick="OnOpenVideo" class="ms-2"></Button>
18-
<Button Text="@Localizer["VideoDeviceCloseText"]" Icon="fa-solid fa-stop" OnClick="OnCloseVideo" class="ms-2"></Button>
19-
<Button Text="@Localizer["VideoDeviceCaptureText"]" Icon="fa-solid fa-camera" OnClick="OnCapture" class="ms-2"></Button>
20-
<Button Text="@Localizer["VideoDeviceFlipText"]" Icon="fa-solid fa-camera-rotate" OnClick="OnFlip" class="ms-2"></Button>
17+
<Button Text="@Localizer["VideoDeviceOpenText"]" Icon="fa-solid fa-play" OnClick="OnOpenVideo" IsDisabled="_isOpen || string.IsNullOrEmpty(_deviceId)" class="ms-2"></Button>
18+
<Button Text="@Localizer["VideoDeviceCloseText"]" Icon="fa-solid fa-stop" OnClick="OnCloseVideo" IsDisabled="!_isOpen" class="ms-2"></Button>
19+
<Button Text="@Localizer["VideoDeviceCaptureText"]" Icon="fa-solid fa-camera" OnClick="OnCapture" IsDisabled="!_isOpen" class="ms-2"></Button>
2120
</div>
2221
<div class="col-12">
2322
<Select Items="@_items" @bind-Value="_deviceId" DisplayText="Devices" ShowLabel="true"></Select>

src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor.cs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace BootstrapBlazor.Server.Components.Samples;
88
/// <summary>
99
/// VideoDevice Component
1010
/// </summary>
11-
public partial class VideoDevices
11+
public partial class VideoDevices : IAsyncDisposable
1212
{
1313
[Inject, NotNull]
1414
private IVideoDevice? VideoDeviceService { get; set; }
@@ -21,13 +21,18 @@ public partial class VideoDevices
2121

2222
private string? _previewUrl;
2323

24+
private bool _isOpen = false;
25+
2426
private async Task OnRequestDevice()
2527
{
2628
var devices = await VideoDeviceService.GetDevices();
2729
if (devices != null)
2830
{
31+
_devices.Clear();
2932
_devices.AddRange(devices);
3033
_items = [.. _devices.Select(i => new SelectedItem(i.DeviceId, i.Label))];
34+
35+
_deviceId = _items.FirstOrDefault()?.Value;
3136
}
3237
}
3338

@@ -40,12 +45,13 @@ private async Task OnOpenVideo()
4045
DeviceId = _deviceId,
4146
VideoSelector = ".bb-video"
4247
};
43-
await VideoDeviceService.Open(constraints);
48+
_isOpen = await VideoDeviceService.Open(constraints);
4449
}
4550
}
4651

4752
private async Task OnCloseVideo()
4853
{
54+
_isOpen = false;
4955
_previewUrl = "";
5056
await VideoDeviceService.Close(".bb-video");
5157
}
@@ -55,8 +61,22 @@ private async Task OnCapture()
5561
_previewUrl = await VideoDeviceService.GetPreviewUrl();
5662
}
5763

58-
private async Task OnFlip()
64+
private async Task DisposeAsync(bool disposing)
65+
{
66+
if (disposing)
67+
{
68+
await OnCloseVideo();
69+
}
70+
}
71+
72+
/// <summary>
73+
/// <inheritdoc/>
74+
/// </summary>
75+
/// <returns></returns>
76+
/// <exception cref="NotImplementedException"></exception>
77+
public async ValueTask DisposeAsync()
5978
{
60-
await VideoDeviceService.Flip();
79+
await DisposeAsync(true);
80+
GC.SuppressFinalize(this);
6181
}
6282
}

src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor.css

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@
22
min-height: 240px;
33
height: auto;
44
width: auto;
5-
transform: scaleX(-1);
65
margin-top: 1rem;
76
display: block;
87
}
98

109
.bb-image {
1110
border: 1px solid var(--bs-border-color);
1211
border-radius: var(--bs-border-radius);
13-
transform: scaleX(-1);
1412
margin-top: 1rem;
1513
display: block;
1614
}

src/BootstrapBlazor/BootstrapBlazor.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk.Razor">
22

33
<PropertyGroup>
4-
<Version>9.6.1-beta01</Version>
4+
<Version>9.6.1-beta02</Version>
55
</PropertyGroup>
66

77
<ItemGroup>

src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,16 @@ private async Task<JSModule> LoadModule()
2121
return await module.InvokeAsync<List<MediaDeviceInfo>?>("enumerateDevices");
2222
}
2323

24-
public async Task Open(MediaTrackConstraints constraints)
24+
public async Task<bool> Open(MediaTrackConstraints constraints)
2525
{
2626
var module = await LoadModule();
27-
await module.InvokeVoidAsync("open", constraints);
27+
return await module.InvokeAsync<bool>("open", constraints);
2828
}
2929

30-
public async Task Close(string? videoSelector)
30+
public async Task<bool> Close(string? videoSelector)
3131
{
3232
var module = await LoadModule();
33-
await module.InvokeVoidAsync("close", videoSelector);
33+
return await module.InvokeAsync<bool>("close", videoSelector);
3434
}
3535

3636
public async Task Capture()
@@ -44,10 +44,4 @@ public async Task Capture()
4444
var module = await LoadModule();
4545
return await module.InvokeAsync<string?>("getPreviewUrl");
4646
}
47-
48-
public async Task Flip()
49-
{
50-
var module = await LoadModule();
51-
await module.InvokeAsync<string?>("flip");
52-
}
5347
}

src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ class DefaultVideoDevice(IMediaDevices deviceService) : IVideoDevice
2222
return ret;
2323
}
2424

25-
public Task Open(MediaTrackConstraints constraints)
25+
public Task<bool> Open(MediaTrackConstraints constraints)
2626
{
2727
return deviceService.Open(constraints);
2828
}
2929

30-
public Task Close(string? videoSelector)
30+
public Task<bool> Close(string? videoSelector)
3131
{
3232
return deviceService.Close(videoSelector);
3333
}
@@ -41,9 +41,4 @@ public Task Capture()
4141
{
4242
return deviceService.GetPreviewUrl();
4343
}
44-
45-
public Task Flip()
46-
{
47-
return deviceService.Flip();
48-
}
4944
}

src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ public interface IMediaDevices
2121
/// </summary>
2222
/// <param name="constraints"></param>
2323
/// <returns></returns>
24-
Task Open(MediaTrackConstraints constraints);
24+
Task<bool> Open(MediaTrackConstraints constraints);
2525

2626
/// <summary>
2727
/// The close() method of the MediaDevices interface stops capturing media from the specified device and closes the MediaStream object.
2828
/// </summary>
2929
/// <param name="videoSelector"></param>
3030
/// <returns></returns>
31-
Task Close(string? videoSelector);
31+
Task<bool> Close(string? videoSelector);
3232

3333
/// <summary>
3434
/// The capture() method of the MediaDevices interface captures a still image from the specified video stream and saves it to the specified location.
@@ -41,10 +41,4 @@ public interface IMediaDevices
4141
/// </summary>
4242
/// <returns></returns>
4343
Task<string?> GetPreviewUrl();
44-
45-
/// <summary>
46-
/// Flip the video device.
47-
/// </summary>
48-
/// <returns></returns>
49-
Task Flip();
5044
}

src/BootstrapBlazor/Services/MediaDevices/IVideoDevice.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ public interface IVideoDevice
2121
/// </summary>
2222
/// <param name="constraints"></param>
2323
/// <returns></returns>
24-
Task Open(MediaTrackConstraints constraints);
24+
Task<bool> Open(MediaTrackConstraints constraints);
2525

2626
/// <summary>
2727
/// Close the video device with the specified selector.
2828
/// </summary>
2929
/// <param name="videoSelector"></param>
3030
/// <returns></returns>
31-
Task Close(string? videoSelector);
31+
Task<bool> Close(string? videoSelector);
3232

3333
/// <summary>
3434
/// Capture a still image from the video stream.
@@ -53,10 +53,4 @@ public interface IVideoDevice
5353
/// </summary>
5454
/// <returns></returns>
5555
Task<string?> GetPreviewUrl();
56-
57-
/// <summary>
58-
/// Flip the video device.
59-
/// </summary>
60-
/// <returns></returns>
61-
Task Flip();
6256
}

src/BootstrapBlazor/wwwroot/modules/media.js

Lines changed: 36 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -29,34 +29,52 @@ export async function open(options) {
2929
if (height) {
3030
constrains.video.height = { ideal: height };
3131
}
32-
const stream = await navigator.mediaDevices.getUserMedia(constrains);
33-
const media = registerBootstrapBlazorModule("MediaDevices");
34-
media.stream = stream;
3532

36-
if (videoSelector) {
37-
const video = document.querySelector(videoSelector);
38-
if (video) {
39-
video.srcObject = stream;
33+
let ret = false;
34+
try {
35+
const stream = await navigator.mediaDevices.getUserMedia(constrains);
36+
const media = registerBootstrapBlazorModule("MediaDevices");
37+
media.stream = stream;
38+
39+
if (videoSelector) {
40+
const video = document.querySelector(videoSelector);
41+
if (video) {
42+
video.srcObject = stream;
43+
}
4044
}
45+
ret = true;
46+
}
47+
catch (err) {
48+
console.error("Error accessing media devices.", err);
4149
}
50+
return ret;
4251
}
4352

4453
export async function close(videoSelector) {
45-
if (videoSelector) {
46-
const video = document.querySelector(videoSelector);
47-
if (video) {
48-
video.pause();
49-
const stream = video.srcObject;
54+
let ret = false;
55+
56+
try {
57+
if (videoSelector) {
58+
const video = document.querySelector(videoSelector);
59+
if (video) {
60+
video.pause();
61+
const stream = video.srcObject;
62+
closeStream(stream);
63+
video.srcObject = null;
64+
}
65+
}
66+
const media = registerBootstrapBlazorModule("MediaDevices");
67+
const { stream } = media;
68+
if (stream && stream.active) {
5069
closeStream(stream);
51-
video.srcObject = null;
5270
}
71+
media.stream = null;
72+
ret = true;
5373
}
54-
const media = registerBootstrapBlazorModule("MediaDevices");
55-
const { stream } = media;
56-
if (stream && stream.active) {
57-
closeStream(stream);
74+
catch (err) {
75+
console.error("Error closing media devices.", err);
5876
}
59-
media.stream = null;
77+
return ret;
6078
}
6179

6280
export async function getPreviewUrl() {
@@ -75,31 +93,6 @@ export async function getPreviewUrl() {
7593
return url;
7694
}
7795

78-
export async function flip() {
79-
const media = registerBootstrapBlazorModule("MediaDevices");
80-
const { stream } = media;
81-
if (stream && stream.active) {
82-
const tracks = stream.getVideoTracks();
83-
if (tracks) {
84-
const track = tracks[0];
85-
const constraints = track.getSettings();
86-
const { facingMode } = constraints;
87-
if (facingMode === void 0) {
88-
console.log('facingMode is not supported');
89-
return;
90-
}
91-
92-
if (facingMode === "user" || facingMode.exact === "user" || facingMode.ideal === "user") {
93-
constraints.facingMode = { ideal: "environment" }
94-
}
95-
else {
96-
constraints.facingMode = { ideal: "user" }
97-
}
98-
await track.applyConstraints(constraints);
99-
}
100-
}
101-
}
102-
10396
const closeStream = stream => {
10497
if (stream) {
10598
const tracks = stream.getTracks();

test/UnitTest/Services/VideoDeviceTest.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ public async Task GetDevices_Ok()
2626
public async Task Open_Ok()
2727
{
2828
Context.JSInterop.Setup<string?>("getPreviewUrl").SetResult("blob:https://test-preview");
29+
Context.JSInterop.Setup<bool>("open", _ => true).SetResult(true);
30+
Context.JSInterop.Setup<bool>("close", _ => true).SetResult(true);
2931

3032
var service = Context.Services.GetRequiredService<IVideoDevice>();
3133
var options = new MediaTrackConstraints()
@@ -36,8 +38,11 @@ public async Task Open_Ok()
3638
Width = 480,
3739
VideoSelector = ".bb-video"
3840
};
39-
await service.Open(options);
40-
await service.Close(".bb-video");
41+
var open = await service.Open(options);
42+
Assert.True(open);
43+
44+
var close = await service.Close(".bb-video");
45+
Assert.True(close);
4146

4247
Assert.Equal("test-device-id", options.DeviceId);
4348
Assert.Equal("user", options.FacingMode);
@@ -46,7 +51,6 @@ public async Task Open_Ok()
4651
Assert.Equal(".bb-video", options.VideoSelector);
4752

4853
await service.Capture();
49-
await service.Flip();
5054
var url = await service.GetPreviewUrl();
5155
Assert.Equal("blob:https://test-preview", url);
5256
}

0 commit comments

Comments
 (0)