[Разработка веб-сайтов, JavaScript, HTML, Расширения для браузеров] Растянуть видео в браузере

Автор Сообщение
news_bot ®

Стаж: 6 лет 4 месяца
Сообщений: 27286

Создавать темы news_bot ® написал(а)
18-Ноя-2020 23:31


Очень часто видео в онлайн-кинотеатрах имеет соотношение сторон, отличное от соотношения сторон монитора. Поэтому иногда возникает желание сделать общий масштаб чуть крупнее за счет небольшой обрезки по краям. Или вовсе — вписать изображение в размеры экрана по меньшей стороне картинки. Особенно это актуально для маленьких экранов, а также, для старых мониторов 4:3. Я уж молчу о том, что оригинальное видео может быть вообще растянуто по одной из сторон и это необходимо как-то исправить.
Для решения данной проблемы я задумал написать браузерное расширение под Chrome и Firefox. Идея такая: при проигрывании любого браузерного видео вызывается экранное меню, которое позволяет произвольно менять масштаб и соотношение сторон картинки.
iframe
Первая проблема, с которой я столкнулся, заключается в том, что видео на сайтах вовсе не обязательно располагается на основной странице, а может быть запрятано глубоко во вложенных iframe. Я решил просканировать все iframe-элементы и найти в каждом из них все элементы video. Кстати, этим решается и другая проблема — никогда не знаешь, где рекламное видео, а где сам фильм. Давайте для начала найдем их всех.
Функция getVideos вызывает рекурсивно сама себя до тех пор, пока в последнем iframe не будут найдены все элементы video. Все видео добавляются в массив ap_ext_space.videos. В качестве входного параметра функция getVideos принимает документ текущей страницы. При первом запуске берется главный документ. По ходу еще на каждое видео навешиваются обработчики, но об этом ниже.
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);
    };
  };
},

Экранное меню

Хорошо, список всех видео-элементов у нас есть. Теперь как отобразить экранное меню? Просто добавим его блочный элемент к каждому видео. Да, тогда у нас будет много экранных меню, но в один момент времени все равно отображается только одно видео: один из рекламных роликов либо сам фильм. И меню вместе с ними будет показываться только одно.
Видео, как правило, располагается в родительском div-элементе. Добавим к нему в качестве последнего child наш div-элемент меню. Таким образом, экранное меню всегда будет отображаться поверх видео.
Изображение экранного меню закодируем в base64 в формате png с прозрачным альфа-каналом и поместим в ap_ext_space.imgUR, так как браузер не позволит нам подгрузить изображение с другого домена. Создание меню для каждого видео:
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);
},

Если добавлять div-элемент экранного меню к видео таким образом: videoEl.parentNode.appendChild(div), то он будет отображаться поверх видео даже в полноэкранном режиме. Осталось только отцентрировать его, а точнее, сделать это со всеми привязанными к видео-элементам блочными элементами меню (они имеют размер 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);
},

Кстати, в итоге я решил вообще скрыть меню в оконном режиме и разрешить управление размерами видео только в полноэкранном режиме. В оконном в этом нет смысла.
Обработчики
Здесь, думаю, и так все понятно. На каждую кнопку экранного меню навешены обработчики клика, тача и еще — нажатия соответствующего сочетания клавиш, чтобы управлять видео даже при скрытом меню. Кнопки управляют величинами масштабов: ap_ext_space.scale, ap_ext_space.scalew и ap_ext_space.scaleh, увеличивая или уменьшая эти значения, а затем изменяется размер каждого найденного выше видео-элемента следующим образом:
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 + ')';
};

Кроме того, я также повесил на обработчики событий видео seeked, abort, pause, play, playing, seeked на каждый video-элемент (в функции getVideos() выше) вызов единственной функции, которая перерисовывает экранное меню с пересчетом его координат, так как иногда оно «уезжает» при некоторых действиях пользователя. То же сделал и для события изменения размеров окна браузера.
Пространство имен
Вообще, что это за ap_ext_space такой? Дело в том, что все функции, которые используются для изменения размера видео, должны быть внедрены в соответствующую страницу (либо в основную, либо — в iframe). Поэтому я просто объединил эти функции, а также, вместе с ними — и фон экранного меню в формате base64 в единое пространстве имен. Инжектируется все это в код текущей вкладки браузера из бэкграунд-скрипта следующим образом:
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() {
      //...
    },
    //...
  };
};

Ну а внутри ap_ext_space уже срабатывает поиск всех iframe, затем — всех video внутри каждого из них, строится экранное меню с обработчиками и так далее.
Как пользоваться
Запустить видео. Кликнуть на иконку расширения. Развернуть видео на полный экран. Настраивать масштаб и соотношение сторон. Меню можно скрыть сочетанием клавиш ctrl+0.
Итог
Расширение называется Browser Video Tuner, оно бесплатное и в данный момент доступно в магазинах расширений Chrome и Firefox. Также, его, естественно, можно установить и во все Chrome-совместимые браузеры типа Opera, Yandex Browser и так далее. Стоит отметить, что расширение срабатывает не на всех сайтах с видео. Там, где доступ к iframe-элементам извне защищен политикой безопасности, то ни одного видео просто не будет найдено. И в консоли появится соответствующее предупреждение об этом. Меню в этом случае просто не отобразится. Но на Youtube и на многих онлайн-кинотеатрах все работает.
С некоторыми браузерами замечены небольшие проблемы. Например, в Yandex Browser выводимое изображение как-то портится и напоминает сильно пережатый jpeg. Но на функциональность это никак не влияет

Я искал способ выводить экранное меню в полноэкранном режиме просто поверх всего документа без внедрения его внутрь iframe-ов, чтобы не зависеть от политики безопасности браузера, и попробовать управлять размерами всего документа в целом, но пока мне это не удалось. Думаю, в дальнейшем расширение будет дополняться новыми функциями.
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_razrabotka_vebsajtov (Разработка веб-сайтов), #_javascript, #_html, #_rasshirenija_dlja_brauzerov (Расширения для браузеров), #_javascript, #_rasshirenie_dlja_chrome (расширение для Chrome), #_rasshirenie_dlja_firefox (расширение для Firefox), #_video (видео), #_html5video (html5-видео), #_razrabotka_vebsajtov (
Разработка веб-сайтов
)
, #_javascript, #_html, #_rasshirenija_dlja_brauzerov (
Расширения для браузеров
)
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 18-Июн 18:58
Часовой пояс: UTC + 5