Возможно, вам удастся создать вид полифилла.
Сначала, чтобы определить, поддерживаем мы событие или нет, мы можем проверить ('onchange' in window.TextTrackList)
.Таким образом, мы можем условно интегрировать наш несовершенный полифилл и оставить правильные реализации без изменений.
Затем мы можем итерировать каждый x-раз по TextTracks нашего TextTrackList, чтобы найти, какой из них является активным, это должен быть тотс mode
, установленным на "showing"
.
Теперь нам просто нужно сохранить предыдущий активный трек и проверить, совпадает ли текущий.В противном случае запустите Event.
Таким образом, простая реализация может быть
// 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];
}
}
}
Но для реализации лучшего полизаполнения мы захотим переопределить исходные addEventListener
, removeEventListener
и onchange
свойства прототипа TextTrackList.
Вот грубая реализация, которая не позаботится о третьем параметре [добавить / удалить] 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>