Это сайт — моя персональная записная книжка. Интересна мне, по большей части, история, своя жизнь и немного программирование.

UserJS для воспроизведения H.264 в «Опере»

Существует плагин «DivX web player», который отлично подменяет тег «VIDEO», в случае, если должен быть использован кодек H.264, но браузер его не поддерживает.

К сожалению, в «Опере» он почему-то не работает. Я сделал UserJS, который, пользуясь этим плагином, подменяет видеотег, если у него указан тип «video/mp4». Все атрибуты он не переносит (я просто пока не столкнулся с тем, что это нужно), буду добавлять, если столкнусь. Кроме того, я подменяю метод «canPlayType», чтобы показать, что браузер H.264 поддерживает.

Самая засада — это тег «VIDEO» без размеров. Мне для поддержки этого приходтся запускать проигрывание и когда становятся доступны медиаданные, останавливать воспроизведение и переписывать размеры видео.

// ==UserScript==
// @name        XVid patch
// @author      Evgeny Stepanischev aka Bolk
// @version     1.00
// @namespace   /files/opera-xvid-patch.js
// @modified    2012-04-14
// @include     http://*
// ==/UserScript==

!function () {
    var htmlif = function (attr, attrname) {
        return attr ? attrname + '="' + attr + '" ' : '';
    }

    var patchtag = function (o, src) {
        var html = '<embed type="video/divx" '+
        'custommode="none" ';

        if (o.preload == 'auto') html += 'bufferingMode="full" '; else
        if (o.preload == 'none') html += 'bufferingMode="null" ';

        html += htmlif(o.poster, 'previewImage')+
                htmlif(o.id, 'id')+
                htmlif(o.name, 'name')+
                htmlif(src || o.src, 'src')+
                htmlif(o.controls ? 'full' : 'null', 'mode')+
                htmlif(o.width, 'width')+
                htmlif(o.height, 'height');

        if (!o.height || !o.width) {
            html += 'onloadedmetadata="this.width=this.videoWidth;this.height=this.videoHeight" ';
            html += htmlif(!o.autoplay && 'if (!this._sized) {this.pause(); this.currentTime=0; this._sized=1}', 'onplay');

            html += 'autoPlay=true ';
        } else {
            html += htmlif(o.autoplay ? 'true' : 'false', 'autoPlay');
        }

        html += 'pluginspage="http://go.divx.com/plugin/download/"></embed>';

        o.outerHTML = html;
    };

    var patch = function () {
        window.opera.addEventListener('BeforeScript', function() {

            with (window.HTMLVideoElement)
            if (prototype._canPlayType === undefined) {
                prototype._canPlayType = prototype.canPlayType;

                prototype.canPlayType = function (type) {
                    if (type.substr(0, 9) == 'video/mp4') {
                        return 'probably';
                    }

                    return this._canPlayType(type);
                }
            }

            !function() {
                var videos = document.getElementsByTagName('VIDEO');

                for (var i = 0, vl = videos.length; i<vl; i++) {
                    if (!videos[i]) continue;

                    if (videos[i].type && videos[i].type.substr(0, 9) == 'video/mp4') {

                    } else {
                        var sources = videos[i].getElementsByTagName('source');

                        var foundSupported = false, foundMP4 = false;

                        for (var j = 0, sl = sources.length; j < sl; j++) {
                            if (videos[i]._canPlayType(sources[j].type) !== '') {
                                foundSupported = true;
                                break;
                            }

                            if (sources[j].type.substr(0, 9) == 'video/mp4') {
                                foundMP4 = sources[j].src;
                            }
                        }

                        if (!foundSupported && foundMP4) {
                            patchtag(videos[i], foundMP4);
                        }
                    }
                }
            }();
        });
    };

    for (var i = 0, l = navigator.plugins.length; i < l; i++) {
        if (navigator.plugins[i].filename == 'DivXBrowserPlugin.plugin') {
            return patch();
        }
    }
}();

Добавлено: в комментариях справдливо указали на недостаток — перехватывать надо не только BeforeScript. Исправлять не буду до первого реального случая, когда где-то этого события не будет достаточно.

13 комментариев
Ivan 2012

если должен использован кодек H.264

«быть» пропущено

Ivan 2012

Имеется ввиду формал «был», конечно. :)

Морозов (morozov.livejournal.com) 2012

А что означает конструкция !function(){}()? Та же ли это самовызывающаяся анонимная функция, что и (function(){})() и в чём смысл/особенность такой формы записи?

GreLI 2012

Комментарий для morozov.livejournal.com:

Да, это тоже самое. Фокус в восклицательном знаке: благодаря ему выходит функциональное выражение, а не определение функции. На один байт меньше скобок, и за ними не надо следить.

Fulcrum (fulc.ru) 2012

«для воспроизведения»

Евгений Степанищев (bolknote.ru) 2012

Ошибки поправил, спасибо всем!

greli (greli.livejournal.com) 2012

Есть некоторые непонятки:

  • почему используется BeforeScript? Если скрипты есть только в начале страницы, не факт, что вообще будут найдены теги `VIDEO`.
  • почему prototype._canPlayType определяется после того, как использован в анонимной функции? Не приведёт ли это к ошибке, когда найден тег при первом проходе?
  • http://bolknote.ru/files/opera-xvid-patch.js  — ошибка 404.
Евгений Степанищев (bolknote.ru) 2012

Комментарий для greli.livejournal.com:

Я нечасто пишу UserJS.

почему используется BeforeScript? Если скрипты есть только в начале страницы, не факт, что вообще будут найдены теги `VIDEO`

Глянул в доку, я думал это выполяется перед каждым выполнением скрипта (например, из событий). Надо переделать…

почему prototype._canPlayType определяется после того, как использован в анонимной функции? Не приведёт ли это к ошибке, когда найден тег при первом проходе?

Нет, конечно. Потому что событие не дёрнется, пока основной JS не выполнится до конца. По сути _canPlayType определяется не до, а после. Сначала выполняется привязка события.

http://bolknote.ru/files/opera-xvid-patch.js  — ошибка 404.

Это же неймспейс, а не УРЛ.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для greli.livejournal.com:

Я пока решил ничего не трогать, до реальных проблем. Всё-таки это не решение, а костыль в какой-то мере. Надеюсь, что авторы DivX web player обратят внимание на «Оперу» и всё будет хорошо.

Treg 2012

Комментарий для Евгения Степанищева:

Нет, конечно. Потому что событие не дёрнется, пока основной JS не выполнится до конца. По сути _canPlayType определяется не до, а после. Сначала выполняется привязка события.

Определение _canPlayType сделано внутри события и не произойдет до его вызова. Соответственно при первом вызове будет ошибка. Необходимо «with (window.HTMLVideoElement) ...» вынести за пределы определения события «window.opera.addEventListener(’BeforeScript’, function() { ...».

Евгений Степанищев (bolknote.ru) 2012

Комментарий для Treg:

Да, точно, надо просто поднять перед функцией. Видимо, правил код потом.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для Treg:

Поправил, спасибо!

Ingvar Reverser (http://rreverser.com/) 2012

Комментарий для Евгения Степанищева:

Двумя годами ранее тоже правил ихний скрипт для использования под Оперой и заодно добавил поддержку для Windows Media компонента — http://habrahabr.ru/post/110794/ :)