From 3eb1dbea41aaa1c7529a529c0d7fcf1bf869fac8 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sat, 3 May 2025 18:03:25 +0800 Subject: [PATCH 01/15] =?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 --- src/BootstrapBlazor/wwwroot/modules/media.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/BootstrapBlazor/wwwroot/modules/media.js b/src/BootstrapBlazor/wwwroot/modules/media.js index b326799d398..0a4317bb6cc 100644 --- a/src/BootstrapBlazor/wwwroot/modules/media.js +++ b/src/BootstrapBlazor/wwwroot/modules/media.js @@ -7,8 +7,7 @@ export async function enumerateDevices() { } else { await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); - const devices = await navigator.mediaDevices.enumerateDevices(); - ret = devices; + ret = await navigator.mediaDevices.enumerateDevices(); } return ret; } From 989a712bf8c00f74de5452a3e75f8654d76ccbaa Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sat, 3 May 2025 18:40:56 +0800 Subject: [PATCH 02/15] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20IAudioDevice?= =?UTF-8?q?=20=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MediaDevices/DefaultAudioDevice.cs | 34 +++++++++++++++++++ .../MediaDevices/DefaultMediaDevices.cs | 4 +-- .../MediaDevices/DefaultVideoDevice.cs | 4 +-- .../Services/MediaDevices/IAudioDevice.cs | 32 +++++++++++++++++ .../Services/MediaDevices/IMediaDevices.cs | 4 +-- .../Services/MediaDevices/IVideoDevice.cs | 4 +-- .../MediaDevices/MediaTrackConstraints.cs | 12 +++---- 7 files changed, 80 insertions(+), 14 deletions(-) create mode 100644 src/BootstrapBlazor/Services/MediaDevices/DefaultAudioDevice.cs create mode 100644 src/BootstrapBlazor/Services/MediaDevices/IAudioDevice.cs diff --git a/src/BootstrapBlazor/Services/MediaDevices/DefaultAudioDevice.cs b/src/BootstrapBlazor/Services/MediaDevices/DefaultAudioDevice.cs new file mode 100644 index 00000000000..efdca3041da --- /dev/null +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultAudioDevice.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 DefaultAudioDevice(IMediaDevices deviceService) : IAudioDevice +{ + /// + /// + /// + /// + public async Task?> GetDevices() + { + var ret = new List(); + var devices = await deviceService.EnumerateDevices(); + if (devices != null) + { + ret.AddRange(devices.Where(d => d.Kind == "audioinput")); + } + 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/DefaultMediaDevices.cs b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs index bd01b7a76c6..b92695d8eaf 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs @@ -27,10 +27,10 @@ public async Task Open(MediaTrackConstraints constraints) return await module.InvokeAsync("open", constraints); } - public async Task Close(string? videoSelector) + public async Task Close(string? selector) { var module = await LoadModule(); - return await module.InvokeAsync("close", videoSelector); + return await module.InvokeAsync("close", selector); } public async Task Capture() diff --git a/src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs b/src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs index cc1a2712592..70b90614bc8 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs @@ -27,9 +27,9 @@ public Task Open(MediaTrackConstraints constraints) return deviceService.Open(constraints); } - public Task Close(string? videoSelector) + public Task Close(string? selector) { - return deviceService.Close(videoSelector); + return deviceService.Close(selector); } public Task Capture() diff --git a/src/BootstrapBlazor/Services/MediaDevices/IAudioDevice.cs b/src/BootstrapBlazor/Services/MediaDevices/IAudioDevice.cs new file mode 100644 index 00000000000..dc8f9f68731 --- /dev/null +++ b/src/BootstrapBlazor/Services/MediaDevices/IAudioDevice.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; + +/// +/// Audio Media Device Interface +/// +public interface IAudioDevice +{ + /// + /// Gets the list of audio devices. + /// + /// + Task?> GetDevices(); + + /// + /// Opens the audio device with the specified constraints. + /// + /// + /// + Task Open(MediaTrackConstraints constraints); + + /// + /// Close the audio device with the specified selector. + /// + /// + /// + Task Close(string? selector); +} diff --git a/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs b/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs index 5e6ee28618b..4ef76afdf7d 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs @@ -26,9 +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(string? videoSelector); + 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. diff --git a/src/BootstrapBlazor/Services/MediaDevices/IVideoDevice.cs b/src/BootstrapBlazor/Services/MediaDevices/IVideoDevice.cs index e4b1916a0f2..c197c87bf37 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/IVideoDevice.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/IVideoDevice.cs @@ -26,9 +26,9 @@ public interface IVideoDevice /// /// Close the video device with the specified selector. /// - /// + /// /// - Task Close(string? videoSelector); + Task Close(string? selector); /// /// 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 9e5c7b052e2..88c39f8d681 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/MediaTrackConstraints.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/MediaTrackConstraints.cs @@ -11,27 +11,27 @@ namespace BootstrapBlazor.Components; public class MediaTrackConstraints { /// - /// + /// /// public string DeviceId { get; set; } = ""; /// - /// + /// /// - public string? VideoSelector { get; set; } + public string? Selector { get; set; } /// - /// + /// /// public int? Width { get; set; } /// - /// + /// /// public int? Height { get; set; } /// - /// + /// /// public string? FacingMode { get; set; } } From c7697029d4b830367f9259251eee176ff6ee4296 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sat, 3 May 2025 18:41:06 +0800 Subject: [PATCH 03/15] =?UTF-8?q?refactor:=20=E5=A2=9E=E5=8A=A0=E6=B3=A8?= =?UTF-8?q?=E5=85=A5=E6=9C=8D=E5=8A=A1=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Extensions/BootstrapBlazorServiceCollectionExtensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs b/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs index 0d482e74430..362f8681147 100644 --- a/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs +++ b/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs @@ -88,6 +88,7 @@ public static IServiceCollection AddBootstrapBlazor(this IServiceCollection serv services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped(); + services.TryAddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); From 077331ee3c6d3fe2f3fb9c7f60c8cba327e5d07c Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sat, 3 May 2025 18:41:20 +0800 Subject: [PATCH 04/15] =?UTF-8?q?refactor:=20=E5=A2=9E=E5=8A=A0=E8=AF=AD?= =?UTF-8?q?=E9=9F=B3=E8=AE=B0=E5=BD=95=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor/wwwroot/modules/media.js | 76 ++++++++++++++++++-- 1 file changed, 71 insertions(+), 5 deletions(-) diff --git a/src/BootstrapBlazor/wwwroot/modules/media.js b/src/BootstrapBlazor/wwwroot/modules/media.js index 0a4317bb6cc..3a016070679 100644 --- a/src/BootstrapBlazor/wwwroot/modules/media.js +++ b/src/BootstrapBlazor/wwwroot/modules/media.js @@ -13,6 +13,26 @@ export async function enumerateDevices() { } export async function open(options) { + const isVideo = options.video && options.video !== false; + if (isVideo) { + await openVideoDevice(options); + } + else { + await record(options); + } +} + +export async function close(selector) { + const media = registerBootstrapBlazorModule("MediaDevices"); + if (media.stream) { + await closeVideoDevice(selector); + } + else { + await stop(selector); + } +} + +const openVideoDevice = async options => { const constrains = { video: { deviceId: options.deviceId ? { exact: options.deviceId } : null, @@ -44,17 +64,17 @@ export async function open(options) { ret = true; } catch (err) { - console.error("Error accessing media devices.", err); + console.error("Error accessing video devices.", err); } return ret; } -export async function close(videoSelector) { +const closeVideoDevice = async selector => { let ret = false; try { - if (videoSelector) { - const video = document.querySelector(videoSelector); + if (selector) { + const video = document.querySelector(selector); if (video) { video.pause(); const stream = video.srcObject; @@ -71,7 +91,7 @@ export async function close(videoSelector) { ret = true; } catch (err) { - console.error("Error closing media devices.", err); + console.error("Error closing video devices.", err); } return ret; } @@ -136,3 +156,49 @@ const closeStream = stream => { }); } } + +export async function record(options) { + const constrains = { + video: false, + audio: { + deviceId: options.deviceId ? { exact: options.deviceId } : null + } + } + + let ret = false; + try { + const stream = await navigator.mediaDevices.getUserMedia(constrains); + const media = registerBootstrapBlazorModule("MediaDevices"); + const mediaRecorder = new MediaRecorder(stream); + media.recorder = mediaRecorder; + media.chunks = []; + + mediaRecorder.start(); + mediaRecorder.ondataavailable = function (e) { + media.chunks.push(e.data); + }; + ret = true; + } + catch (err) { + console.error("Error accessing audio devices.", err); + } + return ret; +} + +export async function stop(selector) { + const media = registerBootstrapBlazorModule("MediaDevices"); + if (media.recorder) { + media.recorder.stop(); + } + + if (selector) { + const audio = document.querySelector(selector); + if (audio) { + if (media.chunks && media.chunks.length > 0) { + const blob = new Blob(media.chunks, {type: media.recorder.mimeType}); + media.chunks = []; + audio.src = window.URL.createObjectURL(blob); + } + } + } +} From a170972d45bcb889bb1aa3c3d23b6f5dc39d928e Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sat, 3 May 2025 18:41:28 +0800 Subject: [PATCH 05/15] =?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 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs b/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs index f293b1a6dd3..b2b1c4578b5 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["AudioDevices"], + Url = "audio-device" + }, + new() { IsNew = true, Text = Localizer["VideoDevices"], From 291bb8b2c90d2739aff370d25dee84ba80dad45c Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sat, 3 May 2025 18:41:40 +0800 Subject: [PATCH 06/15] =?UTF-8?q?doc:=20=E5=A2=9E=E5=8A=A0=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/AudioDevices.razor | 29 ++++++++ .../Components/Samples/AudioDevices.razor.cs | 74 +++++++++++++++++++ .../Components/Samples/AudioDevices.razor.css | 13 ++++ .../Components/Samples/VideoDevices.razor.cs | 2 +- 4 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 src/BootstrapBlazor.Server/Components/Samples/AudioDevices.razor create mode 100644 src/BootstrapBlazor.Server/Components/Samples/AudioDevices.razor.cs create mode 100644 src/BootstrapBlazor.Server/Components/Samples/AudioDevices.razor.css diff --git a/src/BootstrapBlazor.Server/Components/Samples/AudioDevices.razor b/src/BootstrapBlazor.Server/Components/Samples/AudioDevices.razor new file mode 100644 index 00000000000..a59a2d12136 --- /dev/null +++ b/src/BootstrapBlazor.Server/Components/Samples/AudioDevices.razor @@ -0,0 +1,29 @@ +@page "/audio-device" +@inject IStringLocalizer Localizer + +

@Localizer["AudioDeviceTitle"]

+ +

@Localizer["AudioDeviceIntro"]

+ +
[Inject, NotNull]
+private IAudioDevice? AudioDeviceService { get; set; }
+ + +
+
+
+ + + +
+
+
+ +
+
+ + +
+ diff --git a/src/BootstrapBlazor.Server/Components/Samples/AudioDevices.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/AudioDevices.razor.cs new file mode 100644 index 00000000000..11d84ce3788 --- /dev/null +++ b/src/BootstrapBlazor.Server/Components/Samples/AudioDevices.razor.cs @@ -0,0 +1,74 @@ +// 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; + +/// +/// AudioDevice Component +/// +public partial class AudioDevices : IAsyncDisposable +{ + [Inject, NotNull] + private IAudioDevice? AudioDeviceService { get; set; } + + private readonly List _devices = []; + + private List _items = []; + + private string? _deviceId; + + private bool _isOpen = false; + + private async Task OnRequestDevice() + { + var devices = await AudioDeviceService.GetDevices(); + if (devices != null) + { + _devices.Clear(); + _devices.AddRange(devices); + _items = [.. _devices.Select(i => new SelectedItem(i.DeviceId, i.Label))]; + + _deviceId = _items.FirstOrDefault()?.Value; + } + } + + private async Task OnOpen() + { + if (!string.IsNullOrEmpty(_deviceId)) + { + var constraints = new MediaTrackConstraints + { + DeviceId = _deviceId, + Selector = ".bb-audio" + }; + _isOpen = await AudioDeviceService.Open(constraints); + } + } + + private async Task OnClose() + { + _isOpen = false; + await AudioDeviceService.Close(".bb-audio"); + } + + private async Task DisposeAsync(bool disposing) + { + if (disposing) + { + await OnClose(); + } + } + + /// + /// + /// + /// + /// + public async ValueTask DisposeAsync() + { + await DisposeAsync(true); + GC.SuppressFinalize(this); + } +} diff --git a/src/BootstrapBlazor.Server/Components/Samples/AudioDevices.razor.css b/src/BootstrapBlazor.Server/Components/Samples/AudioDevices.razor.css new file mode 100644 index 00000000000..6cd4352b054 --- /dev/null +++ b/src/BootstrapBlazor.Server/Components/Samples/AudioDevices.razor.css @@ -0,0 +1,13 @@ +.bb-actions { + display: flex; + flex-wrap: wrap; + gap: .5rem .5rem; +} + +.bb-audio { + min-height: 240px; + height: auto; + width: auto; + margin: 1rem; + display: block; +} diff --git a/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor.cs index 11eddbd42f0..b19950ff0d3 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor.cs @@ -43,7 +43,7 @@ private async Task OnOpenVideo() var constraints = new MediaTrackConstraints { DeviceId = _deviceId, - VideoSelector = ".bb-video" + Selector = ".bb-video" }; _isOpen = await VideoDeviceService.Open(constraints); } From 7badff88d859166b12423eb5bb5ef66e0c8efa8d Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sat, 3 May 2025 19:04:17 +0800 Subject: [PATCH 07/15] =?UTF-8?q?refactor:=20=E6=9B=B4=E6=96=B0=E5=BD=95?= =?UTF-8?q?=E9=9F=B3=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 | 58 +++++++++++++------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/src/BootstrapBlazor/wwwroot/modules/media.js b/src/BootstrapBlazor/wwwroot/modules/media.js index 3a016070679..c3df17cb04e 100644 --- a/src/BootstrapBlazor/wwwroot/modules/media.js +++ b/src/BootstrapBlazor/wwwroot/modules/media.js @@ -14,22 +14,26 @@ export async function enumerateDevices() { export async function open(options) { const isVideo = options.video && options.video !== false; + let ret = false; if (isVideo) { - await openVideoDevice(options); + ret = await openVideoDevice(options); } else { - await record(options); + ret = await record(options); } + return ret; } export async function close(selector) { const media = registerBootstrapBlazorModule("MediaDevices"); + let ret = false; if (media.stream) { - await closeVideoDevice(selector); + ret = await closeVideoDevice(selector); } else { - await stop(selector); + ret = await stop(selector); } + return ret; } const openVideoDevice = async options => { @@ -108,12 +112,12 @@ export async function apply(options) { const settings = track.getSettings(); const { aspectRatio } = settings; if (options.width) { - settings.width = { - exact: options.width, - }; - settings.height = { - exact: Math.floor(options.width / aspectRatio) - }; + settings.width = { + exact: options.width, + }; + settings.height = { + exact: Math.floor(options.width / aspectRatio) + }; } if (options.facingMode) { settings.facingMode = { @@ -177,6 +181,23 @@ export async function record(options) { mediaRecorder.ondataavailable = function (e) { media.chunks.push(e.data); }; + mediaRecorder.onstop = function () { + if (media.audioSelector) { + const audio = document.querySelector(media.audioSelector); + if (audio) { + if (media.chunks && media.chunks.length > 0) { + const blob = new Blob(media.chunks, { type: media.recorder.mimeType }); + media.chunks = []; + audio.src = window.URL.createObjectURL(blob); + audio.classList.remove("d-none"); + audio.classList.remove("hidden"); + audio.removeAttribute("hidden"); + } + } + delete media.audioSelector; + } + + }; ret = true; } catch (err) { @@ -186,19 +207,14 @@ export async function record(options) { } export async function stop(selector) { + let ret = false; const media = registerBootstrapBlazorModule("MediaDevices"); + if (selector) { + media.audioSelector = selector; + } if (media.recorder) { media.recorder.stop(); + ret = true; } - - if (selector) { - const audio = document.querySelector(selector); - if (audio) { - if (media.chunks && media.chunks.length > 0) { - const blob = new Blob(media.chunks, {type: media.recorder.mimeType}); - media.chunks = []; - audio.src = window.URL.createObjectURL(blob); - } - } - } + return ret; } From 66bfd89aa3eda7392f9d6ea846b29fe252943668 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sat, 3 May 2025 19:04:25 +0800 Subject: [PATCH 08/15] =?UTF-8?q?refactor:=20=E6=9B=B4=E6=96=B0=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Samples/AudioDevices.razor.css | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/BootstrapBlazor.Server/Components/Samples/AudioDevices.razor.css b/src/BootstrapBlazor.Server/Components/Samples/AudioDevices.razor.css index 6cd4352b054..71dcb65c089 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/AudioDevices.razor.css +++ b/src/BootstrapBlazor.Server/Components/Samples/AudioDevices.razor.css @@ -5,9 +5,6 @@ } .bb-audio { - min-height: 240px; - height: auto; - width: auto; - margin: 1rem; - display: block; + margin-top: 1rem; + width: 100%; } From da077737a662bf5aabe59f206c8b576df7433156 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sat, 3 May 2025 19:05:06 +0800 Subject: [PATCH 09/15] =?UTF-8?q?doc:=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 c59c0d20386..5d6a7e2ad57 100644 --- a/src/BootstrapBlazor.Server/docs.json +++ b/src/BootstrapBlazor.Server/docs.json @@ -231,7 +231,8 @@ "shield-badge": "ShieldBadges", "opt-input": "OtpInputs", "otp-service": "OtpServices", - "video-device": "VideoDevices" + "video-device": "VideoDevices", + "audio-device": "AudioDevices" }, "video": { "table": "BV1ap4y1x7Qn?p=1", From d60950081b31c847a88e23bb81dcdf9ad84f74c8 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sat, 3 May 2025 19:08:31 +0800 Subject: [PATCH 10/15] =?UTF-8?q?refactor:=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/AudioDevices.razor | 2 +- src/BootstrapBlazor.Server/Locales/en-US.json | 9 +++++++++ src/BootstrapBlazor.Server/Locales/zh-CN.json | 9 +++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/BootstrapBlazor.Server/Components/Samples/AudioDevices.razor b/src/BootstrapBlazor.Server/Components/Samples/AudioDevices.razor index a59a2d12136..7462a9f529c 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/AudioDevices.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/AudioDevices.razor @@ -14,7 +14,7 @@ private IAudioDevice? AudioDeviceService { get; set; }
- +
diff --git a/src/BootstrapBlazor.Server/Locales/en-US.json b/src/BootstrapBlazor.Server/Locales/en-US.json index 3a496e0dbae..46aa6d871b5 100644 --- a/src/BootstrapBlazor.Server/Locales/en-US.json +++ b/src/BootstrapBlazor.Server/Locales/en-US.json @@ -7128,5 +7128,14 @@ "VideoDeviceCloseText": "Close", "VideoDeviceCaptureText": "Capture", "VideoDeviceFlipText": "Flip" + }, + "BootstrapBlazor.Server.Components.Samples.AudioDevices": { + "VideoDeviceTitle": "IAudioDevice", + "VideoDeviceIntro": "Get audio equipment operation capabilities through this service", + "BaseUsageTitle": "Basic usage", + "BaseUsageIntro": "Perform different operations by calling different API methods", + "AudioDeviceRequestText": "List", + "AudioDeviceOpenText": "Record", + "AudioDeviceCloseText": "Stop" } } diff --git a/src/BootstrapBlazor.Server/Locales/zh-CN.json b/src/BootstrapBlazor.Server/Locales/zh-CN.json index d8dbccfe1fb..93f642b629e 100644 --- a/src/BootstrapBlazor.Server/Locales/zh-CN.json +++ b/src/BootstrapBlazor.Server/Locales/zh-CN.json @@ -7128,5 +7128,14 @@ "VideoDeviceCloseText": "关闭设备", "VideoDeviceCaptureText": "截图", "VideoDeviceFlipText": "翻转镜头" + }, + "BootstrapBlazor.Server.Components.Samples.AudioDevices": { + "VideoDeviceTitle": "IAudioDevice 音频设备服务", + "VideoDeviceIntro": "通过此服务获得音频设备操作能力", + "BaseUsageTitle": "基本用法", + "BaseUsageIntro": "通过调用不同的 api 方法进行不同操作", + "AudioDeviceRequestText": "枚举设备", + "AudioDeviceOpenText": "打开设备", + "AudioDeviceCloseText": "关闭设备" } } From b8a4cded9a1921ae882c8ec3d7301117da3d40de Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sat, 3 May 2025 19:10:04 +0800 Subject: [PATCH 11/15] =?UTF-8?q?doc:=20=E8=8F=9C=E5=8D=95=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Extensions/MenusLocalizerExtensions.cs | 4 ++-- src/BootstrapBlazor.Server/Locales/en-US.json | 3 ++- src/BootstrapBlazor.Server/Locales/zh-CN.json | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs b/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs index b2b1c4578b5..d4c98de9bec 100644 --- a/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs +++ b/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs @@ -1551,13 +1551,13 @@ void AddServices(DemoMenuItem item) new() { IsNew = true, - Text = Localizer["AudioDevices"], + Text = Localizer["AudioDevice"], Url = "audio-device" }, new() { IsNew = true, - Text = Localizer["VideoDevices"], + Text = Localizer["VideoDevice"], Url = "video-device" }, new() diff --git a/src/BootstrapBlazor.Server/Locales/en-US.json b/src/BootstrapBlazor.Server/Locales/en-US.json index 46aa6d871b5..98dc6fd1308 100644 --- a/src/BootstrapBlazor.Server/Locales/en-US.json +++ b/src/BootstrapBlazor.Server/Locales/en-US.json @@ -4921,7 +4921,8 @@ "ShieldBadge": "ShieldBadge", "OtpInput": "OtpInput", "TotpService": "ITotpService", - "VideoDevices": "IVideoDevice" + "VideoDevice": "IVideoDevice", + "AudioDevice": "IAudioDevice" }, "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 93f642b629e..e41d07ba021 100644 --- a/src/BootstrapBlazor.Server/Locales/zh-CN.json +++ b/src/BootstrapBlazor.Server/Locales/zh-CN.json @@ -4921,7 +4921,8 @@ "ShieldBadge": "徽章组件 ShieldBadge", "OtpInput": "验证码输入框 OtpInput", "TotpService": "时间密码验证服务 ITotpService", - "VideoDevices": "视频设备服务 IVideoDevice" + "VideoDevice": "视频设备服务 IVideoDevice", + "AudioDevice": "音频设备服务 IAudioDevice" }, "BootstrapBlazor.Server.Components.Samples.Table.TablesHeader": { "TablesHeaderTitle": "表头分组功能", From 03b92fe0610e089cb0cfdd514887ceefedc165fd Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sat, 3 May 2025 19:18:51 +0800 Subject: [PATCH 12/15] =?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 | 2 +- .../Components/Samples/VideoDevices.razor.cs | 2 +- src/BootstrapBlazor.Server/Locales/en-US.json | 4 ++-- src/BootstrapBlazor.Server/Locales/zh-CN.json | 10 ++++++---- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor b/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor index 5b611f33b92..8cf435af1fc 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor @@ -6,7 +6,7 @@

@Localizer["VideoDeviceIntro"]

[Inject, NotNull]
-private IBluetooth? BluetoothService { get; set; }
+private IVideoDevice? VideoDeviceService { get; set; } VideoDeviceService.Apply(new MediaTrackConstraints() { Width = width, Height = height }); + private async Task OnApply(int width, int height) => await VideoDeviceService.Apply(new MediaTrackConstraints() { Width = width, Height = height }); private async Task DisposeAsync(bool disposing) { diff --git a/src/BootstrapBlazor.Server/Locales/en-US.json b/src/BootstrapBlazor.Server/Locales/en-US.json index 98dc6fd1308..22785ecd4e6 100644 --- a/src/BootstrapBlazor.Server/Locales/en-US.json +++ b/src/BootstrapBlazor.Server/Locales/en-US.json @@ -7131,8 +7131,8 @@ "VideoDeviceFlipText": "Flip" }, "BootstrapBlazor.Server.Components.Samples.AudioDevices": { - "VideoDeviceTitle": "IAudioDevice", - "VideoDeviceIntro": "Get audio equipment operation capabilities through this service", + "AudioDeviceTitle": "IAudioDevice", + "AudioDeviceIntro": "Get audio equipment operation capabilities through this service", "BaseUsageTitle": "Basic usage", "BaseUsageIntro": "Perform different operations by calling different API methods", "AudioDeviceRequestText": "List", diff --git a/src/BootstrapBlazor.Server/Locales/zh-CN.json b/src/BootstrapBlazor.Server/Locales/zh-CN.json index e41d07ba021..5e3cc128f15 100644 --- a/src/BootstrapBlazor.Server/Locales/zh-CN.json +++ b/src/BootstrapBlazor.Server/Locales/zh-CN.json @@ -7131,12 +7131,14 @@ "VideoDeviceFlipText": "翻转镜头" }, "BootstrapBlazor.Server.Components.Samples.AudioDevices": { - "VideoDeviceTitle": "IAudioDevice 音频设备服务", - "VideoDeviceIntro": "通过此服务获得音频设备操作能力", + "AudioDeviceTitle": "IAudioDevice 音频设备服务", + "AudioDeviceIntro": "通过此服务获得音频设备操作能力", "BaseUsageTitle": "基本用法", "BaseUsageIntro": "通过调用不同的 api 方法进行不同操作", "AudioDeviceRequestText": "枚举设备", - "AudioDeviceOpenText": "打开设备", - "AudioDeviceCloseText": "关闭设备" + "AudioDeviceOpenText": "录音", + "AudioDeviceCloseText": "停止", + "AudioDevicePauseText": "暂停", + "AudioDeviceResumeText": "恢复" } } From 94359d57d5723b43c3c7774b1e3c7cbaf823c237 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sat, 3 May 2025 19:23:43 +0800 Subject: [PATCH 13/15] =?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 --- test/UnitTest/Services/AudioDeviceTest.cs | 44 +++++++++++++++++++++++ test/UnitTest/Services/VideoDeviceTest.cs | 11 +++--- 2 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 test/UnitTest/Services/AudioDeviceTest.cs diff --git a/test/UnitTest/Services/AudioDeviceTest.cs b/test/UnitTest/Services/AudioDeviceTest.cs new file mode 100644 index 00000000000..e1a5136c397 --- /dev/null +++ b/test/UnitTest/Services/AudioDeviceTest.cs @@ -0,0 +1,44 @@ +// 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 AudioDeviceTest : BootstrapBlazorTestBase +{ + [Fact] + public async Task GetDevices_Ok() + { + Context.JSInterop.Setup>("enumerateDevices").SetResult([ + new() { DeviceId = "test-device-video-id", GroupId = "test-groupd-id", Kind = "videoinput", Label="test-video" }, + new() { DeviceId = "test-device-audio-id", GroupId = "test-groupd-id", Kind = "audioinput", Label="test-audio" } + ]); + var service = Context.Services.GetRequiredService(); + var devices = await service.GetDevices(); + Assert.NotNull(devices); + Assert.Equal("test-device-audio-id", devices[0].DeviceId); + Assert.Equal("test-groupd-id", devices[0].GroupId); + Assert.Equal("audioinput", devices[0].Kind); + Assert.Equal("test-audio", devices[0].Label); + } + + [Fact] + public async Task Open_Ok() + { + Context.JSInterop.Setup("open", _ => true).SetResult(true); + Context.JSInterop.Setup("close", _ => true).SetResult(true); + + var service = Context.Services.GetRequiredService(); + var options = new MediaTrackConstraints() + { + DeviceId = "test-device-id", + Selector = ".bb-audio" + }; + var open = await service.Open(options); + Assert.True(open); + + var close = await service.Close(".bb-audio"); + Assert.True(close); + } +} diff --git a/test/UnitTest/Services/VideoDeviceTest.cs b/test/UnitTest/Services/VideoDeviceTest.cs index 267c9c4e1b6..500100ea64f 100644 --- a/test/UnitTest/Services/VideoDeviceTest.cs +++ b/test/UnitTest/Services/VideoDeviceTest.cs @@ -11,12 +11,13 @@ public class VideoDeviceTest : BootstrapBlazorTestBase public async Task GetDevices_Ok() { Context.JSInterop.Setup>("enumerateDevices").SetResult([ - new() { DeviceId = "test-device-id", GroupId = "test-groupd-id", Kind = "videoinput", Label="test-video" } + new() { DeviceId = "test-device-video-id", GroupId = "test-groupd-id", Kind = "videoinput", Label="test-video" }, + new() { DeviceId = "test-device-audio-id", GroupId = "test-groupd-id", Kind = "audioinput", Label="test-audio" } ]); var service = Context.Services.GetRequiredService(); var devices = await service.GetDevices(); Assert.NotNull(devices); - Assert.Equal("test-device-id", devices[0].DeviceId); + Assert.Equal("test-device-video-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); @@ -37,7 +38,7 @@ public async Task Open_Ok() FacingMode = "user", Height = 640, Width = 480, - VideoSelector = ".bb-video" + Selector = ".bb-video" }; var open = await service.Open(options); Assert.True(open); @@ -45,14 +46,14 @@ public async Task Open_Ok() var close = await service.Close(".bb-video"); Assert.True(close); - var apply = await service.Apply(new MediaTrackConstraints() { Width = 640, Height = 480, VideoSelector = ".bb-video" }); + var apply = await service.Apply(new MediaTrackConstraints() { Width = 640, Height = 480, Selector = ".bb-video" }); Assert.True(apply); 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); + Assert.Equal(".bb-video", options.Selector); await service.Capture(); var url = await service.GetPreviewUrl(); From edc71cbc51c0ce6589b2bc7db75b4fa38e5bd8cf Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sat, 3 May 2025 19:30:03 +0800 Subject: [PATCH 14/15] =?UTF-8?q?refactor:=20=E5=A2=9E=E5=8A=A0=20type=20?= =?UTF-8?q?=E5=8C=BA=E5=88=86=E9=9F=B3=E8=A7=86=E9=A2=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Services/MediaDevices/DefaultAudioDevice.cs | 2 +- .../Services/MediaDevices/DefaultMediaDevices.cs | 4 ++-- .../Services/MediaDevices/DefaultVideoDevice.cs | 2 +- .../Services/MediaDevices/IMediaDevices.cs | 3 ++- src/BootstrapBlazor/wwwroot/modules/media.js | 11 ++++------- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/BootstrapBlazor/Services/MediaDevices/DefaultAudioDevice.cs b/src/BootstrapBlazor/Services/MediaDevices/DefaultAudioDevice.cs index efdca3041da..05708cea6cc 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/DefaultAudioDevice.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultAudioDevice.cs @@ -24,7 +24,7 @@ class DefaultAudioDevice(IMediaDevices deviceService) : IAudioDevice public Task Open(MediaTrackConstraints constraints) { - return deviceService.Open(constraints); + return deviceService.Open("audio", constraints); } public Task Close(string? selector) diff --git a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs index b92695d8eaf..37e124abed0 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs @@ -21,10 +21,10 @@ private async Task LoadModule() return await module.InvokeAsync?>("enumerateDevices"); } - public async Task Open(MediaTrackConstraints constraints) + public async Task Open(string type, MediaTrackConstraints constraints) { var module = await LoadModule(); - return await module.InvokeAsync("open", constraints); + return await module.InvokeAsync("open", type, constraints); } public async Task Close(string? selector) diff --git a/src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs b/src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs index 70b90614bc8..b4af4b6de4a 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs @@ -24,7 +24,7 @@ class DefaultVideoDevice(IMediaDevices deviceService) : IVideoDevice public Task Open(MediaTrackConstraints constraints) { - return deviceService.Open(constraints); + return deviceService.Open("video", constraints); } public Task Close(string? selector) diff --git a/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs b/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs index 4ef76afdf7d..8cd7345d6c4 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs @@ -19,9 +19,10 @@ public interface IMediaDevices /// /// The open() method of the MediaDevices interface creates a new MediaStream object and starts capturing media from the specified device. /// + /// video or audio /// /// - Task Open(MediaTrackConstraints constraints); + Task Open(string type, MediaTrackConstraints constraints); /// /// The close() method of the MediaDevices interface stops capturing media from the specified device and closes the MediaStream object. diff --git a/src/BootstrapBlazor/wwwroot/modules/media.js b/src/BootstrapBlazor/wwwroot/modules/media.js index c3df17cb04e..7acabc1753d 100644 --- a/src/BootstrapBlazor/wwwroot/modules/media.js +++ b/src/BootstrapBlazor/wwwroot/modules/media.js @@ -12,13 +12,12 @@ export async function enumerateDevices() { return ret; } -export async function open(options) { - const isVideo = options.video && options.video !== false; +export async function open(type, options) { let ret = false; - if (isVideo) { + if (type === "video") { ret = await openVideoDevice(options); } - else { + else if (type === "audio") { ret = await record(options); } return ret; @@ -41,8 +40,7 @@ const openVideoDevice = async options => { video: { deviceId: options.deviceId ? { exact: options.deviceId } : null, facingMode: { ideal: options.facingMode || "environment" } - }, - audio: false + } } const { videoSelector, width, height } = options; @@ -163,7 +161,6 @@ const closeStream = stream => { export async function record(options) { const constrains = { - video: false, audio: { deviceId: options.deviceId ? { exact: options.deviceId } : null } From 2e1ac21246b71a2754b1a775341252acd9bee336 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sat, 3 May 2025 19:47:17 +0800 Subject: [PATCH 15/15] =?UTF-8?q?refactor:=20=E5=A2=9E=E5=8A=A0=E8=B5=84?= =?UTF-8?q?=E6=BA=90=E9=94=80=E6=AF=81=E9=98=B2=E6=AD=A2=E5=86=85=E5=AD=98?= =?UTF-8?q?=E6=B3=84=E6=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor/wwwroot/modules/media.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/BootstrapBlazor/wwwroot/modules/media.js b/src/BootstrapBlazor/wwwroot/modules/media.js index 7acabc1753d..53944134c3d 100644 --- a/src/BootstrapBlazor/wwwroot/modules/media.js +++ b/src/BootstrapBlazor/wwwroot/modules/media.js @@ -43,7 +43,7 @@ const openVideoDevice = async options => { } } - const { videoSelector, width, height } = options; + const { selector, width, height } = options; if (width) { constrains.video.width = { ideal: width }; } @@ -57,8 +57,8 @@ const openVideoDevice = async options => { const media = registerBootstrapBlazorModule("MediaDevices"); media.stream = stream; - if (videoSelector) { - const video = document.querySelector(videoSelector); + if (selector) { + const video = document.querySelector(selector); if (video) { video.srcObject = stream; } @@ -122,7 +122,6 @@ export async function apply(options) { ideal: options.facingMode, } } - console.log(settings); await track.applyConstraints(settings); } } @@ -171,7 +170,10 @@ export async function record(options) { const stream = await navigator.mediaDevices.getUserMedia(constrains); const media = registerBootstrapBlazorModule("MediaDevices"); const mediaRecorder = new MediaRecorder(stream); + + stop(); media.recorder = mediaRecorder; + media.audioSelector = options.selector; media.chunks = []; mediaRecorder.start(); @@ -192,8 +194,8 @@ export async function record(options) { } } delete media.audioSelector; + delete media.recorder; } - }; ret = true; } @@ -210,7 +212,12 @@ export async function stop(selector) { media.audioSelector = selector; } if (media.recorder) { - media.recorder.stop(); + if (media.recorder.state === "recording") { + media.recorder.stop(); + } + else { + delete media.recorder; + } ret = true; } return ret;