Google Drive Video Player for Synchtube

Play Google Drive videos on Synchtube

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name Google Drive Video Player for Synchtube
// @namespace gdcytube
// @description Play Google Drive videos on Synchtube
// @include http://synchtu.be/r/*
// @include https://synchtu.be/r/*
// @grant unsafeWindow
// @grant GM_xmlhttpRequest
// @connect docs.google.com
// @run-at document-end
// @version 1.1.0
// ==/UserScript==

try {
    function debug(message) {
        if (!unsafeWindow.enableCyTubeGoogleDriveUserscriptDebug) {
            return;
        }

        try {
            unsafeWindow.console.log(message);
        } catch (error) {
            unsafeWindow.console.error(error);
        }
    }

    var ITAG_QMAP = {
        37: 1080,
        46: 1080,
        22: 720,
        45: 720,
        59: 480,
        44: 480,
        35: 480,
        18: 360,
        43: 360,
        34: 360
    };

    var ITAG_CMAP = {
        43: 'video/webm',
        44: 'video/webm',
        45: 'video/webm',
        46: 'video/webm',
        18: 'video/mp4',
        22: 'video/mp4',
        37: 'video/mp4',
        59: 'video/mp4',
        35: 'video/flv',
        34: 'video/flv'
    };

    function getVideoInfo(id, cb) {
        var url = 'https://docs.google.com/file/d/' + id + '/get_video_info';
        debug('Fetching ' + url);

        GM_xmlhttpRequest({
            method: 'GET',
            url: url,
            onload: function (res) {
                try {
                    debug('Got response ' + res.responseText);
                    var data = {};
                    var error;
                    res.responseText.split('&').forEach(function (kv) {
                        var pair = kv.split('=');
                        data[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
                    });

                    if (data.status === 'fail') {
                        error = new Error('Google Docs request failed: ' +
                                'metadata indicated status=fail');
                        error.response = res.responseText;
                        error.reason = 'RESPONSE_STATUS_FAIL';
                        return cb(error);
                    }

                    if (!data.fmt_stream_map) {
                        error = new Error('Google Docs request failed: ' +
                                'metadata lookup returned no valid links');
                        error.response = res.responseText;
                        error.reason = 'MISSING_LINKS';
                        return cb(error);
                    }

                    data.links = {};
                    data.fmt_stream_map.split(',').forEach(function (item) {
                        var pair = item.split('|');
                        data.links[pair[0]] = pair[1];
                    });
                    data.videoMap = mapLinks(data.links);

                    cb(null, data);
                } catch (error) {
                    unsafeWindow.console.error(error);
                }
            },

            onerror: function () {
                var error = new Error('Google Docs request failed: ' +
                        'metadata lookup HTTP request failed');
                error.reason = 'HTTP_ONERROR';
                return cb(error);
            }
        });
    }

    function mapLinks(links) {
        var videos = {
            1080: [],
            720: [],
            480: [],
            360: []
        };

        Object.keys(links).forEach(function (itag) {
            itag = parseInt(itag, 10);
            if (!ITAG_QMAP.hasOwnProperty(itag)) {
                return;
            }

            videos[ITAG_QMAP[itag]].push({
                itag: itag,
                contentType: ITAG_CMAP[itag],
                link: links[itag]
            });
        });

        return videos;
    }

    /*
     * Greasemonkey 2.0 has this wonderful sandbox that attempts
     * to prevent script developers from shooting themselves in
     * the foot by removing the trigger from the gun, i.e. it's
     * impossible to cross the boundary between the browser JS VM
     * and the privileged sandbox that can run GM_xmlhttpRequest().
     *
     * So in this case, we have to resort to polling a special
     * variable to see if getGoogleDriveMetadata needs to be called
     * and deliver the result into another special variable that is
     * being polled on the browser side.
     */

    /*
     * Browser side function -- sets gdUserscript.pollID to the
     * ID of the Drive video to be queried and polls
     * gdUserscript.pollResult for the result.
     */
    function getGoogleDriveMetadata_GM(id, callback) {
        debug('Setting GD poll ID to ' + id);
        unsafeWindow.gdUserscript.pollID = id;
        var tries = 0;
        var i = setInterval(function () {
            if (unsafeWindow.gdUserscript.pollResult) {
                debug('Got result');
                clearInterval(i);
                var result = unsafeWindow.gdUserscript.pollResult;
                unsafeWindow.gdUserscript.pollResult = null;
                callback(result.error, result.result);
            } else if (++tries > 100) {
                // Took longer than 10 seconds, give up
                clearInterval(i);
            }
        }, 100);
    }

    /*
     * Sandbox side function -- polls gdUserscript.pollID for
     * the ID of a Drive video to be queried, looks up the
     * metadata, and stores it in gdUserscript.pollResult
     */
    function setupGDPoll() {
        unsafeWindow.gdUserscript = cloneInto({}, unsafeWindow);
        var pollInterval = setInterval(function () {
            if (unsafeWindow.gdUserscript.pollID) {
                var id = unsafeWindow.gdUserscript.pollID;
                unsafeWindow.gdUserscript.pollID = null;
                debug('Polled and got ' + id);
                getVideoInfo(id, function (error, data) {
                    unsafeWindow.gdUserscript.pollResult = cloneInto({
                        error: error,
                        result: data
                    }, unsafeWindow);
                });
            }
        }, 1000);
    }

    function isRunningTampermonkey() {
        try {
            return GM_info.scriptHandler === 'Tampermonkey';
        } catch (error) {
            return false;
        }
    }

    if (isRunningTampermonkey()) {
        unsafeWindow.getGoogleDriveMetadata = getVideoInfo;
    } else {
        debug('Using non-TM polling workaround');
        unsafeWindow.getGoogleDriveMetadata = exportFunction(
                getGoogleDriveMetadata_GM, unsafeWindow);
        setupGDPoll();
    }

    unsafeWindow.console.log('Initialized userscript Google Drive player');
    unsafeWindow.hasDriveUserscript = true;
} catch (error) {
    unsafeWindow.console.error(error);
}