Search code examples
c#blazorblazor-server-side

Previewing a dropped in local image/video in blazor server app without uploading it to the server


I am trying to create an upload component that previews a video or image before actually uploading it.

<FluentInputFile
        Id="video-upload"
        Mode="InputFileMode.SaveToTemporaryFolder"
        Accept="video/*"
        MaximumFileSize="@(2000*1024*1024)"
        @bind-ProgressPercent="@videoProgress"
        OnCompleted="@OnVideoCompletedAsync"
        Style="width:100%; height: 300px;">

        <ChildContent>

            <FluentIcon Value="@(new Icons.Regular.Size16.ArrowUpload())"></FluentIcon>
            <span style="font-family: monospace; margin-left: 10px;">Video</span>       
        </ChildContent>
        
</FluentInputFile>
int videoProgress = 0;
List<FluentInputFileEventArgs> videos = default!; 
FileInfo? video;
string? videoUrl;
private async Task OnVideoCompletedAsync(IEnumerable<FluentInputFileEventArgs> _videos) {
    videos = _videos.ToList();
    Console.WriteLine($"[EVENT] Video Dropped : {videos.Count()}");
    videos.ForEach(x => Console.WriteLine($"Name: {x.Name}, Size: {x.Size/1000000f} mb"));
    video = videos[0].LocalFile;
    videoUrl = await embedFiles(video, "video/mp4");
    videoProgress = 0;
    StateHasChanged();
}

private async Task<string?> embedFiles(FileInfo? fileInfo, string format) {
    if(fileInfo == null) {
        return null;
    }
    byte[] bytes = new byte[fileInfo.Length];
    using(FileStream fileStream = fileInfo.OpenRead()) {
        await fileStream.ReadAsync(bytes);
    }
    string url = await JSRuntime.InvokeAsync<string>("bytesToDataURL", bytes, format); 
    Console.WriteLine($"[INFO] File URLs : {url}");
    return url;
}
async function bytesToDataURL(bytes, format) {
    const blob = new Blob([new Uint8Array(bytes)], format);
    let url = URL.createObjectURL(blob); 
    console.log(url);
    return url;
}

But if I understand this correctly this uploads the video to the server and then would have to send this back to the client as bytes for it to create the blob URL. This is fine for smaller files but I feel this back and forth way is wasteful when you have very large files. I could attach an event handler to FluentInputFile in JS directly but that would disrupt the actual event handlers.


Solution

  • You could try this below code:

    Home.razor:

    @page "/"
    @using Microsoft.FluentUI.AspNetCore.Components
    @inject IJSRuntime JSRuntime
    @rendermode InteractiveServer
    
    <!-- FluentInputFile -->
    <FluentInputFile Id="fileInput"
                     Accept=".mp4, .mov, .avi, .jpg, .png"
                     DragDropZoneVisible="true" />
    
    <!-- Preview Area -->
    <div id="preview-container">
        <!-- JavaScript will populate this area -->
    </div>
    
    @code {
        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            if (firstRender)
            {
                // Attach the JavaScript event handler
                await JSRuntime.InvokeVoidAsync("setupFluentInputFilePreview", "fileInput", "preview-container");
            }
        }
    }
    

    Javascript:

    <script>
            window.setupFluentInputFilePreview = (inputId, previewContainerId) => {
                const inputElement = document.getElementById(inputId);
                const previewContainer = document.getElementById(previewContainerId);
        if (!inputElement || !previewContainer) {
                    console.error("Input or preview container not found.");
                    return;
                }
        // Attach an event listener to the FluentInputFile component
                inputElement.addEventListener("change", (event) => {
                    // Clear any existing preview
                    previewContainer.innerHTML = "";
        // Get the selected file
                    const file = event.target.files[0];
                    if (!file) return;
        // Check if the file is an image or video
                    const isVideo = file.type.startsWith("video");
                    const isImage = file.type.startsWith("image");
        if (isVideo || isImage) {
                        // Create a Blob URL for the file
                        const blobUrl = URL.createObjectURL(file);
        // Create the appropriate preview element
                        let previewElement;
                        if (isVideo) {
                            previewElement = document.createElement("video");
                            previewElement.controls = true;
                            previewElement.width = 320;
                            previewElement.height = 240;
                        } else if (isImage) {
                            previewElement = document.createElement("img");
                            previewElement.style.maxWidth = "100%";
                            previewElement.style.height = "auto";
                        }
        // Set the source of the preview element
                        previewElement.src = blobUrl;
        // Append the preview element to the container
                        previewContainer.appendChild(previewElement);
                    } else {
                        console.warn("Unsupported file type:", file.type);
                    }
                });
            };
    </script>
    

    enter image description here