Stretch video in browser





Muitas vezes, o vídeo em cinemas online tem uma proporção de aspecto diferente da do monitor. Portanto, às vezes há um desejo de tornar a escala geral um pouco maior cortando ligeiramente nas bordas. Ou ainda - ajuste a imagem ao tamanho da tela no lado menor da imagem. Isso é especialmente verdadeiro para telas pequenas, bem como para monitores 4: 3 mais antigos. Já estou calado sobre o fato de que o vídeo original pode ser geralmente esticado para um lado e isso deve ser corrigido de alguma forma.



Para resolver este problema, decidi escrever uma extensão de navegador para Chrome e Firefox. A ideia é esta: ao reproduzir qualquer vídeo do navegador, um menu na tela é chamado, o que permite alterar arbitrariamente a escala e a proporção da imagem.



iframe



O primeiro problema que encontrei é que os vídeos em sites não estão necessariamente localizados na página principal, mas podem estar ocultos em iframes aninhados. Decidi verificar todos os iframes e encontrar todos os elementos de vídeo em cada um. A propósito, isso também resolve outro problema - você nunca sabe onde está o vídeo publicitário e onde está o próprio filme. Vamos encontrá-los todos primeiro.



A função getVideos chama a si mesma recursivamente até que todos os elementos de vídeo sejam encontrados no último iframe. Todos os vídeos são adicionados ao array ap_ext_space.videos. A função getVideos usa o documento da página atual como um parâmetro de entrada. No primeiro lançamento, o documento principal é obtido. Ao longo do caminho, manipuladores são adicionados a cada vídeo, mas mais sobre isso a seguir.



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);
		};
	};
},


Menu OSD





Ok, temos uma lista de todos os elementos de vídeo. Agora, como exibir o menu OSD? Vamos apenas adicionar seu elemento de bloco a cada vídeo. Sim, teremos muitos menus na tela, mas em um determinado momento apenas um vídeo é exibido: um dos comerciais ou o próprio filme. E apenas um menu será mostrado com eles.



O vídeo geralmente está localizado no div pai. Vamos adicionar nosso elemento div de menu a ele como o último filho. Assim, o OSD sempre será exibido sobre o vídeo.



Vamos codificar a imagem OSD em base64 no formato png com um canal alfa transparente e colocá-la em ap_ext_space.imgUR, pois o navegador não nos permitirá carregar a imagem de outro domínio. Crie um menu para cada vídeo:



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);
},


Se você adicionar um OSD div a um vídeo como este: videoEl.parentNode.appendChild (div), ele aparecerá na parte superior do vídeo, mesmo no modo de tela inteira. Resta apenas centralizá-lo, ou melhor, fazê-lo com todos os itens do menu do bloco anexados aos elementos de vídeo (eles têm um tamanho 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);
},


A propĂłsito, no final, decidi ocultar completamente o menu no modo de janela e permitir o controle do tamanho do vĂ­deo apenas no modo de tela inteira. Na janela, nĂŁo faz sentido.



Handlers



Aqui, eu acho, tudo está claro. Em cada botão do menu na tela, manipuladores de clique, um carrinho de mão e também pressionando a combinação de teclas correspondente são pendurados para controlar o vídeo, mesmo com o menu oculto. Os botões controlam os valores de escala: ap_ext_space.scale, ap_ext_space.scalew e ap_ext_space.scaleh, aumentando ou diminuindo esses valores e, em seguida, redimensionando cada elemento de vídeo encontrado acima da seguinte maneira:



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 + ')';
};


Além disso, também desliguei os manipuladores de eventos de vídeo seeked, abort, pause, play, playing, seeked para cada elemento de vídeo (na função getVideos () acima) uma chamada para a única função que redesenha o menu na tela com recalculando suas coordenadas, porque às vezes ele "sai" com alguma ação do usuário. Fiz o mesmo para o evento de redimensionamento da janela do navegador.



Namespace



Em geral, que tipo de ap_ext_space é esse? O fato é que todas as funções que servem para redimensionar o vídeo devem estar embutidas na página correspondente (seja na página principal ou no iframe). Então, eu apenas combinei essas funções, e com elas o fundo OSD de base64, em um único namespace. Tudo isso é injetado no código da guia do navegador atual a partir do script de plano de fundo da seguinte maneira:



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() {
			//...
		},

		//...
	};

};


Bem, dentro de ap_ext_space, uma busca por todos os iframes Ă© acionada, entĂŁo todos os vĂ­deos dentro de cada um deles, um menu na tela com manipuladores Ă© construĂ­do, e assim por diante.



Como usar



Reproduza o vídeo. Clique no ícone da extensão. Expanda o vídeo para tela inteira. Ajuste a escala e a proporção da imagem. O menu pode ser oculto com o atalho de teclado ctrl + 0.



Resultado



A extensão é chamada de Browser Video Tuner, é gratuita e está atualmente disponível nas lojas de extensões do Chrome e do Firefox. Além disso, é claro, ele pode ser instalado em todos os navegadores compatíveis com o Chrome, como Opera, Yandex Browser e assim por diante. Deve-se notar que a extensão não funciona em todos os sites de vídeo. Onde o acesso externo aos elementos iframe for protegido pela política de segurança, nenhum vídeo será simplesmente encontrado. E um aviso correspondente sobre isso aparecerá no console. Nesse caso, o menu simplesmente não será exibido. Mas no Youtube e em muitos cinemas online tudo funciona.



Pequenos problemas foram observados em alguns navegadores. Por exemplo, no Yandex Browser, a imagem exibida de alguma forma se deteriora e se assemelha a um JPEG fortemente compactado. Mas isso nĂŁo afeta a funcionalidade de forma alguma.





Eu estava procurando uma maneira de exibir o menu na tela em modo de tela inteira simplesmente sobre todo o documento sem incorporá-lo em iframes, para não depender da política de segurança do navegador e tentar controlar o tamanho de todo o documento como um todo, mas até agora não consegui. Acho que no futuro a expansão será complementada com novas funções.



All Articles