Blazor onended is not firing

Viewed 714

I'm starting to work with Blazor. My intention is playing one random Video after another. Therefore i wanted to use the Blazor Event Listener. But the onended Event is not firing (everything is working fine with onclick).

video Element:

<figure id="video-player">
    <video autoplay="autoplay" @onended="NewVideo">
        @videoUrl
    </video>
</figure>

Codeblock:

private MarkupString videoUrl;

    protected override void OnInitialized()
    {
        NewVideo();
    }

    private void NewVideo()
    {
        videoUrl = new MarkupString($"<source src=\"videos/{tv.GetRandomVideoFileName()}\" type=\"video/mp4\">");

    }

OnInitialized is working as intended, and if I change the onended to onclick everything is also working fine.

To Mention: I know, only changing the source wouldnt start the next Video. That would be my next Task on the List :). First I only want to Change the source in the DOM.

2 Answers

The short story is that this isn't supported, and did not make the v5 release. See the issue here:

https://github.com/dotnet/aspnetcore/issues/24323

The longer story is that you'll have to fire up some JS interop. Reference is here: https://docs.microsoft.com/en-us/aspnet/core/blazor/call-dotnet-from-javascript?view=aspnetcore-5.0#component-instance-method-call

On the JS side, you'll need something like this:

var player = document.getElementById('player');
player.onended = (e) => {
    DotNet.invokeMethodAsync('{assembly_name_here}', 'SongEnded');
};

The assembly name is probably the name of the project, like MyBlazorApp. Then in your component, you'll need a static action to get the thing wired up:

    protected override void OnInitialized()
    {
        action = UpdateMessage;
    }
    private static Action action;

    private void UpdateMessage()
    {
        // DO STUFF HERE
    }

    [JSInvokable]
    public static void SongEnded()
    {
        action.Invoke();
    }

It's a little clunky, sure, but essentially you're mapping a JS event to a static method on the component. You're probably thinking, "Well, what if I have multiple instances of the component?" In that case, you'll have to be a little creative about passing context to the static method and finding the right instance. Again, check the reference above, it has plenty of examples.

Came up with a solution to avoid static method. In code part we need to hold reference of DotNetObjectReference and also some index of it (that will play part in JS), this is example class:

public abstract class VideoPlayerBase : ComponentBase, IDisposable
{
    [Parameter] public string Source { get; set; }
    [Parameter] public EventCallback VideoEndedCallback { get; set; }

    [Inject] protected IJSRuntime JS { get; set; }

    /// <summary>
    /// DotNetObjectReference of current component instance.
    /// This is used for catching `onended` event on video tag (which apparently is not supported by Blazor)
    /// </summary>
    private DotNetObjectReference<VideoPlayerBase>? _dotNetObjectReference;

    /// <summary>
    /// Index of DotNetObjectReference inside BlazorApp.dotNetObjectReferences array.
    /// This is used to be able to relevant DotNetObjectReference from PriskApp.dotNetObjectReferences array.
    /// </summary>
    protected int DotNetObjectReferenceIndex { get; set; } = -1;

    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();
        _dotNetObjectReference = DotNetObjectReference.Create(this);
        DotNetObjectReferenceIndex = await JS.InvokeAsync<int>("BlazorApp.addDotNetObjectReference", _dotNetObjectReference);
    }

    [JSInvokable("VideoEnded")]
    public async Task VideoEndedAsync()
    {
        await VideoEndedCallback.InvokeAsync(null);
    }

    public void Dispose()
    {
        _dotNetObjectReference?.Dispose();
    }
}

Then we need js part:

var BlazorApp = BlazorApp || {
    dotNetObjectReferences: [],

    addDotNetObjectReference: function (dotNetObjectReference) {
        PriskApp.dotNetObjectReferences.push(dotNetObjectReference);
        return PriskApp.dotNetObjectReferences.length - 1;
    },

    getDotNetObjectReference: function (index) {
        return PriskApp.dotNetObjectReferences[index];
    },
};

And lastly the component view:

@inherits VideoPlayerBase

<div>
    <video style="width:100%;" controls onended="(BlazorApp.getDotNetObjectReference(@DotNetObjectReferenceIndex).invokeMethodAsync('VideoEnded'))()">
        <source src="@Source" type="video/mp4" controls>
        Your browser does not support HTML video.
    </video>
</div>

So basically this work by creating DotNetObjectReference in OnInitializedAsync method and then calling js function BlazorApp.addDotNetObjectReference to add this reference to some js array and to get index of it, which is used in view part. Then once Video ends onended event is triggered and inside it it gets the relevant DotNetObjectReference and calls its method.

Related