skip to Main Content

I’m developing a web application that allows users to switch between the front and back cameras on their device. While the front camera works fine, I’m encountering an issue where the back camera only shows a black screen.

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();
                }
            }
        }
    }
}

What I Have Tried I tried adding a delay between stopping the current stream and starting the new one. I added console logs to debug the flow, but still facing issues. Expected Behavior The back camera should activate immediately after clicking the "Rotate Camera" button without showing a black screen.

Actual Behavior The back camera either shows a black screen or takes multiple clicks to activate. I’m testing this on multiple devices, and the issue persists across different browsers and devices. Camera permissions are granted properly. Any Help or Suggestions? I’d appreciate any help or suggestions to resolve this issue. Thank you!

2

Answers


  1. Chosen as BEST ANSWER

    @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;
        isCameraActive = True; 
    
        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 (isCameraActive) 
                {
                    await JSRuntime.InvokeVoidAsync("stopCamera"); 
                    isCameraActive = false; 
                } 
                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
                }
            }
        }
    }


  2. Test Code

    site.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>
    

    Camera.razor

    @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;
            }
        }
    }
    

    UPDATE

    Test in iPhone 13 with ios17

    enter image description here

    Test Result

    enter image description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search