Estirar video en el navegador





Muy a menudo, el video en los cines en línea tiene una relación de aspecto diferente a la del monitor. Por lo tanto, a veces existe el deseo de aumentar un poco la escala general recortando ligeramente los bordes. O incluso: ajuste la imagen al tamaño de la pantalla en el lado más pequeño de la imagen. Esto es especialmente cierto para las pantallas pequeñas, así como para los monitores 4: 3 más antiguos. Guardo silencio sobre el hecho de que el video original generalmente se puede estirar hacia un lado y esto debe corregirse de alguna manera.



Para resolver este problema, decidí escribir una extensión de navegador para Chrome y Firefox. La idea es la siguiente: al reproducir cualquier video del navegador, se abre un menú en pantalla, que le permite cambiar arbitrariamente la escala y la relación de aspecto de la imagen.



iframe



El primer problema con el que me encontré es que los videos en los sitios web no están necesariamente ubicados en la página principal, pero pueden estar ocultos profundamente en iframes anidados. Decidí escanear todos los iframes y encontrar todos los elementos de video en cada uno. Por cierto, esto también resuelve otro problema: nunca se sabe dónde está el video publicitario y dónde está la película. Encontremos a todos primero.



La función getVideos se llama a sí misma de forma recursiva hasta que todos los elementos de vídeo se encuentran en el último iframe. Todos los videos se agregan a la matriz ap_ext_space.videos. La función getVideos toma el documento de la página actual como parámetro de entrada. En el primer lanzamiento, se toma el documento principal. En el camino, los controladores se cuelgan en cada video, pero más sobre eso a continuación.



getVideos: function (srcDoc) {
	if (!srcDoc) {
		srcDoc = document;
		window.onkeydown = function (event) {
			var e = event || window.event;
			ap_ext_space.keyDn(e);
		};
	};

	var els = srcDoc.getElementsByTagName('video');
	for (var i = 0; i < els.length; i++) {
		els[i].addEventListener("seeked", function () {ap_ext_space.zoomw(); console.log('seeked'); }, true);
		els[i].addEventListener("abort", function () {ap_ext_space.zoomw(); console.log('abort'); }, true);
		els[i].addEventListener("pause", function () {ap_ext_space.zoomw(); console.log('pause'); }, true);
		els[i].addEventListener("play", function () {ap_ext_space.zoomw(); console.log('play'); }, true);
		els[i].addEventListener("playing", function () {ap_ext_space.zoomw(); console.log('playing'); }, true);
		els[i].addEventListener("seeked", function () {ap_ext_space.zoomw(); console.log('seeked'); }, true);

		ap_ext_space.videos.push(els[i]);
		ap_ext_space.menu(els[i], srcDoc);
	};
	console.log('all videos:', ap_ext_space.videos);

	var ifrs = srcDoc.getElementsByTagName("iframe");
	console.log('iframes:', ifrs);

	var ifr;
	for (var i = 0; i < ifrs.length; i++) {
		ifr = ifrs[i];
		try {
			var innerDoc = (ifr.contentDocument || ifr.contentWindow.document);
			var innerWindow = (ifr.contentWindow || ifr);
			innerWindow.onkeydown = function (event) {
				var e = event || window.event;
				ap_ext_space.keyDn(e);
			};
			ap_ext_space.getVideos(innerDoc);
		} catch (err) {
			console.log('err', err);
		};
	};
},


Menú OSD





De acuerdo, tenemos una lista de todos los elementos de video. Ahora, ¿cómo mostrar el menú OSD? Agreguemos su elemento de bloque a cada video. Sí, entonces tendremos muchos menús en pantalla, pero a la vez solo se muestra un video: uno de los comerciales o la película en sí. Y solo se mostrará un menú con ellos.



El video generalmente se encuentra en el div principal. Agreguemos nuestro elemento div del menú como último hijo. De esta manera, el OSD siempre se mostrará sobre el video.



La imagen OSD está codificada en base64 en formato png con un canal alfa transparente y colocada en ap_ext_space.imgUR, ya que el navegador no nos permitirá cargar la imagen desde otro dominio. Crea un menú para cada video:



menu: function(videoEl, doc) {

	//  div   video 
	//  ,       ( menuInside)
	var els = videoEl.parentNode.getElementsByTagName('div');
	var menuInside = false;
	for (var j = 0; j < els.length; j++) {
		if (els[j].id == 'ap_ext_space_container') {
			menuInside = true;
			ap_ext_space.menus.push(els[j]);
		};
	};

	if (menuInside == false) {

		//   
		var div = doc.createElement('div');
		div.innerHTML = ap_ext_space.html();
		videoEl.parentNode.appendChild(div);
		div.style.width = '520px';
		div.style.height = '410px';
		div.style.display = 'block';
		div.style.position = 'absolute';
		div.id = 'ap_ext_space_container';
		var url = "url('" + ap_ext_space.imgURL + "')";
		div.style.backgroundImage = url;
		div.style.opacity = 0.95;
		ap_ext_space.menus.push(div);

		//   
		div.addEventListener("dblclick", function(e) {
			e.preventDefault();
			e.stopPropagation();
		}, true);

		div.addEventListener("mouseover", function(e) {
			e.preventDefault();
			e.stopPropagation();

			var elem, evt = e ? e : event;
			if (evt.srcElement) {
				elem = evt.srcElement;
			} else if (evt.target) {
				elem = evt.target;
			};

			//     
			var pos = {
				ap_ext_space_num7: [520 + 134, 82],
				ap_ext_space_num8: [520 + 134 + 90, 82],
				ap_ext_space_num9: [520 + 134 + 90 + 90, 82],
				ap_ext_space_num4: [520 + 134, 82 + 90],
				ap_ext_space_num5: [520 + 134 + 90, 82 + 90],
				ap_ext_space_num6: [520 + 134 + 90 + 90, 82 + 90],
				ap_ext_space_num1: [520 + 134, 82 + 90 + 90],
				ap_ext_space_num2: [520 + 134 + 90, 82 + 90 + 90],
				ap_ext_space_num3: [520 + 134 + 90 + 90, 82 + 90 + 90]
			};
			var key, el;
			for (var j = 1; j < 10; j++) {
				key = 'ap_ext_space_num' + j;
				if (elem.id == key) {
					elem.style.backgroundImage = "url('" + ap_ext_space.imgURL + "')";
					elem.style.backgroundPosition = -pos[key][0] + 'px ' + -pos[key][1] + 'px';
				};
			};
		}, true);

		div.addEventListener("mouseout", function(e) {
			e.preventDefault();
			e.stopPropagation();

			var elem, evt = e ? e : event;
			if (evt.srcElement) {
				elem = evt.srcElement;
			} else if (evt.target) {
				elem = evt.target;
			};

			var key, el;
			for (var j = 1; j < 10; j++) {
				key = 'ap_ext_space_num' + j;
				if (elem.id == key) {
					elem.style.backgroundImage = "none";
				};
			};
		}, true);

		div.addEventListener("click", function(e) {
			e.preventDefault();
			e.stopPropagation();
			var elem, evt = e ? e : event;
			if (evt.srcElement) {
				elem = evt.srcElement;
			} else if (evt.target) {
				elem = evt.target;
			};
			ap_ext_space.clickHandler(elem);
		}, true);

		div.addEventListener("touchstart", function(e) {
			e.preventDefault();
			e.stopPropagation();
			var elem, evt = e ? e : event;
			if (evt.srcElement) {
				elem = evt.srcElement;
			} else if (evt.target) {
				elem = evt.target;
			};
			ap_ext_space.clickHandler(elem);
		}, true);

		div.addEventListener("touchend", function(e) {
			e.preventDefault();
		}, true);

		div.addEventListener("touchmove", function(e) {
			e.preventDefault();
		}, true);

		//     ( )
		ap_ext_space.menuPos();

	};
	console.log('all menus:', ap_ext_space.menus);
},


Si agrega un div OSD a un video como este: videoEl.parentNode.appendChild (div), aparecerá en la parte superior del video incluso en modo de pantalla completa. Solo queda centrarlo, o mejor dicho, hacerlo con todos los elementos del menú de bloque adjuntos a los elementos de video (tienen un tamaño de 520x410):



menuPos: function() {

	if (ap_ext_space.isFullScreen()) {

		var sc = ap_ext_space.scale;
		var iw = window.innerWidth,
			ih = window.innerHeight;
		var w = iw * sc;
		var h = w / 16 * 9;

		for (var i = 0; i < ap_ext_space.menus.length; i++) {
			ap_ext_space.menus[i].style.marginLeft = (iw - 520) / 2 + 'px';
			ap_ext_space.menus[i].style.marginTop = (-h - 410) / 2 + 'px';
		};

	} else {

		ap_ext_space.scale = 1;

		for (var i = 0; i < ap_ext_space.menus.length; i++) {
			ap_ext_space.menus[i].style.marginLeft = '0px';
			ap_ext_space.menus[i].style.marginTop = '0px';
		};
	};

},

isFullScreen: function() {
	return !!(document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement);
},


Por cierto, al final, decidí ocultar completamente el menú en el modo de ventana y permitir el control del tamaño del video solo en el modo de pantalla completa. En la ventana, no tiene sentido.



Manipuladores



Aquí, creo, todo está claro. En cada botón del menú en pantalla, se cuelgan controladores de clic, una carretilla y también presionando la combinación de teclas correspondiente para controlar el video incluso con el menú oculto. Los botones controlan los valores de escala: ap_ext_space.scale, ap_ext_space.scalew y ap_ext_space.scaleh, aumentando o disminuyendo estos valores, y luego cambiando el tamaño de cada elemento de video encontrado arriba de la siguiente manera:



var sc = ap_ext_space.scale;
var iw = window.innerWidth,
	ih = window.innerHeight;
var w = iw * sc;
var h = w / 16 * 9;

for (var i = 0; i < ap_ext_space.videos.length; i++) {
	el = ap_ext_space.videos[i];
	el.style.position = 'initial';
	el.style.width = (w) + 'px';
	el.style.height = (h) + 'px';
	el.style.marginLeft = -(w - iw) / 2 + 'px';
	el.style.marginTop = -(h - ih) / 2 + 'px';
	el.style.transform = 'scaleX(' + ap_ext_space.scalew + ') scaleY(' + ap_ext_space.scaleh + ')';
};


Además, también colgué en los controladores de eventos de video que buscaban, abortaban, pausaban, reproducían, reproducían, buscaban en cada elemento de video (en la función getVideos () anterior) una llamada a la única función que redibuja el menú en pantalla con recalcular sus coordenadas, ya que a veces se "va" con alguna acción del usuario. Hice lo mismo para el evento de cambio de tamaño de la ventana del navegador.



Espacio de nombres



En general, ¿qué tipo de ap_ext_space es este? El caso es que todas las funciones que se utilizan para redimensionar el video deben estar incrustadas en la página correspondiente (ya sea en la página principal o en el iframe). Así que simplemente combiné estas funciones y, junto con ellas, el fondo OSD base64 en un solo espacio de nombres. Todo esto se inyecta en el código de la pestaña del navegador actual desde el script de fondo de la siguiente manera:



var codeString = ap_ext_space_f.toString() + '; ap_ext_space_f(); ap_ext_space.init()';
chrome.tabs.executeScript({
	code: codeString
});

function ap_ext_space_f() {

	ap_ext_space = {

		init: function() {
			//...
		},

		//...
	};

};


Bueno, dentro de ap_ext_space, se activa una búsqueda de todos los iframes, luego todos los videos dentro de cada uno de ellos, se crea un menú en pantalla con controladores, y así sucesivamente.



Cómo utilizar



Reproduce el video. Haga clic en el icono de la extensión. Expande el video a pantalla completa. Ajuste la escala y la relación de aspecto. El menú se puede ocultar con el atajo de teclado ctrl + 0.



Salir



La extensión se llama Browser Video Tuner, es gratuita y actualmente está disponible en las tiendas de extensiones de Chrome y Firefox. Además, por supuesto, se puede instalar en todos los navegadores compatibles con Chrome, como Opera, Yandex Browser, etc. Cabe señalar que la extensión no funciona en todos los sitios de videos. Cuando el acceso a los elementos del iframe desde el exterior está protegido por una política de seguridad, simplemente no se encontrará ningún video. Y una advertencia correspondiente sobre esto aparecerá en la consola. En este caso, el menú simplemente no se mostrará. Pero en Youtube y en muchos cines online todo funciona.



Se notaron problemas menores con algunos navegadores. Por ejemplo, en el navegador Yandex, la imagen mostrada de alguna manera se deteriora y se parece a un jpeg muy comprimido. Pero esto no afecta la funcionalidad de ninguna manera.





Estaba buscando una forma de mostrar el menú en pantalla en modo de pantalla completa simplemente encima de todo el documento sin incrustarlo dentro de iframes, para no depender de la política de seguridad del navegador e intentar controlar el tamaño de todo el documento como un todo, pero hasta ahora no lo he logrado. Creo que en el futuro la expansión se complementará con nuevas funciones.



All Articles