我正在开发一个 Web 应用程序,允许用户在其设备上的前置摄像头和后置摄像头之间切换。虽然前置摄像头工作正常,但我遇到了后置摄像头仅显示黑屏的问题。
camera.js
// wwwroot/camera.js
let currentStream = null;
async function openCamera(facingMode) {
if (currentStream) {
currentStream.getTracks().forEach(track => track.stop());
}
try {
const constraints = {
video: {
facingMode: facingMode
}
};
const stream = await navigator.mediaDevices.getUserMedia(constraints);
currentStream = stream;
document.getElementById('videoFeed').srcObject = stream;
console.log(`Camera opened with facing mode: ${facingMode}`);
} catch (err) {
console.error('Error accessing camera: ', err);
}
}
function stopCamera() {
if (currentStream) {
currentStream.getTracks().forEach(track => track.stop());
currentStream = null;
}
}
async function startVideo(src) {
try {
const permission = await checkCameraPermission();
if (permission === 'prompt' || permission === 'granted') {
navigator.getUserMedia(
{ video: true, audio: false },
function (localMediaStream) {
let video = document.getElementById(src);
video.srcObject = localMediaStream;
video.onloadedmetadata = function (e) {
video.play();
};
},
function (err) {
console.error('Error accessing camera:', err);
throw err; // Propagate the error
}
);
} else {
console.error('Camera permission denied.');
}
} catch (error) {
console.error('Error starting video:', error);
}
}
function getFrame(src, dest, dotnetHelper) {
let video = document.getElementById(src);
let canvas = document.getElementById(dest);
// Check if the video and canvas elements exist before drawing the image
if (video && canvas) {
canvas.getContext('2d').drawImage(video, 0, 0, 150, 150);
// Resize the image on the canvas
let resizedCanvas = document.createElement('canvas');
resizedCanvas.width = 200; // Set desired width
resizedCanvas.height = 200; // Set desired height
let ctx = resizedCanvas.getContext('2d');
ctx.drawImage(canvas, 0, 0, resizedCanvas.width, resizedCanvas.height);
// Convert the resized image to base64 JPEG format
let dataUrl = resizedCanvas.toDataURL("image/jpeg");
// Invoke the .NET method with the resized image data
dotnetHelper.invokeMethodAsync('ProcessImage', dataUrl);
} else {
console.error('Video or canvas element not found.');
}
}
function stopVideo(src) {
let video = document.getElementById(src);
// Check if the video element exists before stopping
if (video) {
if ('srcObject' in video) {
let tracks = video.srcObject.getTracks();
tracks.forEach(track => track.stop());
video.srcObject = null;
} else {
video.src = '';
}
} else {
console.error('Video element with ID ' + src + ' not found.');
}
}
function closeCamera() {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.getUserMedia({ video: true })
.then(function (stream) {
// Stop all tracks
let tracks = stream.getTracks();
tracks.forEach(track => track.stop());
})
.catch(function (error) {
console.error('Error closing the camera: ', error);
});
} else {
console.error('getUserMedia is not supported on this browser.');
}
}
Razorpage
@using MudBlazor
@inject IJSRuntime JSR
@inject ISnackbar Snackbar
<div>
<MudDialog Style="overflow: hidden;">
<DialogContent>
<MudCard Class="pa-2">
<MudCardContent>
<div style="display: flex; justify-content: center; align-items: center;">
<video id="videoFeed" width="600" height="300"></video>
</div>
<canvas class="d-none" id="currentFrame" width="150" height="150"></canvas>
<div style="display: flex; justify-content: center; margin-top: 16px;">
<MudIconButton Icon="@Icons.Material.Filled.CameraAlt" Color="Color.Primary" Size="Size.Large" OnClick="@Save" />
<MudButton OnClick="ToggleCamera" Color="Color.Primary">@buttonText</MudButton>
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Cancel" Size="Size.Large" OnClick="@Cancel" />
</div>
</MudCardContent>
</MudCard>
</DialogContent>
</MudDialog>
</div>
@code {
[CascadingParameter] MudDialogInstance MudDialog { get; set; }
[Parameter] public string ImageUri { get; set; }
private bool isFrontCamera = true;
private string buttonText = "Open Back Camera";
private DotNetObjectReference<CameraDialog> oCounter;
private string frameUri;
private bool nestedVisible = false;
private bool isVideoStarted = false;
protected override async Task OnInitializedAsync()
{
try
{
await JSR.InvokeVoidAsync("startVideo", "videoFeed");
isVideoStarted = true;
}
catch (Exception ex)
{
Snackbar.Add("Camera access is denied. Please allow access to the camera.", Severity.Error);
}
}
private async Task ToggleCamera()
{
if (isFrontCamera)
{
await OpenCamera("environment");
buttonText = "Open Front Camera";
}
else
{
await OpenCamera("user");
buttonText = "Open Back Camera";
}
isFrontCamera = !isFrontCamera;
}
private async Task OpenCamera(string facingMode)
{
await JSR.InvokeVoidAsync("openCamera", facingMode);
}
private async Task Save()
{
if (!isVideoStarted)
{
Snackbar.Add("Camera access is denied. Please allow access to the camera.", Severity.Error);
MudDialog.Cancel();
return;
}
if (oCounter == null)
oCounter = DotNetObjectReference.Create(this);
try
{
await JSR.InvokeAsync<string>("getFrame", "videoFeed", "currentFrame", oCounter);
await JSR.InvokeVoidAsync("stopVideo", "videoFeed");
isVideoStarted = false;
MudDialog.Close(DialogResult.Ok(frameUri));
}
catch (Exception ex)
{
if (ex.Message.Contains("Cannot read properties of null (reading 'getTracks')"))
{
Snackbar.Add("Camera access is denied. Please give camera permission in the browser settings.", Severity.Error);
MudDialog.Cancel();
}
else
{
Snackbar.Add($"An error occurred while capturing the image. Please try again. Error details: {ex.Message}", Severity.Error);
MudDialog.Cancel();
}
}
}
[JSInvokable]
public async Task ProcessImage(string imageString)
{
frameUri = imageString;
StateHasChanged();
var parameters = new DialogParameters { { "ImageUri", frameUri } };
var options = new DialogOptions { FullWidth = true };
}
private async Task Cancel()
{
if (isVideoStarted)
{
try
{
await JSR.InvokeVoidAsync("stopVideo", "videoFeed");
isVideoStarted = false;
MudDialog.Cancel();
}
catch (Exception ex)
{
if (ex.Message.Contains("Cannot read properties of null (reading 'getTracks')"))
{
MudDialog.Cancel();
}
else
{
MudDialog.Cancel();
}
}
}
}
}
我的尝试 我尝试在停止当前流和启动新流之间添加延迟。我添加了控制台日志来调试流程,但仍然面临问题。预期行为 单击“旋转相机”按钮后,后置摄像头应立即激活,而不显示黑屏。
实际行为后置摄像头要么显示黑屏,要么需要多次单击才能激活。我正在多个设备上对此进行测试,并且该问题在不同的浏览器和设备上仍然存在。相机权限已正确授予。有任何帮助或建议吗?对于解决此问题的任何帮助或建议,我将不胜感激。谢谢!
测试结果
测试代码
网站.css
.camera-container {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
}
.video-container {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
max-width: 600px;
margin-bottom: 16px;
}
#videoFeed {
width: 100%;
height: auto;
}
.button-container {
display: flex;
justify-content: center;
margin-bottom: 16px;
}
.images-container {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
max-width: 600px;
}
.captured-image {
width: 100%;
height: 280px;
margin-bottom: 16px;
}
camera.js
let currentStream = null;
async function openCamera(facingMode) {
if (currentStream) {
currentStream.getTracks().forEach(track => track.stop());
await new Promise(resolve => setTimeout(resolve, 500));
}
try {
const constraints = {
video: {
facingMode: facingMode
}
};
const stream = await navigator.mediaDevices.getUserMedia(constraints);
currentStream = stream;
document.getElementById('videoFeed').srcObject = stream;
console.log(`Camera opened with facing mode: ${facingMode}`);
} catch (err) {
console.error('Error accessing camera: ', err);
}
}
function stopCamera() {
if (currentStream) {
currentStream.getTracks().forEach(track => track.stop());
currentStream = null;
}
}
async function startVideo(src) {
try {
const permission = await checkCameraPermission();
if (permission === 'prompt' || permission === 'granted') {
navigator.mediaDevices.getUserMedia(
{ video: true, audio: false }
).then(function (localMediaStream) {
let video = document.getElementById(src);
video.srcObject = localMediaStream;
video.onloadedmetadata = function (e) {
video.play();
};
}).catch(function (err) {
console.error('Error accessing camera:', err);
throw err; // Propagate the error
});
} else {
console.error('Camera permission denied.');
}
} catch (error) {
console.error('Error starting video:', error);
}
}
function getFrame(src, dest, dotnetHelper) {
let video = document.getElementById(src);
let canvas = document.getElementById(dest);
// Check if the video and canvas elements exist before drawing the image
if (video && canvas) {
canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
// Convert the canvas image to base64 JPEG format
let dataUrl = canvas.toDataURL("image/jpeg");
// Invoke the .NET method with the image data
dotnetHelper.invokeMethodAsync('ProcessImage', dataUrl);
} else {
console.error('Video or canvas element not found.');
}
}
function stopVideo(src) {
let video = document.getElementById(src);
// Check if the video element exists before stopping
if (video) {
if ('srcObject' in video) {
let tracks = video.srcObject.getTracks();
tracks.forEach(track => track.stop());
video.srcObject = null;
} else {
video.src = '';
}
} else {
console.error('Video element with ID ' + src + ' not found.');
}
}
function closeCamera() {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.getUserMedia({ video: true })
.then(function (stream) {
// Stop all tracks
let tracks = stream.getTracks();
tracks.forEach(track => track.stop());
})
.catch(function (error) {
console.error('Error closing the camera: ', error);
});
} else {
console.error('getUserMedia is not supported on this browser.');
}
}
async function checkCameraPermission() {
try {
let stream = await navigator.mediaDevices.getUserMedia({ video: true });
stream.getTracks().forEach(track => track.stop());
return 'granted';
} catch (err) {
if (err.name === 'NotAllowedError') {
return 'denied';
} else if (err.name === 'NotFoundError') {
return 'not found';
} else {
return 'prompt';
}
}
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BlazorApp1</title>
<base href="/" />
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
<link rel="stylesheet" href="css/app.css" />
<link rel="icon" type="image/png" href="favicon.png" />
<link href="BlazorApp1.styles.css" rel="stylesheet" />
<link href="css/site.css" rel="stylesheet" />
<script src="camera.js"></script>
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
</head>
<body>
<div id="app">
<svg class="loading-progress">
<circle r="40%" cx="50%" cy="50%" />
<circle r="40%" cx="50%" cy="50%" />
</svg>
<div class="loading-progress-text"></div>
</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.webassembly.js"></script>
</body>
</html>
相机.剃须刀
@page "/camera"
@using Microsoft.JSInterop
@inject IJSRuntime JSRuntime
@inject ISnackbar Snackbar
<div class="camera-container">
<MudCard Class="pa-2">
<MudCardContent>
<div class="video-container">
<video id="videoFeed" autoplay></video>
</div>
<canvas class="d-none" id="currentFrame"></canvas>
<div class="button-container">
<MudIconButton Icon="@Icons.Material.Filled.CameraAlt" Color="Color.Primary" Size="Size.Large" OnClick="Save" />
<MudButton OnClick="ToggleCamera" Color="Color.Primary">@buttonText</MudButton>
@* <MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Cancel" Size="Size.Large" OnClick="Cancel" /> *@
</div>
@if (capturedImages != null && capturedImages.Count > 0)
{
<div class="images-container">
@foreach (var image in capturedImages)
{
<img src="@image" alt="Captured Image" class="captured-image" />
}
</div>
}
</MudCardContent>
</MudCard>
</div>
@code {
private bool isFrontCamera = true;
private string buttonText = "Open Back Camera";
private DotNetObjectReference<CameraDialog> oCounter;
private List<string> capturedImages = new List<string>();
private bool isVideoStarted = false;
protected override async Task OnInitializedAsync()
{
try
{
await JSRuntime.InvokeVoidAsync("startVideo", "videoFeed");
isVideoStarted = true;
}
catch (Exception ex)
{
Snackbar.Add("Camera access is denied. Please allow access to the camera.", Severity.Error);
}
}
private async Task ToggleCamera()
{
try
{
if (isFrontCamera)
{
await OpenCamera("environment");
buttonText = "Open Front Camera";
}
else
{
await OpenCamera("user");
buttonText = "Open Back Camera";
}
isFrontCamera = !isFrontCamera;
}
catch (Exception ex)
{
Snackbar.Add($"Error switching camera: {ex.Message}", Severity.Error);
}
}
private async Task OpenCamera(string facingMode)
{
try
{
await JSRuntime.InvokeVoidAsync("openCamera", facingMode);
}
catch (Exception ex)
{
Snackbar.Add($"Error opening camera: {ex.Message}", Severity.Error);
}
}
private async Task Save()
{
if (!isVideoStarted)
{
Snackbar.Add("Camera access is denied. Please allow access to the camera.", Severity.Error);
return;
}
if (oCounter == null)
oCounter = DotNetObjectReference.Create(new CameraDialog());
try
{
await JSRuntime.InvokeAsync<string>("getFrame", "videoFeed", "currentFrame", oCounter);
var cameraDialog = oCounter.Value;
var imageUri = cameraDialog.CapturedImage;
if (!string.IsNullOrEmpty(imageUri))
{
capturedImages.Add(imageUri);
StateHasChanged();
}
}
catch (Exception ex)
{
if (ex.Message.Contains("Cannot read properties of null (reading 'getTracks')"))
{
Snackbar.Add("Camera access is denied. Please give camera permission in the browser settings.", Severity.Error);
}
else
{
Snackbar.Add($"An error occurred while capturing the image. Please try again. Error details: {ex.Message}", Severity.Error);
}
}
}
private async Task Cancel()
{
if (isVideoStarted)
{
try
{
await JSRuntime.InvokeVoidAsync("stopVideo", "videoFeed");
isVideoStarted = false;
}
catch (Exception ex)
{
// Handle cancellation errors
}
}
}
}
CameraDialog.cs
using Microsoft.JSInterop;
namespace BlazorApp1
{
public class CameraDialog
{
public string? CapturedImage { get; private set; }
[JSInvokable]
public Task ProcessImage(string imageString)
{
CapturedImage = imageString;
return Task.CompletedTask;
}
}
}