diff --git a/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor b/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor index 790b0ffac35..359a5040826 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor @@ -14,10 +14,9 @@ private IBluetooth? BluetoothService { get; set; }
- - - - + + +
diff --git a/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor.cs index fcb4abdd807..106cf49e745 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor.cs @@ -8,7 +8,7 @@ namespace BootstrapBlazor.Server.Components.Samples; /// /// VideoDevice Component /// -public partial class VideoDevices +public partial class VideoDevices : IAsyncDisposable { [Inject, NotNull] private IVideoDevice? VideoDeviceService { get; set; } @@ -21,13 +21,18 @@ public partial class VideoDevices private string? _previewUrl; + private bool _isOpen = false; + private async Task OnRequestDevice() { var devices = await VideoDeviceService.GetDevices(); if (devices != null) { + _devices.Clear(); _devices.AddRange(devices); _items = [.. _devices.Select(i => new SelectedItem(i.DeviceId, i.Label))]; + + _deviceId = _items.FirstOrDefault()?.Value; } } @@ -40,12 +45,13 @@ private async Task OnOpenVideo() DeviceId = _deviceId, VideoSelector = ".bb-video" }; - await VideoDeviceService.Open(constraints); + _isOpen = await VideoDeviceService.Open(constraints); } } private async Task OnCloseVideo() { + _isOpen = false; _previewUrl = ""; await VideoDeviceService.Close(".bb-video"); } @@ -55,8 +61,22 @@ private async Task OnCapture() _previewUrl = await VideoDeviceService.GetPreviewUrl(); } - private async Task OnFlip() + private async Task DisposeAsync(bool disposing) + { + if (disposing) + { + await OnCloseVideo(); + } + } + + /// + /// + /// + /// + /// + public async ValueTask DisposeAsync() { - await VideoDeviceService.Flip(); + await DisposeAsync(true); + GC.SuppressFinalize(this); } } diff --git a/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor.css b/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor.css index 386d7ebb984..75a4e73f32a 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor.css +++ b/src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor.css @@ -2,7 +2,6 @@ min-height: 240px; height: auto; width: auto; - transform: scaleX(-1); margin-top: 1rem; display: block; } @@ -10,7 +9,6 @@ .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/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index 0e6eb0e364b..1a7f7ef8660 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@  - 9.6.1-beta01 + 9.6.1-beta02 diff --git a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs index d7292f54682..8b9f2c68c77 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs @@ -21,16 +21,16 @@ private async Task LoadModule() return await module.InvokeAsync?>("enumerateDevices"); } - public async Task Open(MediaTrackConstraints constraints) + public async Task Open(MediaTrackConstraints constraints) { var module = await LoadModule(); - await module.InvokeVoidAsync("open", constraints); + return await module.InvokeAsync("open", constraints); } - public async Task Close(string? videoSelector) + public async Task Close(string? videoSelector) { var module = await LoadModule(); - await module.InvokeVoidAsync("close", videoSelector); + return await module.InvokeAsync("close", videoSelector); } public async Task Capture() @@ -44,10 +44,4 @@ 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 afd44842007..ebe2a6123ed 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs @@ -22,12 +22,12 @@ class DefaultVideoDevice(IMediaDevices deviceService) : IVideoDevice return ret; } - public Task Open(MediaTrackConstraints constraints) + public Task Open(MediaTrackConstraints constraints) { return deviceService.Open(constraints); } - public Task Close(string? videoSelector) + public Task Close(string? videoSelector) { return deviceService.Close(videoSelector); } @@ -41,9 +41,4 @@ public Task Capture() { 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 ea4988a9940..24140322fbe 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/IMediaDevices.cs @@ -21,14 +21,14 @@ public interface IMediaDevices /// /// /// - Task Open(MediaTrackConstraints constraints); + 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? videoSelector); + 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. @@ -41,10 +41,4 @@ 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 7adffb16911..d838d97ef95 100644 --- a/src/BootstrapBlazor/Services/MediaDevices/IVideoDevice.cs +++ b/src/BootstrapBlazor/Services/MediaDevices/IVideoDevice.cs @@ -21,14 +21,14 @@ public interface IVideoDevice /// /// /// - Task Open(MediaTrackConstraints constraints); + Task Open(MediaTrackConstraints constraints); /// /// Close the video device with the specified selector. /// /// /// - Task Close(string? videoSelector); + Task Close(string? videoSelector); /// /// Capture a still image from the video stream. @@ -53,10 +53,4 @@ 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 442bebfe87b..ef82408e9d8 100644 --- a/src/BootstrapBlazor/wwwroot/modules/media.js +++ b/src/BootstrapBlazor/wwwroot/modules/media.js @@ -29,34 +29,52 @@ export async function open(options) { 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; + let ret = false; + try { + 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; + } } + ret = true; + } + catch (err) { + console.error("Error accessing media devices.", err); } + return ret; } export async function close(videoSelector) { - if (videoSelector) { - const video = document.querySelector(videoSelector); - if (video) { - video.pause(); - const stream = video.srcObject; + let ret = false; + + try { + 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); - video.srcObject = null; } + media.stream = null; + ret = true; } - const media = registerBootstrapBlazorModule("MediaDevices"); - const { stream } = media; - if (stream && stream.active) { - closeStream(stream); + catch (err) { + console.error("Error closing media devices.", err); } - media.stream = null; + return ret; } export async function getPreviewUrl() { @@ -75,31 +93,6 @@ 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(); diff --git a/test/UnitTest/Services/VideoDeviceTest.cs b/test/UnitTest/Services/VideoDeviceTest.cs index 3446801668d..7b19d907d64 100644 --- a/test/UnitTest/Services/VideoDeviceTest.cs +++ b/test/UnitTest/Services/VideoDeviceTest.cs @@ -26,6 +26,8 @@ public async Task GetDevices_Ok() public async Task Open_Ok() { Context.JSInterop.Setup("getPreviewUrl").SetResult("blob:https://test-preview"); + Context.JSInterop.Setup("open", _ => true).SetResult(true); + Context.JSInterop.Setup("close", _ => true).SetResult(true); var service = Context.Services.GetRequiredService(); var options = new MediaTrackConstraints() @@ -36,8 +38,11 @@ public async Task Open_Ok() Width = 480, VideoSelector = ".bb-video" }; - await service.Open(options); - await service.Close(".bb-video"); + var open = await service.Open(options); + Assert.True(open); + + var close = await service.Close(".bb-video"); + Assert.True(close); Assert.Equal("test-device-id", options.DeviceId); Assert.Equal("user", options.FacingMode); @@ -46,7 +51,6 @@ public async Task Open_Ok() Assert.Equal(".bb-video", options.VideoSelector); await service.Capture(); - await service.Flip(); var url = await service.GetPreviewUrl(); Assert.Equal("blob:https://test-preview", url); }