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.