Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
939eeb5
refactor: 增加 IMediaDevices 服务
ArgoZhang May 1, 2025
95fc5ed
doc: 更新示例
ArgoZhang May 1, 2025
9350eec
feat: 增加 media 脚本
ArgoZhang May 1, 2025
ebcca89
feat: 增加 IMediaDeviceInfo 接口
ArgoZhang May 1, 2025
c42dab6
doc: 更新示例
ArgoZhang May 1, 2025
d31e559
refactor: 增加 getUserMedia 方法
ArgoZhang May 1, 2025
ca78fe9
feat: 增加视频功能
ArgoZhang May 1, 2025
53d788a
doc: 增加示例
ArgoZhang May 1, 2025
912503a
feat: 增加 IMeidaVideo 接口服务
ArgoZhang May 2, 2025
c9cf582
doc: 更新示例
ArgoZhang May 2, 2025
ac7faec
refactor: 优化 registerBootstrapBlazorModule 方法
ArgoZhang May 2, 2025
cc37a8a
feat: 增加 drawImage 方法
ArgoZhang May 2, 2025
1cf7e5a
refactor: 移除 GetDisplayMedia 方法
ArgoZhang May 2, 2025
63d1f6c
feat: 精简代码
ArgoZhang May 2, 2025
890a515
doc: 更新示例
ArgoZhang May 2, 2025
560379a
Merge branch 'main' into feat-media
ArgoZhang May 2, 2025
21d216c
refactor: 重构 IMediaVideo 接口
ArgoZhang May 2, 2025
cb4f77e
refactor: 更新脚本
ArgoZhang May 2, 2025
62ab88d
refactor: 重构代码
ArgoZhang May 2, 2025
9553e17
doc: 更新示例
ArgoZhang May 2, 2025
9555a5c
refactor: 更新接口
ArgoZhang May 2, 2025
2299067
refactor: 增加镜头翻转功能
ArgoZhang May 2, 2025
5e4ee67
refactor: 更改服务名称
ArgoZhang May 2, 2025
5a8f7bd
feat: 更新 open close 方法实现逻辑
ArgoZhang May 2, 2025
38f07b9
doc: 更新示例
ArgoZhang May 2, 2025
3acbb27
feat: 实现 GetPreviewUrl 方法
ArgoZhang May 2, 2025
3bddd3c
doc: 更新示例
ArgoZhang May 2, 2025
4515544
refactor: 精简代码
ArgoZhang May 2, 2025
7f0d7e9
refactor: 增加翻转功能
ArgoZhang May 2, 2025
6f8e7f3
doc: 更新示例代码
ArgoZhang May 2, 2025
8c1edfe
chore: 增加源码映射文件
ArgoZhang May 2, 2025
9a9237f
doc: 增加菜单
ArgoZhang May 2, 2025
f7d7251
doc: 更改示例文档名称
ArgoZhang May 2, 2025
885651e
doc: 更新文档
ArgoZhang May 2, 2025
ea65373
test: 增加单元测试
ArgoZhang May 2, 2025
18d561a
chore: bump version 9.6.1-beta01
ArgoZhang May 2, 2025
d6d62b7
Merge branch 'chore-version' into feat-media
ArgoZhang May 2, 2025
ee34de8
refactor: 撤销 Flip 方法
ArgoZhang May 2, 2025
e549994
doc: 撤销更改
ArgoZhang May 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/BootstrapBlazor.Server/Components/Samples/VideoDevices.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
@page "/video-device"
@inject IStringLocalizer<VideoDevices> Localizer

<h3>@Localizer["VideoDeviceTitle"]</h3>

<h4>@Localizer["VideoDeviceIntro"]</h4>

<Pre>[Inject, NotNull]
private IBluetooth? BluetoothService { get; set; }</Pre>

<DemoBlock Title="@Localizer["BaseUsageTitle"]"
Introduction="@Localizer["BaseUsageIntro"]"
Name="Normal">
<div class="row form-inline g-3">
<div class="col-12">
<Button Text="@Localizer["VideoDeviceRequestText"]" Icon="fa-solid fa-photo-film" OnClick="OnRequestDevice"></Button>
<Button Text="@Localizer["VideoDeviceOpenText"]" Icon="fa-solid fa-play" OnClick="OnOpenVideo" class="ms-2"></Button>
<Button Text="@Localizer["VideoDeviceCloseText"]" Icon="fa-solid fa-stop" OnClick="OnCloseVideo" class="ms-2"></Button>
<Button Text="@Localizer["VideoDeviceCaptureText"]" Icon="fa-solid fa-camera" OnClick="OnCapture" class="ms-2"></Button>
<Button Text="@Localizer["VideoDeviceFlipText"]" Icon="fa-solid fa-camera-rotate" OnClick="OnFlip" class="ms-2"></Button>
</div>
<div class="col-12">
<Select Items="@_items" @bind-Value="_deviceId" DisplayText="Devices" ShowLabel="true"></Select>
</div>
</div>

<video class="bb-video" muted playsinline autoplay></video>

@if (!string.IsNullOrEmpty(_previewUrl))
{
<img class="bb-image" src="@_previewUrl" />
}
</DemoBlock>

Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// 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;

/// <summary>
/// VideoDevice Component
/// </summary>
public partial class VideoDevices
{
[Inject, NotNull]
private IVideoDevice? VideoDeviceService { get; set; }

private readonly List<IMediaDeviceInfo> _devices = [];

private List<SelectedItem> _items = [];

private string? _deviceId;

private string? _previewUrl;

private async Task OnRequestDevice()
{
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()
{
_previewUrl = "";
await VideoDeviceService.Close(".bb-video");
}

private async Task OnCapture()
{
_previewUrl = await VideoDeviceService.GetPreviewUrl();
}

private async Task OnFlip()
{
await VideoDeviceService.Flip();
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
14 changes: 13 additions & 1 deletion src/BootstrapBlazor.Server/Locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -7116,5 +7117,16 @@
"UrlDesc": "Use your iPhone's camera or the QR code scanning function to scan the QR code below, then open the <code>Safari</code> 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 <code>WiFi</code> network.",
"EmailDesc": "Use your iPhone's camera or the QR code scanner to scan the QR code below, then open the <code>Email</code> 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"
}
}
14 changes: 13 additions & 1 deletion src/BootstrapBlazor.Server/Locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -4920,7 +4920,8 @@
"UniverSheet": "表格组件 UniverSheet",
"ShieldBadge": "徽章组件 ShieldBadge",
"OtpInput": "验证码输入框 OtpInput",
"TotpService": "时间密码验证服务 ITotpService"
"TotpService": "时间密码验证服务 ITotpService",
"VideoDevices": "视频设备服务 IVideoDevice"
},
"BootstrapBlazor.Server.Components.Samples.Table.TablesHeader": {
"TablesHeaderTitle": "表头分组功能",
Expand Down Expand Up @@ -7116,5 +7117,16 @@
"UrlDesc": "使用 <code>iPhone</code> 手机相机或者扫描二维码功能扫描下方二维码后,打开 <code>Safari</code> 浏览器并且打开当前地址",
"WiFiDesc": "使用 <code>iPhone</code> 手机相机或者扫描二维码功能扫描下方二维码后,自动加入 <code>WiFi</code> 网络",
"EmailDesc": "使用 <code>iPhone</code> 手机相机或者扫描二维码功能扫描下方二维码后,打开 <code>Email</code> 应用发送邮件"
},
"BootstrapBlazor.Server.Components.Samples.VideoDevices": {
"VideoDeviceTitle": "IVideoDevice 视频设备服务",
"VideoDeviceIntro": "通过此服务获得视频设备操作能力",
"BaseUsageTitle": "基本用法",
"BaseUsageIntro": "通过调用不同的 api 方法进行不同操作",
"VideoDeviceRequestText": "枚举设备",
"VideoDeviceOpenText": "打开设备",
"VideoDeviceCloseText": "关闭设备",
"VideoDeviceCaptureText": "截图",
"VideoDeviceFlipText": "翻转镜头"
}
}
3 changes: 2 additions & 1 deletion src/BootstrapBlazor.Server/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src/BootstrapBlazor/BootstrapBlazor.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<Version>9.6.1-beta02</Version>
<Version>9.6.1-beta01</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
45 changes: 36 additions & 9 deletions src/BootstrapBlazor/Components/Camera/Camera.razor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -136,22 +139,46 @@ 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 }
}
const constraints = {
facingMode: "environment",
width: { ideal: width },
height: { ideal: height }
}
await camera.video.track.applyConstraints(constraints);
}

stopDevice(camera)
play(camera, constrains)

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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ public static IServiceCollection AddBootstrapBlazor(this IServiceCollection serv
services.TryAddScoped<IBrowserFingerService, DefaultBrowserFingerService>();
services.TryAddScoped<ISerialService, DefaultSerialService>();
services.TryAddScoped<IBluetooth, DefaultBluetooth>();
services.TryAddScoped<IMediaDevices, DefaultMediaDevices>();
services.TryAddScoped<IVideoDevice, DefaultVideoDevice>();
services.AddScoped<TabItemTextOptions>();
services.AddScoped<DialogService>();
services.AddScoped<MaskService>();
Expand Down
53 changes: 53 additions & 0 deletions src/BootstrapBlazor/Services/MediaDevices/DefaultMediaDevices.cs
Original file line number Diff line number Diff line change
@@ -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 BootstrapBlazor.Components;

class DefaultMediaDevices(IJSRuntime jsRuntime) : IMediaDevices
{
private JSModule? _module = null;

private async Task<JSModule> LoadModule()
{
_module ??= await jsRuntime.LoadModuleByName("media");
return _module;
}

public async Task<IEnumerable<IMediaDeviceInfo>?> EnumerateDevices()
{
var module = await LoadModule();
return await module.InvokeAsync<List<MediaDeviceInfo>?>("enumerateDevices");
}

public async Task Open(MediaTrackConstraints constraints)
{
var module = await LoadModule();
await module.InvokeVoidAsync("open", constraints);
}

public async Task Close(string? videoSelector)
{
var module = await LoadModule();
await module.InvokeVoidAsync("close", videoSelector);
}

public async Task Capture()
{
var module = await LoadModule();
await module.InvokeVoidAsync("capture");
}

public async Task<string?> GetPreviewUrl()
{
var module = await LoadModule();
return await module.InvokeAsync<string?>("getPreviewUrl");
}

public async Task Flip()
{
var module = await LoadModule();
await module.InvokeAsync<string?>("flip");
}
}
49 changes: 49 additions & 0 deletions src/BootstrapBlazor/Services/MediaDevices/DefaultVideoDevice.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// 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 DefaultVideoDevice(IMediaDevices deviceService) : IVideoDevice
{
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <returns></returns>
public async Task<List<IMediaDeviceInfo>?> GetDevices()
{
var ret = new List<IMediaDeviceInfo>();
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? videoSelector)
{
return deviceService.Close(videoSelector);
}

public Task Capture()
{
return deviceService.Capture();
}

public Task<string?> GetPreviewUrl()
{
return deviceService.GetPreviewUrl();
}

public Task Flip()
{
return deviceService.Flip();
}
}
Loading