Blazor and Media events - Hard to handle?

Preface

I saw some chat recently on the Blazor Gitter Channel asking about binding to events on the html5 video tag. It seems this person was not alone - others had similar problems - the events you subscribe to in Blazor are just not firing.

I decided (I like a challenge) to investigate - read on to find out more.

TLDR; I made a Blazor component to overcome the problem. Blazored.Video

Why do these events not work?

Some html events do not bubble up the dom hierarchy, so they never fire at the document level, and Blazor attaches it's event listeners to the document.

Blazor maintains a list of events that do not bubble so that it can register event listeners for the capture phase on those events.

abort, blur, change, error, focus, load, loadend, loadstart, mouseenter, mouseleave, progress, reset, scroll, submit, unload, DOMNodeInsertedIntoDocument, DOMNodeRemovedFromDocument

Notably absent from this list are the HTMLMediaElement events:

canplay , canplaythrough , durationchange , emptied , ended , loadeddata , loadedmetadata , loadstart , pause , play , playing , progress , ratechange , seeked , seeking, stalled, suspend, timeupdate , volumechange , waiting

This means any attempt to bind to these events does not work as Blazor does not register for the capture phase on these events.

What is the solution?

The real fix is to get the Blazor team to add them to the list, but this hasn't had a lot of community push, in fact the last issue raised about it was closed due to lack of activity.

Maybe someone could do this during one of the Blazor Sprints

How about a workaround?

Luckily it's not that hard to work around this with a new Video component.

It's long been known that you can trigger an html event in JavaScript land using dispatchEvent and (as long as it bubbles) Blazor will catch it and process it like any other.

el.dispatchEvent('play', { bubbles: true })

One problem is we don't want to write all those bits of code to capture and rethrow specific events because it's a lot of work to do that and prevent event loops.

Could we use an existing one that bubbles on the video element?

If you noticed, there is no change event on a media element, like video, so can we use that?

Yes, we can dispatch a change event on the video element, but what use is that when we want to subscribe to the play or timeupdate events?

The answer lies in the property value, which the change event passes back to Blazor/C#.

What if we create and populate a value property on the video element just before we dispatch the change event? We could populate it with the name of the event we are capturing.

Wait a minute, can we add value as well?

Here's a thought - what if we populate the new value with actual data along with the event name?

var data = { currentTime: el.currentTime }
el['value']=JSON.stringify({ event: 'play', payload: data })
el.dispatchEvent('change')

Now, we have a way to capture not only the non-bubbling event, but also to return an object with useful information from the video.

Summary

After doing this investigation, I realised it was workable in quite a small amount of code to wrap the html5 video element in a Blazor component that allows you, as a developer, to subscribe to any of these non-bubbling events and return arbitrary slices of data for each one.

You can find the resulting component in the Blazored components - it is a little more complicated than I laid out here, but the method is pretty much as described.

Comments (2)

Hannes Preishuber's photo

"It's long been known that you can trigger an html event in JavaScript land using dispatchEvent and (as long as it bubbles) Blazor will catch it and process it like any other." can you give me some reference? it is new for me.

Mister Magoo's photo

I don't know of any reference, sorry but it does get talked about on StackOverflow quite often in relation to Blazor/Javascript interactions.