diff --git a/NEWS.md b/NEWS.md index cc4eea5e..34dd755d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,16 @@ +2016-08-23 +========== + +A few weeks ago, the previous Google Drive player stopped working. This is +nothing new; Google Drive has consistently broken a few times a year ever since +support for it was added. However, it's becoming increasingly difficult and +complicated to provide good support for Google Drive, so I've made the decision +to phase out the native player and require a userscript for it, in order to +bypass CORS and allow each browser to request the video stream itself. + +See [the updated documentation](docs/gdrive-userscript-serveradmins.md) for +details on how to enable this for your users. + 2016-04-27 ========== diff --git a/docs/gdrive-userscript-serveradmins.md b/docs/gdrive-userscript-serveradmins.md new file mode 100644 index 00000000..3f4b19a6 --- /dev/null +++ b/docs/gdrive-userscript-serveradmins.md @@ -0,0 +1,24 @@ +# Google Drive Userscript Setup + +In response to increasing difficulty and complexity of maintaining Google Drive +support, the native player is being phased out in favor of requiring a +userscript to allow each client to fetch the video stream links for themselves. +Users will be prompted with a link to `/google_drive_userscript`, which explains +the situation and instructs how to install the userscript. + +As a server admin, you must generate the userscript from the template by using +the following command: + +```sh +npm run generate-userscript [...] +``` + +The first argument is the site name as it will appear in the userscript title. +The remaining arguments are the URL patterns on which the script will run. For +example, for cytu.be I use: + +```sh +npm run generate-userscript CyTube http://cytu.be/r/* https://cytu.be/r/* +``` + +This will generate `www/js/cytube-google-drive.user.js`. diff --git a/package.json b/package.json index 9a65db52..698ae34c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Calvin Montgomery", "name": "CyTube", "description": "Online media synchronizer and chat", - "version": "3.19.0", + "version": "3.20.0", "repository": { "url": "http://github.com/calzoneman/sync" }, diff --git a/player/gdrive-youtube.coffee b/player/gdrive-youtube.coffee index c7461c03..bb463150 100644 --- a/player/gdrive-youtube.coffee +++ b/player/gdrive-youtube.coffee @@ -7,6 +7,7 @@ window.GoogleDriveYouTubePlayer = class GoogleDriveYouTubePlayer extends Player @init(data) init: (data) -> + window.promptToInstallDriveUserscript() embed = $('').attr( type: 'application/x-shockwave-flash' src: "https://www.youtube.com/get_player?docid=#{data.id}&ps=docs\ @@ -102,3 +103,30 @@ window.GoogleDriveYouTubePlayer = class GoogleDriveYouTubePlayer extends Player cb(@yt.getVolume() / 100) else cb(VOLUME) + +window.promptToInstallDriveUserscript = -> + if document.getElementById('prompt-install-drive-userscript') + return + alertBox = document.createElement('div') + alertBox.id = 'prompt-install-drive-userscript' + alertBox.className = 'alert alert-info' + alertBox.innerHTML = """ +Due to continual breaking changes making it increasingly difficult to +maintain Google Drive support, you can now install a userscript that +simplifies the code and has better compatibility. In the future, the +old player will be removed.""" + alertBox.appendChild(document.createElement('br')) + infoLink = document.createElement('a') + infoLink.className = 'btn btn-info' + infoLink.href = '/google_drive_userscript' + infoLink.textContent = 'Click here for details' + infoLink.target = '_blank' + alertBox.appendChild(infoLink) + + closeButton = document.createElement('button') + closeButton.className = 'close pull-right' + closeButton.innerHTML = '×' + closeButton.onclick = -> + alertBox.parentNode.removeChild(alertBox) + alertBox.insertBefore(closeButton, alertBox.firstChild) + document.getElementById('videowrap').appendChild(alertBox) diff --git a/player/videojs.coffee b/player/videojs.coffee index 4ad445ac..9a3820af 100644 --- a/player/videojs.coffee +++ b/player/videojs.coffee @@ -93,6 +93,8 @@ window.VideoJSPlayer = class VideoJSPlayer extends Player @player.src(@sources[@sourceIdx]) else console.error('Out of sources, video will not play') + if @mediaType is 'gd' and not window.hasDriveUserscript + window.promptToInstallDriveUserscript() ) @setVolume(VOLUME) @player.on('ended', -> diff --git a/src/web/pug.js b/src/web/pug.js index 60815539..b944ed0b 100644 --- a/src/web/pug.js +++ b/src/web/pug.js @@ -36,6 +36,9 @@ function getBaseUrl(res) { * Renders and serves a pug template */ function sendPug(res, view, locals) { + if (!locals) { + locals = {}; + } locals.loggedIn = locals.loggedIn || !!res.user; locals.loginName = locals.loginName || res.user ? res.user.name : false; locals.superadmin = locals.superadmin || res.user ? res.user.global_rank >= 255 : false; diff --git a/src/web/routes/google_drive_userscript.js b/src/web/routes/google_drive_userscript.js new file mode 100644 index 00000000..72515bb9 --- /dev/null +++ b/src/web/routes/google_drive_userscript.js @@ -0,0 +1,7 @@ +import { sendPug } from '../pug'; + +export default function initialize(app) { + app.get('/google_drive_userscript', (req, res) => { + return sendPug(res, 'google_drive_userscript') + }); +} diff --git a/src/web/webserver.js b/src/web/webserver.js index b428517f..60ff5f83 100644 --- a/src/web/webserver.js +++ b/src/web/webserver.js @@ -177,6 +177,7 @@ module.exports = { require('./account').init(app); require('./acp').init(app); require('../google2vtt').attach(app); + require('./routes/google_drive_userscript')(app); app.use(serveStatic(path.join(__dirname, '..', '..', 'www'), { maxAge: webConfig.getCacheTTL() })); diff --git a/templates/google_drive_userscript.pug b/templates/google_drive_userscript.pug new file mode 100644 index 00000000..f0744431 --- /dev/null +++ b/templates/google_drive_userscript.pug @@ -0,0 +1,74 @@ +doctype html +html(lang="en") + head + include head + +head() + body + #wrap + nav.navbar.navbar-inverse.navbar-fixed-top(role="navigation") + include nav + +navheader() + #nav-collapsible.collapse.navbar-collapse + ul.nav.navbar-nav + +navdefaultlinks("/google_drive_userscript") + +navloginlogout("/google_drive_userscript") + section#mainpage + .container + .col-md-8.col-md-offset-2 + h1 Google Drive Userscript + h2 Why? + p. + Since Google Drive support was launched in early 2014, it has broken + at least 4-5 times, requiring increasing effort to get it working again + and disrupting many channels. This is because there is no official API + for it like there is for YouTube videos, which means support for it + relies on undocumented tricks. In August 2016, the decision was made + to phase out the native support for Google Drive and instead require + users to install a userscript, which allows to bypass certain browser + restrictions and make the code easier, simpler, and less prone to failure + (it could still break due to future Google Drive changes, but is less + likely to be difficult to fix). + h2 How It Works + p. + The userscript is a short script that you can install using a browser + extension such as Greasemonkey or Tampermonkey that runs on the page + and provides additional functionality needed to play Google Drive + videos. + h2 Installation + ul + li + strong Chrome + | —Install Tampermonkey. + li + strong Firefox + | —Install Tampermonkey + | or Greasemonkey. + li + strong Other Browsers + | —Install the appropriate userscript plugin for your browser. + | Tampermonkey supports many browsers besides Chrome. + p. + Once you have installed the userscript manager addon for your browser, + you can + install the userscript. If this link 404s, it means the administrator + of this server hasn't generated it yet. + p. + You can find a guide with screenshots of the installation process + on GitHub. + + include footer + +footer() + script(type="text/javascript"). + function showEmail(btn, email, key) { + email = unescape(email); + key = unescape(key); + var dest = new Array(email.length); + for (var i = 0; i < email.length; i++) { + dest[i] = String.fromCharCode(email.charCodeAt(i) ^ key.charCodeAt(i % key.length)); + } + email = dest.join(""); + $("").attr("href", "mailto:" + email) + .text(email) + .insertBefore(btn); + $(btn).remove(); + } diff --git a/www/js/player.js b/www/js/player.js index 11690df0..3089fe24 100644 --- a/www/js/player.js +++ b/www/js/player.js @@ -558,7 +558,10 @@ if (_this.sourceIdx < _this.sources.length) { return _this.player.src(_this.sources[_this.sourceIdx]); } else { - return console.error('Out of sources, video will not play'); + console.error('Out of sources, video will not play'); + if (_this.mediaType === 'gd' && !window.hasDriveUserscript) { + return window.promptToInstallDriveUserscript(); + } } } }); @@ -1179,6 +1182,7 @@ GoogleDriveYouTubePlayer.prototype.init = function(data) { var embed; + window.promptToInstallDriveUserscript(); embed = $('').attr({ type: 'application/x-shockwave-flash', src: "https://www.youtube.com/get_player?docid=" + data.id + "&ps=docs&partnerid=30&enablejsapi=1&cc_load_policy=1&auth_timeout=86400000000", @@ -1308,6 +1312,32 @@ })(Player); + window.promptToInstallDriveUserscript = function() { + var alertBox, closeButton, infoLink; + if (document.getElementById('prompt-install-drive-userscript')) { + return; + } + alertBox = document.createElement('div'); + alertBox.id = 'prompt-install-drive-userscript'; + alertBox.className = 'alert alert-info'; + alertBox.innerHTML = "Due to continual breaking changes making it increasingly difficult to\nmaintain Google Drive support, you can now install a userscript that\nsimplifies the code and has better compatibility. In the future, the\nold player will be removed."; + alertBox.appendChild(document.createElement('br')); + infoLink = document.createElement('a'); + infoLink.className = 'btn btn-info'; + infoLink.href = '/google_drive_userscript'; + infoLink.textContent = 'Click here for details'; + infoLink.target = '_blank'; + alertBox.appendChild(infoLink); + closeButton = document.createElement('button'); + closeButton.className = 'close pull-right'; + closeButton.innerHTML = '×'; + closeButton.onclick = function() { + return alertBox.parentNode.removeChild(alertBox); + }; + alertBox.insertBefore(closeButton, alertBox.firstChild); + return document.getElementById('videowrap').appendChild(alertBox); + }; + window.HLSPlayer = HLSPlayer = (function(superClass) { extend(HLSPlayer, superClass);