The TextTrackList.onchange event is not working in IE and Edge. In Chrome and FireFox it works fine.
Is there any alternative I can use? Ive searched through the available events but can't find any.
Or how can I create a workaround? So it works amongst all browsers?
https://www.javascripture.com/TextTrackList
var video = document.getElementById('video');
video.textTracks.addEventListener('change', function () {
console.log("TextTracks change event fired!");
});
video {
max-width: 400px;
max-height: 400px;
}
<video controls id="video">
<source src="https://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_30mb.mp4" type="video/mp4" />
<track label="Caption #1" kind="subtitles" srclang="nl" src="path/to/caption1.vtt">
<track label="Caption #2" kind="subtitles" srclang="en" src="path/to/caption2.vtt">
<track label="Caption #3" kind="subtitles" srclang="de" src="path/to/caption3.vtt">
</video>
You might be able to create a kind of polyfill.
First to detect if we support the event or not, we can check for ('onchange' in window.TextTrackList). So we can integrate our unperfect polyfill conditionally and leave the correct implementations unchanged.
Then, we can iterate every x-time over our TextTrackList's TextTracks in order to find which one is the active one, it should be the one with its mode set to "showing".
Now, we just have to store the previous active track and check if the current one is the same. Otherwise, fire the Event.
So a simple implementation could be
// in an outer scope
// textTracks is our TextTrackList object
var active = getActive();
// start polling
poll();
function poll() {
// schedule next check in a frame
requestAnimationFrame(poll);
var current = getActive();
if (current !== active) {
active = current; // update the active one
// dispatchEvent is not supported on TextTrackList in IE...
onchange({
target: textTracks
});
}
}
function getActive() {
for (var i = 0; i < textTracks.length; i++) {
if (textTracks[i].mode === 'showing') {
return textTracks[i];
}
}
}
But to implement a better polyfill, we will want to override the original addEventListener, removeEventListener and onchange properties of the TextTrackList prototype.
Here is a rough implementation, which will not take care of the third param of [add/remove]EventListener.
(function() {
/* Tries to implement an 'change' Event on TextTrackList Objects when not implemented */
if (window.TextTrackList && !('onchange' in window.TextTrackList.prototype)) {
var textTracksLists = [], // we will store all the TextTrackLists instances
polling = false; // and run only one polling loop
var proto = TextTrackList.prototype,
original_addEvent = proto.addEventListener,
original_removeEvent = proto.removeEventListener;
var onchange = {
get: getonchange,
set: setonchange
};
Object.defineProperty(proto, 'onchange', onchange);
Object.defineProperty(proto, 'addEventListener', fnGetter(addListener));
Object.defineProperty(proto, 'removeEventListener', fnGetter(removeListener));
function fnGetter(fn) {
return {
get: function() {
return fn;
}
};
}
/* When we add a new EventListener, we attach a new object on our instance
This object set as '._fakeevent' will hold informations about
the current EventListeners
the current onchange handler
the parent <video> element if any
the current activeTrack
*/
function initFakeEvent(instance) {
// first try to grab the video element from where we were generated
// this is useful to not run useless tests when the video is detached
var vid_elems = document.querySelectorAll('video'),
vid_elem = null;
for (var i = 0; i < vid_elems.length; i++) {
if (vid_elems[i].textTracks === instance) {
vid_elem = vid_elems[i];
break;
}
}
textTracksLists.push(instance);
instance._fakeevent = {
parentElement: vid_elem,
listeners: {
change: []
}
}
if (!polling) { // if we are the first instance being initialised
polling = true;
requestAnimationFrame(poll); // start the checks
}
return instance._fakeevent;
}
function getonchange() {
var fakeevent = this._fakeevent;
if (!fakeevent || typeof fakeevent !== 'object') {
return null;
}
return fakeevent.onchange || null;
}
function setonchange(fn) {
var fakeevent = this._fakeevent;
if (!fakeevent) {
fakeevent = initFakeEvent(this);
}
if (fn === null) fakeevent.onchange = null;
if (typeof fn !== 'function') return fn;
return fakeevent.onchange = fn;
}
function addListener(type, fn, options) {
if (type !== 'change') { // we only handle change for now
return original_addEvent.bind(this)(type, fn, options);
}
if (!fn || typeof fn !== 'object' && typeof fn !== 'function') {
throw new TypeError('Argument 2 of EventTarget.addEventListener is not an object.');
}
var fakeevent = this._fakeevent;
if (!fakeevent) {
fakeevent = initFakeEvent(this);
}
if (typeof fn === 'object') {
if (typeof fn.handleEvent === 'function') {
fn = fn.handleEvent;
} else return;
}
// we don't handle options yet...
if (fakeevent.listeners[type].indexOf(fn) < 0) {
fakeevent.listeners[type].push(fn);
}
}
function removeListener(type, fn, options) {
if (type !== 'change') { // we only handle change for now
return original_removeEvent.call(this, arguments);
}
var fakeevent = this._fakeevent;
if (!fakeevent || !fn || typeof fn !== 'object' && typeof fn !== 'function') {
return
}
if (typeof fn === 'object') {
if (typeof fn.handleEvent === 'function') {
fn = fn.handleEvent;
} else return;
}
// we don't handle options yet...
var index = fakeevent.listeners[type].indexOf(fn);
if (index > -1) {
fakeevent.listeners[type].splice(index, 1);
}
}
function poll() {
requestAnimationFrame(poll);
textTracksLists.forEach(check);
}
function check(instance) {
var fakeevent = instance._fakeevent;
// if the parent vid not in screen, we probably have not changed
if (fakeevent.parentElement && !fakeevent.parentElement.parentElement) {
return;
}
// get the current active track
var current = getActiveTrack(instance);
// has changed
if (current !== fakeevent.active) {
if (instance.onchange) {
try {
instance.onchange({
type: 'change',
target: instance
});
} catch (e) {}
}
fakeevent.listeners.change.forEach(call, this);
}
fakeevent.active = current;
}
function getActiveTrack(textTracks) {
for (var i = 0; i < textTracks.length; i++) {
if (textTracks[i].mode === 'showing') {
return textTracks[i];
}
}
return null;
}
function call(fn) {
fn({
type: 'change',
target: this
});
}
}
})();
var video = document.getElementById('video');
video.textTracks.onchange = function ontrackchange(e) {
console.log('changed');
};
video {
max-width: 400px;
max-height: 400px;
}
<video controls id="video">
<source src="https://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_30mb.mp4" type="video/mp4" />
<track label="Caption #1" kind="subtitles" srclang="nl" src="path/to/caption1.vtt">
<track label="Caption #2" kind="subtitles" srclang="en" src="path/to/caption2.vtt">
<track label="Caption #3" kind="subtitles" srclang="de" src="path/to/caption3.vtt">
</video>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With