From 939eeb56b4626832110bd9df0fef8d53c3835947 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 1 May 2025 15:20:02 +0800 Subject: [PATCH 01/37] =?UTF-8?q?refactor:=20=E5=A2=9E=E5=8A=A0=20IMediaDe?= =?UTF-8?q?vices=20=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...tstrapBlazorServiceCollectionExtensions.cs | 1 + .../MediaDevices/DefaultMediaDevices.cs | 20 +++++++++++++++ .../Services/MediaDevices/IMediaDevices.cs | 25 +++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs create mode 100644 src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs diff --git a/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs b/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs index ab81e7c0203..ce2e804a7f3 100644 --- a/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs +++ b/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs @@ -86,6 +86,7 @@ public static IServiceCollection AddBootstrapBlazor(this IServiceCollection serv services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped(); + services.TryAddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs new file mode 100644 index 00000000000..753b2d33bf9 --- /dev/null +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + + +namespace BootstrapBlazor.Components; + +class DefaultMediaDevices : IMediaDevices +{ + public Task> EnumerateDevices() + { + throw new NotImplementedException(); + } + + public Task GetDisplayMedia(DisplayMediaOptions? options = null) + { + throw new NotImplementedException(); + } +} diff --git a/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs b/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs new file mode 100644 index 00000000000..77698256946 --- /dev/null +++ b/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace BootstrapBlazor.Components; + +/// +/// The MediaDevices interface of the Media Capture and Streams API provides access to connected media input devices like cameras and microphones, as well as screen sharing. In essence, it lets you obtain access to any hardware source of media data. +/// +public interface IMediaDevices +{ + /// + /// An array of MediaDeviceInfo objects. Each object in the array describes one of the available media input and output devices. + /// + /// + Task> EnumerateDevices(); + + /// + /// The getDisplayMedia() method of the MediaDevices interface prompts the user to select and grant permission to capture the contents of a display or portion thereof (such as a window) as a MediaStream. + /// + /// + /// + Task GetDisplayMedia(DisplayMediaOptions? options = null); +} From 95fc5ed0ed4febb010fe52e25b755108afc8f6b2 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 1 May 2025 19:02:13 +0800 Subject: [PATCH 02/37] =?UTF-8?q?doc:=20=E6=9B=B4=E6=96=B0=E7=A4=BA?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Samples/MediaDevice.razor | 43 +++++++++++++++++++ .../Components/Samples/MediaDevice.razor.cs | 26 +++++++++++ 2 files changed, 69 insertions(+) create mode 100644 src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor create mode 100644 src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs diff --git a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor new file mode 100644 index 00000000000..0dff422d5c4 --- /dev/null +++ b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor @@ -0,0 +1,43 @@ +@page "/media-device" +@inject IStringLocalizer Localizer + +

@Localizer["MediaDeviceTitle"]

+ +

@Localizer["MediaDeviceIntro"]

+ +

@((MarkupString)Localizer["MediaDeviceDescription"].Value)

+ +
[Inject, NotNull]
+    private IBluetooth? BluetoothService { get; set; }
+ + +
    +
  • @((MarkupString)Localizer["MediaDeviceTipsLi1"].Value)
  • +
  • @((MarkupString)Localizer["MediaDeviceTipsLi2"].Value)
  • +
+
@((MarkupString)Localizer["MediaDeviceTipsTitle"].Value)
+
+ + +
+ @Localizer["UsageDesc"] +
+
+
+ +
+
+ + @foreach (var d in _devices) + { +
+
@d.DeviceId
+
@d.GroupId
+
@d.Kind
+
@d.Label
+
+ } +
+ diff --git a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs new file mode 100644 index 00000000000..50b6922d720 --- /dev/null +++ b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace BootstrapBlazor.Server.Components.Samples; + +/// +/// +/// +public partial class MediaDevice +{ + [Inject, NotNull] + private IMediaDevices? MediaDevices { get; set; } + + private readonly List _devices = []; + + private async Task OnRequestDevice() + { + var devices = await MediaDevices.EnumerateDevices(); + if (devices != null) + { + _devices.AddRange(devices); + } + } +} From 9350eecdef4ad25315f876fc28684e90cd12ce4f Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 1 May 2025 19:02:24 +0800 Subject: [PATCH 03/37] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20media=20?= =?UTF-8?q?=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MediaDevices/DefaultMediaDevices.cs | 18 ++++++++--- .../MediaDevices/DisplayMediaOptions.cs | 13 ++++++++ .../Services/MediaDevices/IMediaDevices.cs | 2 +- .../Services/MediaDevices/MediaDeviceInfo.cs | 32 +++++++++++++++++++ .../Services/MediaDevices/MediaStream.cs | 13 ++++++++ src/BootstrapBlazor/wwwroot/modules/media.js | 11 +++++++ 6 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 src/BootstrapBlazor/Services/MediaDevices/DisplayMediaOptions.cs create mode 100644 src/BootstrapBlazor/Services/MediaDevices/MediaDeviceInfo.cs create mode 100644 src/BootstrapBlazor/Services/MediaDevices/MediaStream.cs create mode 100644 src/BootstrapBlazor/wwwroot/modules/media.js diff --git a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs index 753b2d33bf9..e25cd645ada 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs @@ -3,14 +3,24 @@ // See the LICENSE file in the project root for more information. // Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone - namespace BootstrapBlazor.Components; -class DefaultMediaDevices : IMediaDevices +class DefaultMediaDevices(IJSRuntime jsRuntime) : IMediaDevices { - public Task> EnumerateDevices() + private DotNetObjectReference? _interop = null; + private JSModule? _module = null; + + private async Task LoadModule() { - throw new NotImplementedException(); + _interop ??= DotNetObjectReference.Create(this); + _module ??= await jsRuntime.LoadModuleByName("media"); + return _module; + } + + public async Task?> EnumerateDevices() + { + var module = await LoadModule(); + return await module.InvokeAsync?>("enumerateDevices"); } public Task GetDisplayMedia(DisplayMediaOptions? options = null) diff --git a/src/BootstrapBlazor/Services/MediaDevices/DisplayMediaOptions.cs b/src/BootstrapBlazor/Services/MediaDevices/DisplayMediaOptions.cs new file mode 100644 index 00000000000..4bb94634d1b --- /dev/null +++ b/src/BootstrapBlazor/Services/MediaDevices/DisplayMediaOptions.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace BootstrapBlazor.Components; + +/// +/// +/// +public class DisplayMediaOptions +{ +} diff --git a/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs b/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs index 77698256946..28139c2a382 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs @@ -14,7 +14,7 @@ public interface IMediaDevices /// An array of MediaDeviceInfo objects. Each object in the array describes one of the available media input and output devices. /// /// - Task> EnumerateDevices(); + Task?> EnumerateDevices(); /// /// The getDisplayMedia() method of the MediaDevices interface prompts the user to select and grant permission to capture the contents of a display or portion thereof (such as a window) as a MediaStream. diff --git a/src/BootstrapBlazor/Services/MediaDevices/MediaDeviceInfo.cs b/src/BootstrapBlazor/Services/MediaDevices/MediaDeviceInfo.cs new file mode 100644 index 00000000000..fe753aaf9ff --- /dev/null +++ b/src/BootstrapBlazor/Services/MediaDevices/MediaDeviceInfo.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace BootstrapBlazor.Components; + +/// +/// The MediaDeviceInfo interface of the Media Capture and Streams API contains information that describes a single media input or output device. +/// +public class MediaDeviceInfo(string deviceId, string groupId, string kind, string label) +{ + /// + /// Returns a string that is an identifier for the represented device that is persisted across sessions. It is un-guessable by other applications and unique to the origin of the calling application. It is reset when the user clears cookies (for Private Browsing, a different identifier is used that is not persisted across sessions). + /// + public string DeviceId => deviceId; + + /// + /// Returns a string that is a group identifier. Two devices have the same group identifier if they belong to the same physical device — for example a monitor with both a built-in camera and a microphone. + /// + public string GroupId => groupId; + + /// + /// Returns a string that is a group identifier. Two devices have the same group identifier if they belong to the same physical device — for example a monitor with both a built-in camera and a microphone. + /// + public string Kind => kind; + + /// + /// Returns a string that is a group identifier. Two devices have the same group identifier if they belong to the same physical device — for example a monitor with both a built-in camera and a microphone. + /// + public string Label => label; +} diff --git a/src/BootstrapBlazor/Services/MediaDevices/MediaStream.cs b/src/BootstrapBlazor/Services/MediaDevices/MediaStream.cs new file mode 100644 index 00000000000..0efb2213cd8 --- /dev/null +++ b/src/BootstrapBlazor/Services/MediaDevices/MediaStream.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace BootstrapBlazor.Components; + +/// +/// The MediaStream interface of the Media Capture and Streams API represents a stream of media content. A stream consists of several tracks, such as video or audio tracks. Each track is specified as an instance of MediaStreamTrack. +/// +public class MediaStream +{ +} diff --git a/src/BootstrapBlazor/wwwroot/modules/media.js b/src/BootstrapBlazor/wwwroot/modules/media.js new file mode 100644 index 00000000000..f31f49446d9 --- /dev/null +++ b/src/BootstrapBlazor/wwwroot/modules/media.js @@ -0,0 +1,11 @@ +export async function enumerateDevices() { + if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) { + console.log("enumerateDevices() not supported."); + } + else { + const devices = await navigator.mediaDevices.enumerateDevices(); + devices.forEach(device => { + console.log(`${device.kind}: ${device.label} id = ${device.deviceId} ${device.groupId}`); + }); + } +} From ebcca89ccda48661bed5855b4399c8973194e687 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 1 May 2025 19:22:21 +0800 Subject: [PATCH 04/37] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20IMediaDevice?= =?UTF-8?q?Info=20=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MediaDevices/DefaultMediaDevices.cs | 2 +- .../Services/MediaDevices/IMediaDeviceInfo.cs | 32 +++++++++++++++++++ .../Services/MediaDevices/IMediaDevices.cs | 2 +- .../Services/MediaDevices/MediaDeviceInfo.cs | 25 +++------------ src/BootstrapBlazor/wwwroot/modules/media.js | 6 ++-- 5 files changed, 42 insertions(+), 25 deletions(-) create mode 100644 src/BootstrapBlazor/Services/MediaDevices/IMediaDeviceInfo.cs diff --git a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs index e25cd645ada..106246a740e 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs @@ -17,7 +17,7 @@ private async Task LoadModule() return _module; } - public async Task?> EnumerateDevices() + public async Task?> EnumerateDevices() { var module = await LoadModule(); return await module.InvokeAsync?>("enumerateDevices"); diff --git a/src/BootstrapBlazor/Services/MediaDevices/IMediaDeviceInfo.cs b/src/BootstrapBlazor/Services/MediaDevices/IMediaDeviceInfo.cs new file mode 100644 index 00000000000..ad0962bf892 --- /dev/null +++ b/src/BootstrapBlazor/Services/MediaDevices/IMediaDeviceInfo.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace BootstrapBlazor.Components; + +/// +/// The MediaDeviceInfo interface of the Media Capture and Streams API contains information that describes a single media input or output device. +/// +public interface IMediaDeviceInfo +{ + /// + /// Returns a string that is an identifier for the represented device that is persisted across sessions. It is un-guessable by other applications and unique to the origin of the calling application. It is reset when the user clears cookies (for Private Browsing, a different identifier is used that is not persisted across sessions). + /// + public string? DeviceId { get; } + + /// + /// Returns a string that is a group identifier. Two devices have the same group identifier if they belong to the same physical device — for example a monitor with both a built-in camera and a microphone. + /// + public string? GroupId { get; } + + /// + /// Returns a string that is a group identifier. Two devices have the same group identifier if they belong to the same physical device — for example a monitor with both a built-in camera and a microphone. + /// + public string? Kind { get; } + + /// + /// Returns a string that is a group identifier. Two devices have the same group identifier if they belong to the same physical device — for example a monitor with both a built-in camera and a microphone. + /// + public string? Label { get; } +} diff --git a/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs b/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs index 28139c2a382..5eb7e7fa0f0 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs @@ -14,7 +14,7 @@ public interface IMediaDevices /// An array of MediaDeviceInfo objects. Each object in the array describes one of the available media input and output devices. /// /// - Task?> EnumerateDevices(); + Task?> EnumerateDevices(); /// /// The getDisplayMedia() method of the MediaDevices interface prompts the user to select and grant permission to capture the contents of a display or portion thereof (such as a window) as a MediaStream. diff --git a/src/BootstrapBlazor/Services/MediaDevices/MediaDeviceInfo.cs b/src/BootstrapBlazor/Services/MediaDevices/MediaDeviceInfo.cs index fe753aaf9ff..7460897bfbf 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/MediaDeviceInfo.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/MediaDeviceInfo.cs @@ -5,28 +5,13 @@ namespace BootstrapBlazor.Components; -/// -/// The MediaDeviceInfo interface of the Media Capture and Streams API contains information that describes a single media input or output device. -/// -public class MediaDeviceInfo(string deviceId, string groupId, string kind, string label) +class MediaDeviceInfo : IMediaDeviceInfo { - /// - /// Returns a string that is an identifier for the represented device that is persisted across sessions. It is un-guessable by other applications and unique to the origin of the calling application. It is reset when the user clears cookies (for Private Browsing, a different identifier is used that is not persisted across sessions). - /// - public string DeviceId => deviceId; + public string? DeviceId { get; set; } - /// - /// Returns a string that is a group identifier. Two devices have the same group identifier if they belong to the same physical device — for example a monitor with both a built-in camera and a microphone. - /// - public string GroupId => groupId; + public string? GroupId { get; set; } - /// - /// Returns a string that is a group identifier. Two devices have the same group identifier if they belong to the same physical device — for example a monitor with both a built-in camera and a microphone. - /// - public string Kind => kind; + public string? Kind { get; set; } - /// - /// Returns a string that is a group identifier. Two devices have the same group identifier if they belong to the same physical device — for example a monitor with both a built-in camera and a microphone. - /// - public string Label => label; + public string? Label { get; set; } } diff --git a/src/BootstrapBlazor/wwwroot/modules/media.js b/src/BootstrapBlazor/wwwroot/modules/media.js index f31f49446d9..d98b6dd18b6 100644 --- a/src/BootstrapBlazor/wwwroot/modules/media.js +++ b/src/BootstrapBlazor/wwwroot/modules/media.js @@ -1,11 +1,11 @@ export async function enumerateDevices() { + let ret = null; if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) { console.log("enumerateDevices() not supported."); } else { const devices = await navigator.mediaDevices.enumerateDevices(); - devices.forEach(device => { - console.log(`${device.kind}: ${device.label} id = ${device.deviceId} ${device.groupId}`); - }); + ret = devices; } + return ret; } From c42dab6e9028d0d84b372388c6910ed67306fcf9 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 1 May 2025 19:22:29 +0800 Subject: [PATCH 05/37] =?UTF-8?q?doc:=20=E6=9B=B4=E6=96=B0=E7=A4=BA?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Samples/MediaDevice.razor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs index 50b6922d720..a6dfd53221d 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs @@ -13,7 +13,7 @@ public partial class MediaDevice [Inject, NotNull] private IMediaDevices? MediaDevices { get; set; } - private readonly List _devices = []; + private readonly List _devices = []; private async Task OnRequestDevice() { From d31e5597eb806ea6abcae9d805043ec99a66daa6 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 1 May 2025 19:34:04 +0800 Subject: [PATCH 06/37] =?UTF-8?q?refactor:=20=E5=A2=9E=E5=8A=A0=20getUserM?= =?UTF-8?q?edia=20=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor/wwwroot/modules/media.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/BootstrapBlazor/wwwroot/modules/media.js b/src/BootstrapBlazor/wwwroot/modules/media.js index d98b6dd18b6..c2432308cd1 100644 --- a/src/BootstrapBlazor/wwwroot/modules/media.js +++ b/src/BootstrapBlazor/wwwroot/modules/media.js @@ -1,10 +1,16 @@ export async function enumerateDevices() { let ret = null; - if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) { + if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia || !navigator.mediaDevices.enumerateDevices) { console.log("enumerateDevices() not supported."); } else { + const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); + console.log(stream); + const devices = await navigator.mediaDevices.enumerateDevices(); + devices.forEach(d => { + console.log(d); + }); ret = devices; } return ret; From ca78fe97514db5d5d88ced57f0e1d92b35fb806b Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 1 May 2025 21:59:27 +0800 Subject: [PATCH 07/37] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...tstrapBlazorServiceCollectionExtensions.cs | 1 + .../MediaDevices/DefaultMediaDevices.cs | 12 ++++++ .../MediaDevices/DefaultMediaVideo.cs | 34 ++++++++++++++++ .../Services/MediaDevices/IMediaDeviceInfo.cs | 16 ++++---- .../Services/MediaDevices/IMediaDevices.cs | 14 +++++++ .../Services/MediaDevices/IMediaVideo.cs | 32 +++++++++++++++ .../Services/MediaDevices/MediaDeviceInfo.cs | 8 ++-- .../MediaDevices/MediaTrackConstraints.cs | 37 ++++++++++++++++++ src/BootstrapBlazor/wwwroot/modules/media.js | 39 ++++++++++++++++--- 9 files changed, 175 insertions(+), 18 deletions(-) create mode 100644 src/BootstrapBlazor/Services/MediaDevices/DefaultMediaVideo.cs create mode 100644 src/BootstrapBlazor/Services/MediaDevices/IMediaVideo.cs create mode 100644 src/BootstrapBlazor/Services/MediaDevices/MediaTrackConstraints.cs diff --git a/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs b/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs index ce2e804a7f3..695316b8847 100644 --- a/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs +++ b/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs @@ -87,6 +87,7 @@ public static IServiceCollection AddBootstrapBlazor(this IServiceCollection serv services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped(); + services.TryAddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs index 106246a740e..973dbdeab37 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs @@ -27,4 +27,16 @@ public Task GetDisplayMedia(DisplayMediaOptions? options = null) { throw new NotImplementedException(); } + + public async Task Open(MediaTrackConstraints constraints) + { + var module = await LoadModule(); + await module.InvokeVoidAsync("open", constraints); + } + + public async Task Close(string selector) + { + var module = await LoadModule(); + await module.InvokeVoidAsync("close", selector); + } } diff --git a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaVideo.cs b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaVideo.cs new file mode 100644 index 00000000000..0afd09f0d1c --- /dev/null +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaVideo.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace BootstrapBlazor.Components; + +class DefaultMediaVideo(IMediaDevices deviceService) : IMediaVideo +{ + /// + /// + /// + /// + public async Task?> GetDevices() + { + var ret = new List(); + var devices = await deviceService.EnumerateDevices(); + if (devices != null) + { + ret.AddRange(devices.Where(d => d.Kind == "videoinput")); + } + return ret; + } + + public Task Open(MediaTrackConstraints constraints) + { + return deviceService.Open(constraints); + } + + public Task Close(string selector) + { + return deviceService.Close(selector); + } +} diff --git a/src/BootstrapBlazor/Services/MediaDevices/IMediaDeviceInfo.cs b/src/BootstrapBlazor/Services/MediaDevices/IMediaDeviceInfo.cs index ad0962bf892..71c57b71a37 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/IMediaDeviceInfo.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/IMediaDeviceInfo.cs @@ -11,22 +11,22 @@ namespace BootstrapBlazor.Components; public interface IMediaDeviceInfo { /// - /// Returns a string that is an identifier for the represented device that is persisted across sessions. It is un-guessable by other applications and unique to the origin of the calling application. It is reset when the user clears cookies (for Private Browsing, a different identifier is used that is not persisted across sessions). + /// The deviceId read-only property of the MediaDeviceInfo interface returns a string that is an identifier for the represented device and is persisted across sessions. /// - public string? DeviceId { get; } + public string DeviceId { get; } /// - /// Returns a string that is a group identifier. Two devices have the same group identifier if they belong to the same physical device — for example a monitor with both a built-in camera and a microphone. + /// The groupId read-only property of the MediaDeviceInfo interface returns a string that is a group identifier. /// - public string? GroupId { get; } + public string GroupId { get; } /// - /// Returns a string that is a group identifier. Two devices have the same group identifier if they belong to the same physical device — for example a monitor with both a built-in camera and a microphone. + /// The kind read-only property of the MediaDeviceInfo interface returns an enumerated value, that is either "videoinput", "audioinput" or "audiooutput". /// - public string? Kind { get; } + public string Kind { get; } /// - /// Returns a string that is a group identifier. Two devices have the same group identifier if they belong to the same physical device — for example a monitor with both a built-in camera and a microphone. + /// The label read-only property of the MediaDeviceInfo interface returns a string describing this device (for example "External USB Webcam"). /// - public string? Label { get; } + public string Label { get; } } diff --git a/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs b/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs index 5eb7e7fa0f0..8058fd8d7f3 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs @@ -22,4 +22,18 @@ public interface IMediaDevices /// /// Task GetDisplayMedia(DisplayMediaOptions? options = null); + + /// + /// The open() method of the MediaDevices interface creates a new MediaStream object and starts capturing media from the specified device. + /// + /// + /// + Task Open(MediaTrackConstraints constraints); + + /// + /// The close() method of the MediaDevices interface stops capturing media from the specified device and closes the MediaStream object. + /// + /// + /// + Task Close(string selector); } diff --git a/src/BootstrapBlazor/Services/MediaDevices/IMediaVideo.cs b/src/BootstrapBlazor/Services/MediaDevices/IMediaVideo.cs new file mode 100644 index 00000000000..5e7a1a074e5 --- /dev/null +++ b/src/BootstrapBlazor/Services/MediaDevices/IMediaVideo.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace BootstrapBlazor.Components; + +/// +/// +/// +public interface IMediaVideo +{ + /// + /// + /// + /// + Task?> GetDevices(); + + /// + /// + /// + /// + /// + Task Open(MediaTrackConstraints constraints); + + /// + /// + /// + /// + /// + Task Close(string selector); +} diff --git a/src/BootstrapBlazor/Services/MediaDevices/MediaDeviceInfo.cs b/src/BootstrapBlazor/Services/MediaDevices/MediaDeviceInfo.cs index 7460897bfbf..6d2dd3bfcba 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/MediaDeviceInfo.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/MediaDeviceInfo.cs @@ -7,11 +7,11 @@ namespace BootstrapBlazor.Components; class MediaDeviceInfo : IMediaDeviceInfo { - public string? DeviceId { get; set; } + public string DeviceId { get; set; } = ""; - public string? GroupId { get; set; } + public string GroupId { get; set; } = ""; - public string? Kind { get; set; } + public string Kind { get; set; } = ""; - public string? Label { get; set; } + public string Label { get; set; } = ""; } diff --git a/src/BootstrapBlazor/Services/MediaDevices/MediaTrackConstraints.cs b/src/BootstrapBlazor/Services/MediaDevices/MediaTrackConstraints.cs new file mode 100644 index 00000000000..c9cb83204de --- /dev/null +++ b/src/BootstrapBlazor/Services/MediaDevices/MediaTrackConstraints.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace BootstrapBlazor.Components; + +/// +/// The MediaTrackConstraints interface of the Media Capture and Streams API is used to specify constraints on the media tracks that are requested from a media device. It is used in conjunction with the getUserMedia() method to specify the desired properties of the media tracks, such as resolution, frame rate, and aspect ratio. +/// +public class MediaTrackConstraints +{ + /// + /// + /// + public string DeviceId { get; set; } = ""; + + /// + /// + /// + public string? VideoSelector { get; set; } + + /// + /// + /// + public int Width { get; set; } + + /// + /// + /// + public int Height { get; set; } + + /// + /// + /// + public string? FacingMode { get; set; } +} diff --git a/src/BootstrapBlazor/wwwroot/modules/media.js b/src/BootstrapBlazor/wwwroot/modules/media.js index c2432308cd1..7e2d2619c8c 100644 --- a/src/BootstrapBlazor/wwwroot/modules/media.js +++ b/src/BootstrapBlazor/wwwroot/modules/media.js @@ -4,14 +4,41 @@ console.log("enumerateDevices() not supported."); } else { - const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); - console.log(stream); - + await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); const devices = await navigator.mediaDevices.enumerateDevices(); - devices.forEach(d => { - console.log(d); - }); ret = devices; } return ret; } + +export async function open(options) { + const constrains = { + video: { + facingMode: options.facingMode || "environment", + deviceId: options.deviceId ? { exact: options.deviceId } : null, + }, + audio: false + } + const video = document.querySelector(options.videoSelector); + if (video) { + const stream = await navigator.mediaDevices.getUserMedia(constrains); + video.srcObject = stream; + } +} + +export async function close(videoSelector) { + const video = document.querySelector(videoSelector); + if (video) { + video.pause(); + const stream = video.srcObject; + if (stream) { + const tracks = stream.getTracks(); + + tracks.forEach(track => { + track.stop(); + }); + + video.srcObject = null; + } + } +} From 53d788a7e8aecd400e835bb23d7b5db2257f7f71 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 1 May 2025 21:59:34 +0800 Subject: [PATCH 08/37] =?UTF-8?q?doc:=20=E5=A2=9E=E5=8A=A0=E7=A4=BA?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Samples/MediaDevice.razor | 16 ++++------- .../Components/Samples/MediaDevice.razor.cs | 28 ++++++++++++++++++- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor index 0dff422d5c4..7d6ac0e9e54 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor @@ -26,18 +26,14 @@
- + + +
- @foreach (var d in _devices) - { -
-
@d.DeviceId
-
@d.GroupId
-
@d.Kind
-
@d.Label
-
- } + + + diff --git a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs index a6dfd53221d..f72cc3850c5 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs @@ -13,14 +13,40 @@ public partial class MediaDevice [Inject, NotNull] private IMediaDevices? MediaDevices { get; set; } + [Inject, NotNull] + private IMediaVideo? VideoDeviceService { get; set; } + private readonly List _devices = []; + private List _items = []; + + private string? _deviceId; + private async Task OnRequestDevice() { - var devices = await MediaDevices.EnumerateDevices(); + var devices = await VideoDeviceService.GetDevices(); if (devices != null) { _devices.AddRange(devices); + _items = [.. _devices.Select(i => new SelectedItem(i.DeviceId, i.Label))]; + } + } + + private async Task OnOpenVideo() + { + if (!string.IsNullOrEmpty(_deviceId)) + { + var constraints = new MediaTrackConstraints + { + DeviceId = _deviceId, + VideoSelector = ".bb-video" + }; + await VideoDeviceService.Open(constraints); } } + + private async Task OnCloseVideo() + { + await VideoDeviceService.Close(".bb-video"); + } } From 912503a7c19315f4bcbe37915454d84c8e539560 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 10:04:50 +0800 Subject: [PATCH 09/37] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20IMeidaVideo?= =?UTF-8?q?=20=E6=8E=A5=E5=8F=A3=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MediaDevices/DefaultMediaDevices.cs | 6 ++++ .../MediaDevices/DefaultMediaVideo.cs | 5 ++++ .../Services/MediaDevices/IMediaDevices.cs | 7 +++++ .../Services/MediaDevices/IMediaVideo.cs | 7 +++++ src/BootstrapBlazor/wwwroot/modules/media.js | 28 +++++++++++++++++++ 5 files changed, 53 insertions(+) diff --git a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs index 973dbdeab37..ee17b936f9e 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs @@ -39,4 +39,10 @@ public async Task Close(string selector) var module = await LoadModule(); await module.InvokeVoidAsync("close", selector); } + + public async Task Capture(string selector) + { + var module = await LoadModule(); + await module.InvokeVoidAsync("capture", selector); + } } diff --git a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaVideo.cs b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaVideo.cs index 0afd09f0d1c..eb0d73d0a5b 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaVideo.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaVideo.cs @@ -31,4 +31,9 @@ public Task Close(string selector) { return deviceService.Close(selector); } + + public Task Capture(string selector) + { + return deviceService.Capture(selector); + } } diff --git a/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs b/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs index 8058fd8d7f3..05031a0fc22 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs @@ -36,4 +36,11 @@ public interface IMediaDevices /// /// Task Close(string selector); + + /// + /// The capture() method of the MediaDevices interface captures a still image from the specified video stream and saves it to the specified location. + /// + /// + /// + Task Capture(string selector); } diff --git a/src/BootstrapBlazor/Services/MediaDevices/IMediaVideo.cs b/src/BootstrapBlazor/Services/MediaDevices/IMediaVideo.cs index 5e7a1a074e5..a7fc35a6827 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/IMediaVideo.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/IMediaVideo.cs @@ -29,4 +29,11 @@ public interface IMediaVideo /// /// Task Close(string selector); + + /// + /// + /// + /// + /// + Task Capture(string selector); } diff --git a/src/BootstrapBlazor/wwwroot/modules/media.js b/src/BootstrapBlazor/wwwroot/modules/media.js index 7e2d2619c8c..2c7ca78ad77 100644 --- a/src/BootstrapBlazor/wwwroot/modules/media.js +++ b/src/BootstrapBlazor/wwwroot/modules/media.js @@ -42,3 +42,31 @@ export async function close(videoSelector) { } } } + +export async function capture(videoSelector) { + const video = document.querySelector(videoSelector); + if (video) { + const stream = video.srcObject; + if (stream) { + const tracks = stream.getVideoTracks(); + if (tracks) { + const track = tracks[0]; + const capture = new ImageCapture(track); + const blob = await capture.takePhoto(); + const image = await createImageBitmap(blob); + const { offsetWidth, offsetHeight } = video; + drawImage(document.querySelector(".b-video-image"), image, offsetWidth, offsetHeight); + } + } + } +} + +const drawImage = (canvas, image, offsetWidth, offsetHeight) => { + canvas.width = offsetWidth * devicePixelRatio; + canvas.height = offsetHeight * devicePixelRatio; + canvas.style.width = `${offsetWidth}px`; + canvas.style.height = `${offsetHeight}px`; + const context = canvas.getContext('2d') + //context.scale(devicePixelRatio, devicePixelRatio) + context.drawImage(image, 0, 0, offsetWidth, offsetHeight); +} From c9cf582520bfac694d7bd2e3dce887224d8df3ed Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 10:04:57 +0800 Subject: [PATCH 10/37] =?UTF-8?q?doc:=20=E6=9B=B4=E6=96=B0=E7=A4=BA?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Samples/MediaDevice.razor | 3 +++ .../Components/Samples/MediaDevice.razor.cs | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor index 7d6ac0e9e54..e7f380715f2 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor @@ -29,11 +29,14 @@ + + + diff --git a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs index f72cc3850c5..91f7cfdb257 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs @@ -49,4 +49,9 @@ private async Task OnCloseVideo() { await VideoDeviceService.Close(".bb-video"); } + + private async Task OnCapture() + { + await VideoDeviceService.Capture(".bb-video"); + } } From ac7faec422f8de9d3390228e45f76262efcf60f6 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 10:22:29 +0800 Subject: [PATCH 11/37] =?UTF-8?q?refactor:=20=E4=BC=98=E5=8C=96=20register?= =?UTF-8?q?BootstrapBlazorModule=20=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor/wwwroot/modules/utility.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/BootstrapBlazor/wwwroot/modules/utility.js b/src/BootstrapBlazor/wwwroot/modules/utility.js index f824177237d..fdbd159c44c 100644 --- a/src/BootstrapBlazor/wwwroot/modules/utility.js +++ b/src/BootstrapBlazor/wwwroot/modules/utility.js @@ -822,7 +822,9 @@ export function registerBootstrapBlazorModule(name, identifier, callback) { } if (this._init === false) { this._init = true; - cb(this); + if (isFunction(cb)) { + cb(this); + } } return this; }, @@ -830,9 +832,11 @@ export function registerBootstrapBlazorModule(name, identifier, callback) { if (id) { this._items = this._items.filter(item => item !== id); } - if (this._items.length === 0 && cb) { + if (this._items.length === 0) { this._init = false; - cb(this); + if (isFunction(cb)) { + cb(this); + } } } }; From cc37a8af72f864e9e0e5516f281f84f9a4c3b36d Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 10:22:54 +0800 Subject: [PATCH 12/37] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20drawImage=20?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor/wwwroot/modules/utility.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/BootstrapBlazor/wwwroot/modules/utility.js b/src/BootstrapBlazor/wwwroot/modules/utility.js index fdbd159c44c..b5ea69e7025 100644 --- a/src/BootstrapBlazor/wwwroot/modules/utility.js +++ b/src/BootstrapBlazor/wwwroot/modules/utility.js @@ -876,6 +876,16 @@ export function setMemorialMode(memorial) { } } +export function drawImage(canvas, image, offsetWidth, offsetHeight) { + canvas.width = offsetWidth * devicePixelRatio; + canvas.height = offsetHeight * devicePixelRatio; + canvas.style.width = `${offsetWidth}px`; + canvas.style.height = `${offsetHeight}px`; + const context = canvas.getContext('2d'); + context.scale(devicePixelRatio, devicePixelRatio); + context.drawImage(image, 0, 0, offsetWidth, offsetHeight); +} + export { autoAdd, autoRemove, From 1cf7e5ad7443c79b19a770000fbeff70e03885b0 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 11:02:13 +0800 Subject: [PATCH 13/37] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4=20GetDispl?= =?UTF-8?q?ayMedia=20=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Services/MediaDevices/DefaultMediaDevices.cs | 5 ----- src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs | 7 ------- 2 files changed, 12 deletions(-) diff --git a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs index ee17b936f9e..97f5ef9aada 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs @@ -23,11 +23,6 @@ private async Task LoadModule() return await module.InvokeAsync?>("enumerateDevices"); } - public Task GetDisplayMedia(DisplayMediaOptions? options = null) - { - throw new NotImplementedException(); - } - public async Task Open(MediaTrackConstraints constraints) { var module = await LoadModule(); diff --git a/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs b/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs index 05031a0fc22..29a8124d57a 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs @@ -16,13 +16,6 @@ public interface IMediaDevices /// Task?> EnumerateDevices(); - /// - /// The getDisplayMedia() method of the MediaDevices interface prompts the user to select and grant permission to capture the contents of a display or portion thereof (such as a window) as a MediaStream. - /// - /// - /// - Task GetDisplayMedia(DisplayMediaOptions? options = null); - /// /// The open() method of the MediaDevices interface creates a new MediaStream object and starts capturing media from the specified device. /// From 63d1f6cdd82a9f5f4549837f0712acea58a1e244 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 11:02:21 +0800 Subject: [PATCH 14/37] =?UTF-8?q?feat:=20=E7=B2=BE=E7=AE=80=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor/wwwroot/modules/media.js | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/BootstrapBlazor/wwwroot/modules/media.js b/src/BootstrapBlazor/wwwroot/modules/media.js index 2c7ca78ad77..fe7ac48d122 100644 --- a/src/BootstrapBlazor/wwwroot/modules/media.js +++ b/src/BootstrapBlazor/wwwroot/modules/media.js @@ -1,4 +1,10 @@ -export async function enumerateDevices() { +import { drawImage } from "./utility.js" + +window.BootstrapBlazor = window.BootstrapBlazor || {}; +window.BootstrapBlazor[name] = window.BootstrapBlazor[name] || { + + +export async function enumerateDevices() { let ret = null; if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia || !navigator.mediaDevices.enumerateDevices) { console.log("enumerateDevices() not supported."); @@ -60,13 +66,3 @@ export async function capture(videoSelector) { } } } - -const drawImage = (canvas, image, offsetWidth, offsetHeight) => { - canvas.width = offsetWidth * devicePixelRatio; - canvas.height = offsetHeight * devicePixelRatio; - canvas.style.width = `${offsetWidth}px`; - canvas.style.height = `${offsetHeight}px`; - const context = canvas.getContext('2d') - //context.scale(devicePixelRatio, devicePixelRatio) - context.drawImage(image, 0, 0, offsetWidth, offsetHeight); -} From 890a515ff7da3f02aa8094f04ce3cc7b6b68e187 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 11:02:27 +0800 Subject: [PATCH 15/37] =?UTF-8?q?doc:=20=E6=9B=B4=E6=96=B0=E7=A4=BA?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Samples/MediaDevice.razor.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs index 91f7cfdb257..68168d991cf 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs @@ -10,9 +10,6 @@ namespace BootstrapBlazor.Server.Components.Samples; ///
public partial class MediaDevice { - [Inject, NotNull] - private IMediaDevices? MediaDevices { get; set; } - [Inject, NotNull] private IMediaVideo? VideoDeviceService { get; set; } From 21d216c7646a5465069f015e8a10dbfc1bbd0176 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 11:52:12 +0800 Subject: [PATCH 16/37] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=20IMediaVi?= =?UTF-8?q?deo=20=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MediaDevices/DefaultMediaVideo.cs | 19 +++++++++-- .../Services/MediaDevices/IMediaVideo.cs | 34 ++++++++++++++----- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaVideo.cs b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaVideo.cs index eb0d73d0a5b..842438b3f00 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaVideo.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaVideo.cs @@ -27,13 +27,28 @@ public Task Open(MediaTrackConstraints constraints) return deviceService.Open(constraints); } - public Task Close(string selector) + public Task Close() { return deviceService.Close(selector); } - public Task Capture(string selector) + public Task Capture() { return deviceService.Capture(selector); } + + public Task Preview() + { + throw new NotImplementedException(); + } + + public Task GetPreviewImage() + { + throw new NotImplementedException(); + } + + public Task GetPreviewUrl() + { + throw new NotImplementedException(); + } } diff --git a/src/BootstrapBlazor/Services/MediaDevices/IMediaVideo.cs b/src/BootstrapBlazor/Services/MediaDevices/IMediaVideo.cs index a7fc35a6827..1296d1d3a09 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/IMediaVideo.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/IMediaVideo.cs @@ -6,34 +6,50 @@ namespace BootstrapBlazor.Components; /// -/// +/// Video Media Device Interface /// public interface IMediaVideo { /// - /// + /// Gets the list of video devices. /// /// Task?> GetDevices(); /// - /// + /// Opens the video device with the specified constraints. /// /// /// Task Open(MediaTrackConstraints constraints); /// - /// + /// Close the video device with the specified selector. /// - /// /// - Task Close(string selector); + Task Close(); /// - /// + /// Capture a still image from the video stream. /// - /// /// - Task Capture(string selector); + Task Capture(); + + /// + /// Preview a still image from the video stream. + /// + /// + Task Preview(); + + /// + /// Gets the stream of the captured image. + /// + /// + Task GetPreviewImage(); + + /// + /// Gets the preview URL of the captured image. + /// + /// + Task GetPreviewUrl(); } From cb4f77ea28cab423a52ce162193574693314ad58 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 11:52:23 +0800 Subject: [PATCH 17/37] =?UTF-8?q?refactor:=20=E6=9B=B4=E6=96=B0=E8=84=9A?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor/wwwroot/modules/media.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/BootstrapBlazor/wwwroot/modules/media.js b/src/BootstrapBlazor/wwwroot/modules/media.js index fe7ac48d122..530618d42b4 100644 --- a/src/BootstrapBlazor/wwwroot/modules/media.js +++ b/src/BootstrapBlazor/wwwroot/modules/media.js @@ -1,8 +1,4 @@ -import { drawImage } from "./utility.js" - -window.BootstrapBlazor = window.BootstrapBlazor || {}; -window.BootstrapBlazor[name] = window.BootstrapBlazor[name] || { - +import { drawImage, registerBootstrapBlazorModule } from "./utility.js" export async function enumerateDevices() { let ret = null; @@ -20,7 +16,7 @@ export async function enumerateDevices() { export async function open(options) { const constrains = { video: { - facingMode: options.facingMode || "environment", + facingMode: { exact: options.facingMode || "environment" }, deviceId: options.deviceId ? { exact: options.deviceId } : null, }, audio: false @@ -61,7 +57,10 @@ export async function capture(videoSelector) { const blob = await capture.takePhoto(); const image = await createImageBitmap(blob); const { offsetWidth, offsetHeight } = video; - drawImage(document.querySelector(".b-video-image"), image, offsetWidth, offsetHeight); + drawImage(document.querySelector(".bb-video-image"), image, offsetWidth, offsetHeight); + + const img = document.querySelector(".bb-image"); + img.src = URL.createObjectURL(blob); } } } From 62ab88d9e62a6da22ba743c2aabd52665db3b663 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 11:52:33 +0800 Subject: [PATCH 18/37] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Camera/Camera.razor.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/BootstrapBlazor/Components/Camera/Camera.razor.js b/src/BootstrapBlazor/Components/Camera/Camera.razor.js index 37329c3a68f..0c6783dc14f 100644 --- a/src/BootstrapBlazor/Components/Camera/Camera.razor.js +++ b/src/BootstrapBlazor/Components/Camera/Camera.razor.js @@ -136,22 +136,18 @@ export function download(id, fileName) { createEl.remove(); } -export function resize(id, width, height) { +export async function resize(id, width, height) { const camera = Data.get(id) if (camera === null || camera.video === void 0) { return } const constrains = { - video: { - deviceId: camera.video.deviceId, - width: { ideal: width }, - height: { ideal: height } - } + facingMode: "environment", + width: { ideal: width }, + height: { ideal: height } } - - stopDevice(camera) - play(camera, constrains) + await camera.video.track.applyConstraints(constrains); } export function dispose(id) { From 9553e17be1ce0fdf80585062d64d8feb4ac8e854 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 11:52:39 +0800 Subject: [PATCH 19/37] =?UTF-8?q?doc:=20=E6=9B=B4=E6=96=B0=E7=A4=BA?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Samples/MediaDevice.razor | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor index e7f380715f2..851667f5ac0 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor @@ -37,6 +37,9 @@ - + + +
Image
+ From 9555a5c7b76aee70227b48549d6c4d1ba0f76e7c Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 12:48:45 +0800 Subject: [PATCH 20/37] =?UTF-8?q?refactor:=20=E6=9B=B4=E6=96=B0=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Samples/MediaDevice.razor.cs | 6 ++++-- .../Services/MediaDevices/DefaultMediaDevices.cs | 8 ++++---- .../Services/MediaDevices/DefaultMediaVideo.cs | 4 ++-- .../Services/MediaDevices/IMediaDevices.cs | 6 ++---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs index 68168d991cf..2f97ca8b2ab 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs @@ -44,11 +44,13 @@ private async Task OnOpenVideo() private async Task OnCloseVideo() { - await VideoDeviceService.Close(".bb-video"); + await Task.Delay(1); + //await VideoDeviceService.Close(".bb-video"); } private async Task OnCapture() { - await VideoDeviceService.Capture(".bb-video"); + await Task.Delay(1); + //await VideoDeviceService.Capture(".bb-video"); } } diff --git a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs index 97f5ef9aada..441766fd8f6 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs @@ -29,15 +29,15 @@ public async Task Open(MediaTrackConstraints constraints) await module.InvokeVoidAsync("open", constraints); } - public async Task Close(string selector) + public async Task Close() { var module = await LoadModule(); - await module.InvokeVoidAsync("close", selector); + await module.InvokeVoidAsync("close", ""); } - public async Task Capture(string selector) + public async Task Capture() { var module = await LoadModule(); - await module.InvokeVoidAsync("capture", selector); + await module.InvokeVoidAsync("capture", ""); } } diff --git a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaVideo.cs b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaVideo.cs index 842438b3f00..39036567b12 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaVideo.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaVideo.cs @@ -29,12 +29,12 @@ public Task Open(MediaTrackConstraints constraints) public Task Close() { - return deviceService.Close(selector); + return deviceService.Close(); } public Task Capture() { - return deviceService.Capture(selector); + return deviceService.Capture(); } public Task Preview() diff --git a/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs b/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs index 29a8124d57a..ad94244e026 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs @@ -26,14 +26,12 @@ public interface IMediaDevices /// /// The close() method of the MediaDevices interface stops capturing media from the specified device and closes the MediaStream object. /// - /// /// - Task Close(string selector); + Task Close(); /// /// The capture() method of the MediaDevices interface captures a still image from the specified video stream and saves it to the specified location. /// - /// /// - Task Capture(string selector); + Task Capture(); } From 229906745ac0253a189249999a9a6ad3707d4c8e Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 12:49:16 +0800 Subject: [PATCH 21/37] =?UTF-8?q?refactor:=20=E5=A2=9E=E5=8A=A0=E9=95=9C?= =?UTF-8?q?=E5=A4=B4=E7=BF=BB=E8=BD=AC=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Samples/Cameras.razor | 1 + .../Components/Samples/Cameras.razor.cs | 2 ++ .../Components/Camera/Camera.razor.cs | 6 ++++ .../Components/Camera/Camera.razor.js | 35 +++++++++++++++++-- 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/BootstrapBlazor.Server/Components/Samples/Cameras.razor b/src/BootstrapBlazor.Server/Components/Samples/Cameras.razor index f928bbaacdc..3568e1ac48b 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/Cameras.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/Cameras.razor @@ -42,6 +42,7 @@ +
diff --git a/src/BootstrapBlazor.Server/Components/Samples/Cameras.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/Cameras.razor.cs index 24df426c884..c2d3dd11f42 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/Cameras.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/Cameras.razor.cs @@ -135,6 +135,8 @@ private async Task OnClickPreview() private Task OnApply(int width, int height) => Camera.Resize(width, height); + private Task OnFlip() => Camera.Flip(); + private Task OnError(string err) { PlayDisabled = false; diff --git a/src/BootstrapBlazor/Components/Camera/Camera.razor.cs b/src/BootstrapBlazor/Components/Camera/Camera.razor.cs index f3873621345..3121ce2335d 100644 --- a/src/BootstrapBlazor/Components/Camera/Camera.razor.cs +++ b/src/BootstrapBlazor/Components/Camera/Camera.razor.cs @@ -153,6 +153,12 @@ protected override async Task OnAfterRenderAsync(bool firstRender) /// public Task Resize(int width, int height) => InvokeVoidAsync("resize", Id, width, height); + /// + /// 反转摄像头方法 + /// + /// + public Task Flip() => InvokeVoidAsync("flip", Id); + /// /// 初始化设备方法 /// diff --git a/src/BootstrapBlazor/Components/Camera/Camera.razor.js b/src/BootstrapBlazor/Components/Camera/Camera.razor.js index 0c6783dc14f..1c610695f8e 100644 --- a/src/BootstrapBlazor/Components/Camera/Camera.razor.js +++ b/src/BootstrapBlazor/Components/Camera/Camera.razor.js @@ -40,6 +40,9 @@ const play = (camera, option = {}) => { ...option } navigator.mediaDevices.getUserMedia(constrains).then(stream => { + const supportedConstraints = navigator.mediaDevices.getSupportedConstraints(); + console.log(supportedConstraints); + camera.video = { deviceId: option.video.deviceId }; camera.video.element = camera.el.querySelector('video') camera.video.element.srcObject = stream @@ -142,12 +145,40 @@ export async function resize(id, width, height) { return } - const constrains = { + const constraints = { facingMode: "environment", width: { ideal: width }, height: { ideal: height } } - await camera.video.track.applyConstraints(constrains); + await camera.video.track.applyConstraints(constraints); +} + + +export async function flip(id) { + const camera = Data.get(id) + if (camera === null || camera.video === void 0) { + return + } + + const { track } = camera.video; + if (track) { + const constraints = track.getConstraints(); + if (constraints.facingMode === void 0) { + constraints.facingMode = { exact: "environment" } + } + const { exact } = constraints.facingMode; + if (exact === void 0) { + constraints.facingMode = { ideal: "environment" } + } + else if (exact === "user") { + constraints.facingMode = { ideal: "environment" } + } + else { + constraints.facingMode = { ideal: "user" } + } + console.log(constraints); + await track.applyConstraints(constraints); + } } export function dispose(id) { From 5e4ee67de89651160351ea211029446395a7eb77 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 13:31:18 +0800 Subject: [PATCH 22/37] =?UTF-8?q?refactor:=20=E6=9B=B4=E6=94=B9=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Samples/MediaDevice.razor.cs | 9 ++++----- .../BootstrapBlazorServiceCollectionExtensions.cs | 2 +- .../Services/MediaDevices/DefaultMediaDevices.cs | 6 +++--- .../{DefaultMediaVideo.cs => DefaultVideoDevice.cs} | 6 +++--- .../Services/MediaDevices/IMediaDevices.cs | 3 ++- .../MediaDevices/{IMediaVideo.cs => IVideoDevice.cs} | 5 +++-- .../Services/MediaDevices/MediaTrackConstraints.cs | 4 ++-- 7 files changed, 18 insertions(+), 17 deletions(-) rename src/BootstrapBlazor/Services/MediaDevices/{DefaultMediaVideo.cs => DefaultVideoDevice.cs} (88%) rename src/BootstrapBlazor/Services/MediaDevices/{IMediaVideo.cs => IVideoDevice.cs} (92%) diff --git a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs index 2f97ca8b2ab..873789e1f5b 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs @@ -11,7 +11,7 @@ namespace BootstrapBlazor.Server.Components.Samples; public partial class MediaDevice { [Inject, NotNull] - private IMediaVideo? VideoDeviceService { get; set; } + private IVideoDevice? MediaVideoService { get; set; } private readonly List _devices = []; @@ -21,7 +21,7 @@ public partial class MediaDevice private async Task OnRequestDevice() { - var devices = await VideoDeviceService.GetDevices(); + var devices = await MediaVideoService.GetDevices(); if (devices != null) { _devices.AddRange(devices); @@ -38,14 +38,13 @@ private async Task OnOpenVideo() DeviceId = _deviceId, VideoSelector = ".bb-video" }; - await VideoDeviceService.Open(constraints); + await MediaVideoService.Open(constraints); } } private async Task OnCloseVideo() { - await Task.Delay(1); - //await VideoDeviceService.Close(".bb-video"); + await MediaVideoService.Close(".bb-video"); } private async Task OnCapture() diff --git a/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs b/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs index 695316b8847..0d482e74430 100644 --- a/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs +++ b/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs @@ -87,7 +87,7 @@ public static IServiceCollection AddBootstrapBlazor(this IServiceCollection serv services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped(); - services.TryAddScoped(); + services.TryAddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs index 441766fd8f6..3d168418126 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs @@ -29,15 +29,15 @@ public async Task Open(MediaTrackConstraints constraints) await module.InvokeVoidAsync("open", constraints); } - public async Task Close() + public async Task Close(string? videoSelector) { var module = await LoadModule(); - await module.InvokeVoidAsync("close", ""); + await module.InvokeVoidAsync("close", videoSelector); } public async Task Capture() { var module = await LoadModule(); - await module.InvokeVoidAsync("capture", ""); + await module.InvokeVoidAsync("capture"); } } diff --git a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaVideo.cs b/src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs similarity index 88% rename from src/BootstrapBlazor/Services/MediaDevices/DefaultMediaVideo.cs rename to src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs index 39036567b12..d37a7eeae67 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaVideo.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs @@ -5,7 +5,7 @@ namespace BootstrapBlazor.Components; -class DefaultMediaVideo(IMediaDevices deviceService) : IMediaVideo +class DefaultVideoDevice(IMediaDevices deviceService) : IVideoDevice { /// /// @@ -27,9 +27,9 @@ public Task Open(MediaTrackConstraints constraints) return deviceService.Open(constraints); } - public Task Close() + public Task Close(string? videoSelector) { - return deviceService.Close(); + return deviceService.Close(videoSelector); } public Task Capture() diff --git a/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs b/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs index ad94244e026..66a5933358e 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs @@ -26,8 +26,9 @@ public interface IMediaDevices /// /// The close() method of the MediaDevices interface stops capturing media from the specified device and closes the MediaStream object. /// + /// /// - Task Close(); + Task Close(string? videoSelector); /// /// The capture() method of the MediaDevices interface captures a still image from the specified video stream and saves it to the specified location. diff --git a/src/BootstrapBlazor/Services/MediaDevices/IMediaVideo.cs b/src/BootstrapBlazor/Services/MediaDevices/IVideoDevice.cs similarity index 92% rename from src/BootstrapBlazor/Services/MediaDevices/IMediaVideo.cs rename to src/BootstrapBlazor/Services/MediaDevices/IVideoDevice.cs index 1296d1d3a09..32af38726e5 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/IMediaVideo.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/IVideoDevice.cs @@ -8,7 +8,7 @@ namespace BootstrapBlazor.Components; /// /// Video Media Device Interface /// -public interface IMediaVideo +public interface IVideoDevice { /// /// Gets the list of video devices. @@ -26,8 +26,9 @@ public interface IMediaVideo /// /// Close the video device with the specified selector. /// + /// /// - Task Close(); + Task Close(string? videoSelector); /// /// Capture a still image from the video stream. diff --git a/src/BootstrapBlazor/Services/MediaDevices/MediaTrackConstraints.cs b/src/BootstrapBlazor/Services/MediaDevices/MediaTrackConstraints.cs index c9cb83204de..9e5c7b052e2 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/MediaTrackConstraints.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/MediaTrackConstraints.cs @@ -23,12 +23,12 @@ public class MediaTrackConstraints /// /// /// - public int Width { get; set; } + public int? Width { get; set; } /// /// /// - public int Height { get; set; } + public int? Height { get; set; } /// /// From 5a8f7bd5e376b33d0138d6ac331c60fa87e6adb0 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 13:32:21 +0800 Subject: [PATCH 23/37] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=20open=20close?= =?UTF-8?q?=20=E6=96=B9=E6=B3=95=E5=AE=9E=E7=8E=B0=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor/wwwroot/modules/media.js | 56 ++++++++++++++------ 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/src/BootstrapBlazor/wwwroot/modules/media.js b/src/BootstrapBlazor/wwwroot/modules/media.js index 530618d42b4..0ed9960737b 100644 --- a/src/BootstrapBlazor/wwwroot/modules/media.js +++ b/src/BootstrapBlazor/wwwroot/modules/media.js @@ -16,33 +16,57 @@ export async function enumerateDevices() { export async function open(options) { const constrains = { video: { - facingMode: { exact: options.facingMode || "environment" }, deviceId: options.deviceId ? { exact: options.deviceId } : null, + facingMode: { ideal: options.facingMode || "environment" } }, audio: false } - const video = document.querySelector(options.videoSelector); - if (video) { - const stream = await navigator.mediaDevices.getUserMedia(constrains); - video.srcObject = stream; + + const { videoSelector, width, height } = options; + if (width) { + constrains.video.width = { ideal: width }; + } + if (height) { + constrains.video.height = { ideal: height }; + } + const stream = await navigator.mediaDevices.getUserMedia(constrains); + const media = registerBootstrapBlazorModule("MediaDevices"); + media.stream = stream; + + if (videoSelector) { + const video = document.querySelector(videoSelector); + if (video) { + video.srcObject = stream; + } } } export async function close(videoSelector) { - const video = document.querySelector(videoSelector); - if (video) { - video.pause(); - const stream = video.srcObject; - if (stream) { - const tracks = stream.getTracks(); - - tracks.forEach(track => { - track.stop(); - }); - + if (videoSelector) { + const video = document.querySelector(videoSelector); + if (video) { + video.pause(); + const stream = video.srcObject; + closeStream(stream); video.srcObject = null; } } + const media = registerBootstrapBlazorModule("MediaDevices"); + const { stream } = media; + if (stream && stream.active) { + closeStream(stream); + } + media.stream = null; +} + +const closeStream = stream => { + if (stream) { + const tracks = stream.getTracks(); + + tracks.forEach(track => { + track.stop(); + }); + } } export async function capture(videoSelector) { From 38f07b92333d67765ce375f0a1c0b4ca0334f765 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 13:32:54 +0800 Subject: [PATCH 24/37] =?UTF-8?q?doc:=20=E6=9B=B4=E6=96=B0=E7=A4=BA?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor index 851667f5ac0..0b5733f04be 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor @@ -8,7 +8,7 @@

@((MarkupString)Localizer["MediaDeviceDescription"].Value)

[Inject, NotNull]
-    private IBluetooth? BluetoothService { get; set; }
+private IBluetooth? BluetoothService { get; set; }
    From 3acbb2702b27143734ccd3293ea3bd184a8f38d0 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 13:42:45 +0800 Subject: [PATCH 25/37] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=20GetPreviewUr?= =?UTF-8?q?l=20=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MediaDevices/DefaultMediaDevices.cs | 8 +++-- .../MediaDevices/DefaultVideoDevice.cs | 2 +- .../Services/MediaDevices/IMediaDevices.cs | 6 ++++ src/BootstrapBlazor/wwwroot/modules/media.js | 36 +++++++++++++------ 4 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs index 3d168418126..b5c1316c2ee 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs @@ -7,12 +7,10 @@ namespace BootstrapBlazor.Components; class DefaultMediaDevices(IJSRuntime jsRuntime) : IMediaDevices { - private DotNetObjectReference? _interop = null; private JSModule? _module = null; private async Task LoadModule() { - _interop ??= DotNetObjectReference.Create(this); _module ??= await jsRuntime.LoadModuleByName("media"); return _module; } @@ -40,4 +38,10 @@ public async Task Capture() var module = await LoadModule(); await module.InvokeVoidAsync("capture"); } + + public async Task GetPreviewUrl() + { + var module = await LoadModule(); + return await module.InvokeAsync("getPreviewUrl"); + } } diff --git a/src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs b/src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs index d37a7eeae67..29e75d99d8d 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs @@ -49,6 +49,6 @@ public Task Preview() public Task GetPreviewUrl() { - throw new NotImplementedException(); + return deviceService.GetPreviewUrl(); } } diff --git a/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs b/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs index 66a5933358e..da245ae0397 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs @@ -35,4 +35,10 @@ public interface IMediaDevices ///
/// Task Capture(); + + /// + /// Gets the preview URL of the captured image. + /// + /// + Task GetPreviewUrl(); } diff --git a/src/BootstrapBlazor/wwwroot/modules/media.js b/src/BootstrapBlazor/wwwroot/modules/media.js index 0ed9960737b..e528b3a5f41 100644 --- a/src/BootstrapBlazor/wwwroot/modules/media.js +++ b/src/BootstrapBlazor/wwwroot/modules/media.js @@ -59,16 +59,6 @@ export async function close(videoSelector) { media.stream = null; } -const closeStream = stream => { - if (stream) { - const tracks = stream.getTracks(); - - tracks.forEach(track => { - track.stop(); - }); - } -} - export async function capture(videoSelector) { const video = document.querySelector(videoSelector); if (video) { @@ -89,3 +79,29 @@ export async function capture(videoSelector) { } } } + +export async function getPreviewUrl() { + let url = null; + const media = registerBootstrapBlazorModule("MediaDevices"); + const { stream } = media; + if (stream) { + const tracks = stream.getVideoTracks(); + if (tracks) { + const track = tracks[0]; + const capture = new ImageCapture(track); + const blob = await capture.takePhoto(); + url = URL.createObjectURL(blob); + } + } + return url; +} + +const closeStream = stream => { + if (stream) { + const tracks = stream.getTracks(); + + tracks.forEach(track => { + track.stop(); + }); + } +} From 3bddd3c69f9a03ed08e4c0876b78338d2dffedbd Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 13:42:53 +0800 Subject: [PATCH 26/37] =?UTF-8?q?doc:=20=E6=9B=B4=E6=96=B0=E7=A4=BA?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Samples/MediaDevice.razor | 10 +++++++--- .../Components/Samples/MediaDevice.razor.cs | 13 +++++++------ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor index 0b5733f04be..f5dc7fc53a3 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor @@ -35,11 +35,15 @@ private IBluetooth? BluetoothService { get; set; } +
Video
+ - +
Preview
-
Image
- + @if (!string.IsNullOrEmpty(_previewUrl)) + { + + } diff --git a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs index 873789e1f5b..51e3fcf166b 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs @@ -11,7 +11,7 @@ namespace BootstrapBlazor.Server.Components.Samples; public partial class MediaDevice { [Inject, NotNull] - private IVideoDevice? MediaVideoService { get; set; } + private IVideoDevice? VideoDeviceService { get; set; } private readonly List _devices = []; @@ -19,9 +19,11 @@ public partial class MediaDevice private string? _deviceId; + private string? _previewUrl; + private async Task OnRequestDevice() { - var devices = await MediaVideoService.GetDevices(); + var devices = await VideoDeviceService.GetDevices(); if (devices != null) { _devices.AddRange(devices); @@ -38,18 +40,17 @@ private async Task OnOpenVideo() DeviceId = _deviceId, VideoSelector = ".bb-video" }; - await MediaVideoService.Open(constraints); + await VideoDeviceService.Open(constraints); } } private async Task OnCloseVideo() { - await MediaVideoService.Close(".bb-video"); + await VideoDeviceService.Close(".bb-video"); } private async Task OnCapture() { - await Task.Delay(1); - //await VideoDeviceService.Capture(".bb-video"); + _previewUrl = await VideoDeviceService.GetPreviewUrl(); } } From 451554447cce8593e259ec732f2bfb1d3e0eac52 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 13:46:43 +0800 Subject: [PATCH 27/37] =?UTF-8?q?refactor:=20=E7=B2=BE=E7=AE=80=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor/wwwroot/modules/media.js | 25 ++------------------ 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/src/BootstrapBlazor/wwwroot/modules/media.js b/src/BootstrapBlazor/wwwroot/modules/media.js index e528b3a5f41..987cf91dd6e 100644 --- a/src/BootstrapBlazor/wwwroot/modules/media.js +++ b/src/BootstrapBlazor/wwwroot/modules/media.js @@ -1,4 +1,4 @@ -import { drawImage, registerBootstrapBlazorModule } from "./utility.js" +import { registerBootstrapBlazorModule } from "./utility.js" export async function enumerateDevices() { let ret = null; @@ -59,32 +59,11 @@ export async function close(videoSelector) { media.stream = null; } -export async function capture(videoSelector) { - const video = document.querySelector(videoSelector); - if (video) { - const stream = video.srcObject; - if (stream) { - const tracks = stream.getVideoTracks(); - if (tracks) { - const track = tracks[0]; - const capture = new ImageCapture(track); - const blob = await capture.takePhoto(); - const image = await createImageBitmap(blob); - const { offsetWidth, offsetHeight } = video; - drawImage(document.querySelector(".bb-video-image"), image, offsetWidth, offsetHeight); - - const img = document.querySelector(".bb-image"); - img.src = URL.createObjectURL(blob); - } - } - } -} - export async function getPreviewUrl() { let url = null; const media = registerBootstrapBlazorModule("MediaDevices"); const { stream } = media; - if (stream) { + if (stream && stream.active) { const tracks = stream.getVideoTracks(); if (tracks) { const track = tracks[0]; From 7f0d7e99b0ee656f719a1e3742a4b6a22e940c01 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 14:20:08 +0800 Subject: [PATCH 28/37] =?UTF-8?q?refactor:=20=E5=A2=9E=E5=8A=A0=E7=BF=BB?= =?UTF-8?q?=E8=BD=AC=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MediaDevices/DefaultMediaDevices.cs | 6 +++++ .../MediaDevices/DefaultVideoDevice.cs | 5 ++++ .../Services/MediaDevices/IMediaDevices.cs | 6 +++++ .../Services/MediaDevices/IVideoDevice.cs | 6 +++++ src/BootstrapBlazor/wwwroot/modules/media.js | 25 +++++++++++++++++++ 5 files changed, 48 insertions(+) diff --git a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs index b5c1316c2ee..d7292f54682 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs @@ -44,4 +44,10 @@ public async Task Capture() var module = await LoadModule(); return await module.InvokeAsync("getPreviewUrl"); } + + public async Task Flip() + { + var module = await LoadModule(); + await module.InvokeAsync("flip"); + } } diff --git a/src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs b/src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs index 29e75d99d8d..2f665b643a3 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs @@ -51,4 +51,9 @@ public Task Preview() { return deviceService.GetPreviewUrl(); } + + public Task Flip() + { + return deviceService.Flip(); + } } diff --git a/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs b/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs index da245ae0397..ea4988a9940 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs @@ -41,4 +41,10 @@ public interface IMediaDevices ///
/// Task GetPreviewUrl(); + + /// + /// Flip the video device. + /// + /// + Task Flip(); } diff --git a/src/BootstrapBlazor/Services/MediaDevices/IVideoDevice.cs b/src/BootstrapBlazor/Services/MediaDevices/IVideoDevice.cs index 32af38726e5..eddf82e3d2a 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/IVideoDevice.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/IVideoDevice.cs @@ -53,4 +53,10 @@ public interface IVideoDevice ///
/// Task GetPreviewUrl(); + + /// + /// Flip the video device. + /// + /// + Task Flip(); } diff --git a/src/BootstrapBlazor/wwwroot/modules/media.js b/src/BootstrapBlazor/wwwroot/modules/media.js index 987cf91dd6e..442bebfe87b 100644 --- a/src/BootstrapBlazor/wwwroot/modules/media.js +++ b/src/BootstrapBlazor/wwwroot/modules/media.js @@ -75,6 +75,31 @@ export async function getPreviewUrl() { return url; } +export async function flip() { + const media = registerBootstrapBlazorModule("MediaDevices"); + const { stream } = media; + if (stream && stream.active) { + const tracks = stream.getVideoTracks(); + if (tracks) { + const track = tracks[0]; + const constraints = track.getSettings(); + const { facingMode } = constraints; + if (facingMode === void 0) { + console.log('facingMode is not supported'); + return; + } + + if (facingMode === "user" || facingMode.exact === "user" || facingMode.ideal === "user") { + constraints.facingMode = { ideal: "environment" } + } + else { + constraints.facingMode = { ideal: "user" } + } + await track.applyConstraints(constraints); + } + } +} + const closeStream = stream => { if (stream) { const tracks = stream.getTracks(); From 6f8e7f36e4a913a77cf3dae4ec6daa68d65f4ef6 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 14:20:17 +0800 Subject: [PATCH 29/37] =?UTF-8?q?doc:=20=E6=9B=B4=E6=96=B0=E7=A4=BA?= =?UTF-8?q?=E4=BE=8B=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Samples/MediaDevice.razor | 1 + .../Components/Samples/MediaDevice.razor.cs | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor index f5dc7fc53a3..e75c7b97c73 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor @@ -30,6 +30,7 @@ private IBluetooth? BluetoothService { get; set; } +
diff --git a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs index 51e3fcf166b..6d147d9672a 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs @@ -53,4 +53,9 @@ private async Task OnCapture() { _previewUrl = await VideoDeviceService.GetPreviewUrl(); } + + private async Task OnFlip() + { + await VideoDeviceService.Flip(); + } } From 8c1edfedaaec3b2c10a17adea7277d54d069c672 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 14:25:31 +0800 Subject: [PATCH 30/37] =?UTF-8?q?chore:=20=E5=A2=9E=E5=8A=A0=E6=BA=90?= =?UTF-8?q?=E7=A0=81=E6=98=A0=E5=B0=84=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor.Server/docs.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/BootstrapBlazor.Server/docs.json b/src/BootstrapBlazor.Server/docs.json index c86e547fe12..c59c0d20386 100644 --- a/src/BootstrapBlazor.Server/docs.json +++ b/src/BootstrapBlazor.Server/docs.json @@ -230,7 +230,8 @@ "univer-sheet": "UniverSheets", "shield-badge": "ShieldBadges", "opt-input": "OtpInputs", - "otp-service": "OtpServices" + "otp-service": "OtpServices", + "video-device": "VideoDevices" }, "video": { "table": "BV1ap4y1x7Qn?p=1", From 9a9237f38bae05b5786c5925ddcc22fb5efdff8a Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 14:25:39 +0800 Subject: [PATCH 31/37] =?UTF-8?q?doc:=20=E5=A2=9E=E5=8A=A0=E8=8F=9C?= =?UTF-8?q?=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Extensions/MenusLocalizerExtensions.cs | 6 ++++++ src/BootstrapBlazor.Server/Locales/en-US.json | 3 ++- src/BootstrapBlazor.Server/Locales/zh-CN.json | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs b/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs index 4f7c676f2bd..f293b1a6dd3 100644 --- a/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs +++ b/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs @@ -1549,6 +1549,12 @@ void AddServices(DemoMenuItem item) Url = "title" }, new() + { + IsNew = true, + Text = Localizer["VideoDevices"], + Url = "video-device" + }, + new() { Text = Localizer["WebSerial"], Url = "web-serial" diff --git a/src/BootstrapBlazor.Server/Locales/en-US.json b/src/BootstrapBlazor.Server/Locales/en-US.json index e8e0184e4ba..fa1878da590 100644 --- a/src/BootstrapBlazor.Server/Locales/en-US.json +++ b/src/BootstrapBlazor.Server/Locales/en-US.json @@ -4920,7 +4920,8 @@ "UniverSheet": "UniverSheet", "ShieldBadge": "ShieldBadge", "OtpInput": "OtpInput", - "TotpService": "ITotpService" + "TotpService": "ITotpService", + "VideoDevices": "IVideoDevice" }, "BootstrapBlazor.Server.Components.Samples.Table.TablesHeader": { "TablesHeaderTitle": "Header grouping function", diff --git a/src/BootstrapBlazor.Server/Locales/zh-CN.json b/src/BootstrapBlazor.Server/Locales/zh-CN.json index ede716295d7..a54afd5a344 100644 --- a/src/BootstrapBlazor.Server/Locales/zh-CN.json +++ b/src/BootstrapBlazor.Server/Locales/zh-CN.json @@ -4920,7 +4920,8 @@ "UniverSheet": "表格组件 UniverSheet", "ShieldBadge": "徽章组件 ShieldBadge", "OtpInput": "验证码输入框 OtpInput", - "TotpService": "时间密码验证服务 ITotpService" + "TotpService": "时间密码验证服务 ITotpService", + "VideoDevices": "视频设备服务 IVideoDevice" }, "BootstrapBlazor.Server.Components.Samples.Table.TablesHeader": { "TablesHeaderTitle": "表头分组功能", From f7d72510595bdc1139d3d4d759cb50241d0db9af Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 14:25:50 +0800 Subject: [PATCH 32/37] =?UTF-8?q?doc:=20=E6=9B=B4=E6=94=B9=E7=A4=BA?= =?UTF-8?q?=E4=BE=8B=E6=96=87=E6=A1=A3=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{MediaDevice.razor => VideoDevices.razor} | 26 +++++++++---------- ...aDevice.razor.cs => VideoDevices.razor.cs} | 4 +-- 2 files changed, 15 insertions(+), 15 deletions(-) rename src/BootstrapBlazor.Server/Components/Samples/{MediaDevice.razor => VideoDevices.razor} (59%) rename src/BootstrapBlazor.Server/Components/Samples/{MediaDevice.razor.cs => VideoDevices.razor.cs} (96%) diff --git a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor b/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor similarity index 59% rename from src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor rename to src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor index e75c7b97c73..f831525a0bd 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor @@ -1,21 +1,21 @@ -@page "/media-device" -@inject IStringLocalizer Localizer +@page "/video-device" +@inject IStringLocalizer Localizer -

@Localizer["MediaDeviceTitle"]

+

@Localizer["VideoDeviceTitle"]

-

@Localizer["MediaDeviceIntro"]

+

@Localizer["VideoDeviceIntro"]

-

@((MarkupString)Localizer["MediaDeviceDescription"].Value)

+

@((MarkupString)Localizer["VideoDeviceDescription"].Value)

[Inject, NotNull]
 private IBluetooth? BluetoothService { get; set; }
    -
  • @((MarkupString)Localizer["MediaDeviceTipsLi1"].Value)
  • -
  • @((MarkupString)Localizer["MediaDeviceTipsLi2"].Value)
  • +
  • @((MarkupString)Localizer["VideoDeviceTipsLi1"].Value)
  • +
  • @((MarkupString)Localizer["VideoDeviceTipsLi2"].Value)
-
@((MarkupString)Localizer["MediaDeviceTipsTitle"].Value)
+
@((MarkupString)Localizer["VideoDeviceTipsTitle"].Value)
- - - - - + + + + +
diff --git a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor.cs similarity index 96% rename from src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs rename to src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor.cs index 6d147d9672a..e264be8af29 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/MediaDevice.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor.cs @@ -6,9 +6,9 @@ namespace BootstrapBlazor.Server.Components.Samples; /// -/// +/// VideoDevice Component /// -public partial class MediaDevice +public partial class VideoDevices { [Inject, NotNull] private IVideoDevice? VideoDeviceService { get; set; } From 885651eae862fb7b02413c07c07e24aaf95ef0ae Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 14:55:48 +0800 Subject: [PATCH 33/37] =?UTF-8?q?doc:=20=E6=9B=B4=E6=96=B0=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Samples/VideoDevices.razor | 32 +++++-------------- .../Components/Samples/VideoDevices.razor.cs | 1 + .../Components/Samples/VideoDevices.razor.css | 16 ++++++++++ src/BootstrapBlazor.Server/Locales/en-US.json | 11 +++++++ src/BootstrapBlazor.Server/Locales/zh-CN.json | 11 +++++++ 5 files changed, 47 insertions(+), 24 deletions(-) create mode 100644 src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor.css diff --git a/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor b/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor index f831525a0bd..790b0ffac35 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor @@ -5,42 +5,26 @@

@Localizer["VideoDeviceIntro"]

-

@((MarkupString)Localizer["VideoDeviceDescription"].Value)

-
[Inject, NotNull]
 private IBluetooth? BluetoothService { get; set; }
- -
    -
  • @((MarkupString)Localizer["VideoDeviceTipsLi1"].Value)
  • -
  • @((MarkupString)Localizer["VideoDeviceTipsLi2"].Value)
  • -
-
@((MarkupString)Localizer["VideoDeviceTipsTitle"].Value)
-
- -
- @Localizer["UsageDesc"] -
- - - - + + + + +
+
+
- - -
Video
- - - -
Preview
+ @if (!string.IsNullOrEmpty(_previewUrl)) { diff --git a/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor.cs index e264be8af29..fcb4abdd807 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor.cs @@ -46,6 +46,7 @@ private async Task OnOpenVideo() private async Task OnCloseVideo() { + _previewUrl = ""; await VideoDeviceService.Close(".bb-video"); } diff --git a/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor.css b/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor.css new file mode 100644 index 00000000000..386d7ebb984 --- /dev/null +++ b/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor.css @@ -0,0 +1,16 @@ +.bb-video { + min-height: 240px; + height: auto; + width: auto; + transform: scaleX(-1); + margin-top: 1rem; + display: block; +} + +.bb-image { + border: 1px solid var(--bs-border-color); + border-radius: var(--bs-border-radius); + transform: scaleX(-1); + margin-top: 1rem; + display: block; +} diff --git a/src/BootstrapBlazor.Server/Locales/en-US.json b/src/BootstrapBlazor.Server/Locales/en-US.json index fa1878da590..3a496e0dbae 100644 --- a/src/BootstrapBlazor.Server/Locales/en-US.json +++ b/src/BootstrapBlazor.Server/Locales/en-US.json @@ -7117,5 +7117,16 @@ "UrlDesc": "Use your iPhone's camera or the QR code scanning function to scan the QR code below, then open the Safari browser and open the current address", "WiFiDesc": "Use your iPhone's camera or the QR code scanner to scan the QR code below to automatically join the WiFi network.", "EmailDesc": "Use your iPhone's camera or the QR code scanner to scan the QR code below, then open the Email app to send an email." + }, + "BootstrapBlazor.Server.Components.Samples.VideoDevices": { + "VideoDeviceTitle": "IVideoDevice", + "VideoDeviceIntro": "Get video equipment operation capabilities through this service", + "BaseUsageTitle": "Basic usage", + "BaseUsageIntro": "Perform different operations by calling different API methods", + "VideoDeviceRequestText": "List", + "VideoDeviceOpenText": "Open", + "VideoDeviceCloseText": "Close", + "VideoDeviceCaptureText": "Capture", + "VideoDeviceFlipText": "Flip" } } diff --git a/src/BootstrapBlazor.Server/Locales/zh-CN.json b/src/BootstrapBlazor.Server/Locales/zh-CN.json index a54afd5a344..d8dbccfe1fb 100644 --- a/src/BootstrapBlazor.Server/Locales/zh-CN.json +++ b/src/BootstrapBlazor.Server/Locales/zh-CN.json @@ -7117,5 +7117,16 @@ "UrlDesc": "使用 iPhone 手机相机或者扫描二维码功能扫描下方二维码后,打开 Safari 浏览器并且打开当前地址", "WiFiDesc": "使用 iPhone 手机相机或者扫描二维码功能扫描下方二维码后,自动加入 WiFi 网络", "EmailDesc": "使用 iPhone 手机相机或者扫描二维码功能扫描下方二维码后,打开 Email 应用发送邮件" + }, + "BootstrapBlazor.Server.Components.Samples.VideoDevices": { + "VideoDeviceTitle": "IVideoDevice 视频设备服务", + "VideoDeviceIntro": "通过此服务获得视频设备操作能力", + "BaseUsageTitle": "基本用法", + "BaseUsageIntro": "通过调用不同的 api 方法进行不同操作", + "VideoDeviceRequestText": "枚举设备", + "VideoDeviceOpenText": "打开设备", + "VideoDeviceCloseText": "关闭设备", + "VideoDeviceCaptureText": "截图", + "VideoDeviceFlipText": "翻转镜头" } } From ea653730a2144aef13b882d0a562cc017048fcfc Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 15:23:24 +0800 Subject: [PATCH 34/37] =?UTF-8?q?test:=20=E5=A2=9E=E5=8A=A0=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MediaDevices/DefaultVideoDevice.cs | 10 ---- .../MediaDevices/DisplayMediaOptions.cs | 13 ----- .../Services/MediaDevices/IVideoDevice.cs | 22 ++++---- .../Services/MediaDevices/MediaDeviceInfo.cs | 17 +++++- .../Services/MediaDevices/MediaStream.cs | 13 ----- test/UnitTest/Services/VideoDeviceTest.cs | 53 +++++++++++++++++++ 6 files changed, 80 insertions(+), 48 deletions(-) delete mode 100644 src/BootstrapBlazor/Services/MediaDevices/DisplayMediaOptions.cs delete mode 100644 src/BootstrapBlazor/Services/MediaDevices/MediaStream.cs create mode 100644 test/UnitTest/Services/VideoDeviceTest.cs diff --git a/src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs b/src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs index 2f665b643a3..afd44842007 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs @@ -37,16 +37,6 @@ public Task Capture() return deviceService.Capture(); } - public Task Preview() - { - throw new NotImplementedException(); - } - - public Task GetPreviewImage() - { - throw new NotImplementedException(); - } - public Task GetPreviewUrl() { return deviceService.GetPreviewUrl(); diff --git a/src/BootstrapBlazor/Services/MediaDevices/DisplayMediaOptions.cs b/src/BootstrapBlazor/Services/MediaDevices/DisplayMediaOptions.cs deleted file mode 100644 index 4bb94634d1b..00000000000 --- a/src/BootstrapBlazor/Services/MediaDevices/DisplayMediaOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License -// See the LICENSE file in the project root for more information. -// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone - -namespace BootstrapBlazor.Components; - -/// -/// -/// -public class DisplayMediaOptions -{ -} diff --git a/src/BootstrapBlazor/Services/MediaDevices/IVideoDevice.cs b/src/BootstrapBlazor/Services/MediaDevices/IVideoDevice.cs index eddf82e3d2a..7adffb16911 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/IVideoDevice.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/IVideoDevice.cs @@ -36,17 +36,17 @@ public interface IVideoDevice /// Task Capture(); - /// - /// Preview a still image from the video stream. - /// - /// - Task Preview(); - - /// - /// Gets the stream of the captured image. - /// - /// - Task GetPreviewImage(); + ///// + ///// Preview a still image from the video stream. + ///// + ///// + //Task Preview(); + + ///// + ///// Gets the stream of the captured image. + ///// + ///// + //Task GetPreviewImage(); /// /// Gets the preview URL of the captured image. diff --git a/src/BootstrapBlazor/Services/MediaDevices/MediaDeviceInfo.cs b/src/BootstrapBlazor/Services/MediaDevices/MediaDeviceInfo.cs index 6d2dd3bfcba..12ab88a2cfb 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/MediaDeviceInfo.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/MediaDeviceInfo.cs @@ -5,13 +5,28 @@ namespace BootstrapBlazor.Components; -class MediaDeviceInfo : IMediaDeviceInfo +/// +/// +/// +public class MediaDeviceInfo : IMediaDeviceInfo { + /// + /// + /// public string DeviceId { get; set; } = ""; + /// + /// + /// public string GroupId { get; set; } = ""; + /// + /// + /// public string Kind { get; set; } = ""; + /// + /// + /// public string Label { get; set; } = ""; } diff --git a/src/BootstrapBlazor/Services/MediaDevices/MediaStream.cs b/src/BootstrapBlazor/Services/MediaDevices/MediaStream.cs deleted file mode 100644 index 0efb2213cd8..00000000000 --- a/src/BootstrapBlazor/Services/MediaDevices/MediaStream.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License -// See the LICENSE file in the project root for more information. -// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone - -namespace BootstrapBlazor.Components; - -/// -/// The MediaStream interface of the Media Capture and Streams API represents a stream of media content. A stream consists of several tracks, such as video or audio tracks. Each track is specified as an instance of MediaStreamTrack. -/// -public class MediaStream -{ -} diff --git a/test/UnitTest/Services/VideoDeviceTest.cs b/test/UnitTest/Services/VideoDeviceTest.cs new file mode 100644 index 00000000000..3446801668d --- /dev/null +++ b/test/UnitTest/Services/VideoDeviceTest.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace UnitTest.Services; + +public class VideoDeviceTest : BootstrapBlazorTestBase +{ + [Fact] + public async Task GetDevices_Ok() + { + Context.JSInterop.Setup>("enumerateDevices").SetResult([ + new() { DeviceId = "test-device-id", GroupId = "test-groupd-id", Kind = "videoinput", Label="test-video" } + ]); + var service = Context.Services.GetRequiredService(); + var devices = await service.GetDevices(); + Assert.NotNull(devices); + Assert.Equal("test-device-id", devices[0].DeviceId); + Assert.Equal("test-groupd-id", devices[0].GroupId); + Assert.Equal("videoinput", devices[0].Kind); + Assert.Equal("test-video", devices[0].Label); + } + + [Fact] + public async Task Open_Ok() + { + Context.JSInterop.Setup("getPreviewUrl").SetResult("blob:https://test-preview"); + + var service = Context.Services.GetRequiredService(); + var options = new MediaTrackConstraints() + { + DeviceId = "test-device-id", + FacingMode = "user", + Height = 640, + Width = 480, + VideoSelector = ".bb-video" + }; + await service.Open(options); + await service.Close(".bb-video"); + + Assert.Equal("test-device-id", options.DeviceId); + Assert.Equal("user", options.FacingMode); + Assert.Equal(640, options.Height); + Assert.Equal(480, options.Width); + Assert.Equal(".bb-video", options.VideoSelector); + + await service.Capture(); + await service.Flip(); + var url = await service.GetPreviewUrl(); + Assert.Equal("blob:https://test-preview", url); + } +} From 18d561a3b1f833fd7feb3b817d80494855257915 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 15:24:58 +0800 Subject: [PATCH 35/37] chore: bump version 9.6.1-beta01 Co-Authored-By: Vincent <142959771+vincent8725@users.noreply.github.com> --- src/BootstrapBlazor/BootstrapBlazor.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index 1a7f7ef8660..0e6eb0e364b 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@  - 9.6.1-beta02 + 9.6.1-beta01 From ee34de838cdacb8e82e051615ec7f9a7612c4ec9 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 15:29:37 +0800 Subject: [PATCH 36/37] =?UTF-8?q?refactor:=20=E6=92=A4=E9=94=80=20Flip=20?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor/Components/Camera/Camera.razor.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/BootstrapBlazor/Components/Camera/Camera.razor.cs b/src/BootstrapBlazor/Components/Camera/Camera.razor.cs index 3121ce2335d..f3873621345 100644 --- a/src/BootstrapBlazor/Components/Camera/Camera.razor.cs +++ b/src/BootstrapBlazor/Components/Camera/Camera.razor.cs @@ -153,12 +153,6 @@ protected override async Task OnAfterRenderAsync(bool firstRender) /// public Task Resize(int width, int height) => InvokeVoidAsync("resize", Id, width, height); - /// - /// 反转摄像头方法 - /// - /// - public Task Flip() => InvokeVoidAsync("flip", Id); - /// /// 初始化设备方法 /// From e549994adb27457257a87ef0e744f380c2bb6228 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 2 May 2025 15:31:09 +0800 Subject: [PATCH 37/37] =?UTF-8?q?doc:=20=E6=92=A4=E9=94=80=E6=9B=B4?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor.Server/Components/Samples/Cameras.razor | 1 - src/BootstrapBlazor.Server/Components/Samples/Cameras.razor.cs | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/BootstrapBlazor.Server/Components/Samples/Cameras.razor b/src/BootstrapBlazor.Server/Components/Samples/Cameras.razor index 3568e1ac48b..f928bbaacdc 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/Cameras.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/Cameras.razor @@ -42,7 +42,6 @@ -
diff --git a/src/BootstrapBlazor.Server/Components/Samples/Cameras.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/Cameras.razor.cs index c2d3dd11f42..24df426c884 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/Cameras.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/Cameras.razor.cs @@ -135,8 +135,6 @@ private async Task OnClickPreview() private Task OnApply(int width, int height) => Camera.Resize(width, height); - private Task OnFlip() => Camera.Flip(); - private Task OnError(string err) { PlayDisabled = false;