From 4feee02e33a3d3741b2d1cc605d061cc91d8e2db Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Mon, 15 Aug 2016 21:00:56 -0700 Subject: [PATCH] Add initial userscript --- build-player.js | 1 + gdrive-userscript/cytube-google-drive.user.js | 155 ++++++++++++++++++ gdrive-userscript/generate-userscript.js | 19 +++ package.json | 3 +- player/gdrive-player.coffee | 6 + player/update.coffee | 4 +- www/js/data.js | 3 + www/js/player.js | 20 ++- 8 files changed, 205 insertions(+), 6 deletions(-) create mode 100644 gdrive-userscript/cytube-google-drive.user.js create mode 100644 gdrive-userscript/generate-userscript.js create mode 100644 player/gdrive-player.coffee diff --git a/build-player.js b/build-player.js index 9e233106..8ea29dc8 100644 --- a/build-player.js +++ b/build-player.js @@ -8,6 +8,7 @@ var order = [ 'youtube.coffee', 'dailymotion.coffee', 'videojs.coffee', + 'gdrive-player.coffee', 'raw-file.coffee', 'soundcloud.coffee', 'embed.coffee', diff --git a/gdrive-userscript/cytube-google-drive.user.js b/gdrive-userscript/cytube-google-drive.user.js new file mode 100644 index 00000000..6add91fa --- /dev/null +++ b/gdrive-userscript/cytube-google-drive.user.js @@ -0,0 +1,155 @@ +// ==UserScript== +// @name Google Drive Video Player for {SITENAME} +// @namespace gdcytube +// @description Play Google Drive videos on {SITENAME} +// {INCLUDE_BLOCK} +// @grant unsafeWindow +// @grant GM_xmlhttpRequest +// @connect docs.google.com +// @run-at document-end +// @version 1.0.0 +// ==/UserScript== + +(function () { + if (!unsafeWindow.enableCyTubeGoogleDriveUserscript) { + return; + } + + function debug(message) { + if (!unsafeWindow.enableCyTubeGoogleDriveUserscriptDebug) { + return; + } + + unsafeWindow.console.log.apply(unsafeWindow.console, arguments); + } + + 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) { + var data = {}; + res.responseText.split('&').forEach(function (kv) { + var pair = kv.split('='); + data[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]); + }); + + if (data.status === 'fail') { + var 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) { + var 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]; + }); + + cb(null, data); + }, + + 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; + } + + function GoogleDrivePlayer(data) { + if (!(this instanceof GoogleDrivePlayer)) { + return new GoogleDrivePlayer(data); + } + + this.setMediaProperties(data); + this.load(data); + } + + GoogleDrivePlayer.prototype = Object.create(unsafeWindow.VideoJSPlayer.prototype); + + GoogleDrivePlayer.prototype.load = function (data) { + var self = this; + getVideoInfo(data.id, function (err, videoData) { + if (err) { + debug(err); + var alertBox = unsafeWindow.document.createElement('div'); + alertBox.className = 'alert alert-danger'; + alertBox.textContent = err.message; + document.getElementById('ytapiplayer').appendChild(alertBox); + return; + } + + debug('Retrieved links: ' + JSON.stringify(videoData.links)); + data.meta.direct = mapLinks(videoData.links); + unsafeWindow.VideoJSPlayer.prototype.loadPlayer.call(self, data); + }); + }; + + unsafeWindow.GoogleDrivePlayer = GoogleDrivePlayer; + unsafeWindow.console.log('Initialized userscript Google Drive player'); +})(); diff --git a/gdrive-userscript/generate-userscript.js b/gdrive-userscript/generate-userscript.js new file mode 100644 index 00000000..33b297e5 --- /dev/null +++ b/gdrive-userscript/generate-userscript.js @@ -0,0 +1,19 @@ +var fs = require('fs'); +var path = require('path'); + +var sitename = process.argv[2]; +var includes = process.argv.slice(3).map(function (include) { + return '// @include ' + include; +}).join('\n'); + +var lines = String(fs.readFileSync( + path.resolve(__dirname, 'cytube-google-drive.user.js'))).split('\n'); +lines.forEach(function (line) { + if (line.match(/\{INCLUDE_BLOCK\}/)) { + console.log(includes); + } else if (line.match(/\{SITENAME\}/)) { + console.log(line.replace(/\{SITENAME\}/, sitename)); + } else { + console.log(line); + } +}); diff --git a/package.json b/package.json index 7643b71c..9a65db52 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,8 @@ "build-player": "$npm_node_execpath build-player.js", "build-server": "babel -D --source-maps --loose es6.destructuring,es6.forOf --out-dir lib/ src/", "postinstall": "./postinstall.sh", - "server-dev": "babel -D --watch --source-maps --loose es6.destructuring,es6.forOf --out-dir lib/ src/" + "server-dev": "babel -D --watch --source-maps --loose es6.destructuring,es6.forOf --out-dir lib/ src/", + "generate-userscript": "$npm_node_execpath gdrive-userscript/generate-userscript $@ > www/js/cytube-google-drive.user.js" }, "devDependencies": { "coffee-script": "^1.9.2" diff --git a/player/gdrive-player.coffee b/player/gdrive-player.coffee new file mode 100644 index 00000000..34c8565f --- /dev/null +++ b/player/gdrive-player.coffee @@ -0,0 +1,6 @@ +window.GoogleDrivePlayer = class GoogleDrivePlayer extends VideoJSPlayer + constructor: (data) -> + if not (this instanceof GoogleDrivePlayer) + return new GoogleDrivePlayer(data) + + super(data) diff --git a/player/update.coffee b/player/update.coffee index 5504a14d..20b528cb 100644 --- a/player/update.coffee +++ b/player/update.coffee @@ -2,7 +2,7 @@ TYPE_MAP = yt: YouTubePlayer vi: VimeoPlayer dm: DailymotionPlayer - gd: GoogleDriveYouTubePlayer + gd: GoogleDrivePlayer gp: VideoJSPlayer fi: FilePlayer jw: FilePlayer @@ -33,7 +33,7 @@ window.loadMediaPlayer = (data) -> else if data.type is 'gd' try if data.meta.html5hack - window.PLAYER = new VideoJSPlayer(data) + window.PLAYER = new window.GoogleDrivePlayer(data) else window.PLAYER = new GoogleDriveYouTubePlayer(data) catch e diff --git a/www/js/data.js b/www/js/data.js index da23942b..8a38b061 100644 --- a/www/js/data.js +++ b/www/js/data.js @@ -216,3 +216,6 @@ function eraseCookie(name) { /* to be implemented in callbacks.js */ function setupCallbacks() { } + +window.enableCyTubeGoogleDriveUserscript = true; +window.enableCyTubeGoogleDriveUserscriptDebug = true; diff --git a/www/js/player.js b/www/js/player.js index ef333654..395dd53c 100644 --- a/www/js/player.js +++ b/www/js/player.js @@ -1,5 +1,5 @@ (function() { - var CUSTOM_EMBED_WARNING, CustomEmbedPlayer, DEFAULT_ERROR, DailymotionPlayer, EmbedPlayer, FilePlayer, GoogleDriveYouTubePlayer, HITBOX_ERROR, HLSPlayer, HitboxPlayer, ImgurPlayer, LivestreamPlayer, Player, RTMPPlayer, SoundCloudPlayer, TYPE_MAP, TwitchPlayer, USTREAM_ERROR, UstreamPlayer, VideoJSPlayer, VimeoPlayer, YouTubePlayer, codecToMimeType, genParam, sortSources, + var CUSTOM_EMBED_WARNING, CustomEmbedPlayer, DEFAULT_ERROR, DailymotionPlayer, EmbedPlayer, FilePlayer, GoogleDrivePlayer, GoogleDriveYouTubePlayer, HITBOX_ERROR, HLSPlayer, HitboxPlayer, ImgurPlayer, LivestreamPlayer, Player, RTMPPlayer, SoundCloudPlayer, TYPE_MAP, TwitchPlayer, USTREAM_ERROR, UstreamPlayer, VideoJSPlayer, VimeoPlayer, YouTubePlayer, codecToMimeType, genParam, sortSources, extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, hasProp = {}.hasOwnProperty; @@ -666,6 +666,20 @@ })(Player); + window.GoogleDrivePlayer = GoogleDrivePlayer = (function(superClass) { + extend(GoogleDrivePlayer, superClass); + + function GoogleDrivePlayer(data) { + if (!(this instanceof GoogleDrivePlayer)) { + return new GoogleDrivePlayer(data); + } + GoogleDrivePlayer.__super__.constructor.call(this, data); + } + + return GoogleDrivePlayer; + + })(VideoJSPlayer); + codecToMimeType = function(codec) { switch (codec) { case 'mov/h264': @@ -1308,7 +1322,7 @@ yt: YouTubePlayer, vi: VimeoPlayer, dm: DailymotionPlayer, - gd: GoogleDriveYouTubePlayer, + gd: GoogleDrivePlayer, gp: VideoJSPlayer, fi: FilePlayer, jw: FilePlayer, @@ -1345,7 +1359,7 @@ } else if (data.type === 'gd') { try { if (data.meta.html5hack) { - return window.PLAYER = new VideoJSPlayer(data); + return window.PLAYER = new window.GoogleDrivePlayer(data); } else { return window.PLAYER = new GoogleDriveYouTubePlayer(data); }