Merge remote-tracking branch 'origin' into ip-session-age

This commit is contained in:
Calvin Montgomery 2016-08-24 19:48:58 -07:00
commit af6e958c49
27 changed files with 6770 additions and 1420 deletions

13
NEWS.md
View file

@ -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
==========

View file

@ -8,6 +8,7 @@ var order = [
'youtube.coffee',
'dailymotion.coffee',
'videojs.coffee',
'gdrive-player.coffee',
'raw-file.coffee',
'soundcloud.coffee',
'embed.coffee',
@ -19,6 +20,7 @@ var order = [
'ustream.coffee',
'imgur.coffee',
'gdrive-youtube.coffee',
'hls.coffee',
'update.coffee'
];

View file

@ -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 <site name> <url> [<url>...]
```
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`.

View file

@ -0,0 +1,210 @@
// ==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.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);
}

View file

@ -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);
}
});

View file

@ -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"

View file

@ -0,0 +1,22 @@
window.GoogleDrivePlayer = class GoogleDrivePlayer extends VideoJSPlayer
constructor: (data) ->
if not (this instanceof GoogleDrivePlayer)
return new GoogleDrivePlayer(data)
super(data)
load: (data) ->
if typeof window.getGoogleDriveMetadata is 'function'
window.getGoogleDriveMetadata(data.id, (error, metadata) =>
if error
console.error(error)
alertBox = window.document.createElement('div')
alertBox.className = 'alert alert-danger'
alertBox.textContent = error.message
document.getElementById('ytapiplayer').appendChild(alertBox)
else
data.meta.direct = metadata.videoMap
super(data)
)
else
super(data)

View file

@ -7,6 +7,7 @@ window.GoogleDriveYouTubePlayer = class GoogleDriveYouTubePlayer extends Player
@init(data)
init: (data) ->
window.promptToInstallDriveUserscript()
embed = $('<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 = '&times;'
closeButton.onclick = ->
alertBox.parentNode.removeChild(alertBox)
alertBox.insertBefore(closeButton, alertBox.firstChild)
document.getElementById('videowrap').appendChild(alertBox)

23
player/hls.coffee Normal file
View file

@ -0,0 +1,23 @@
window.HLSPlayer = class HLSPlayer extends VideoJSPlayer
constructor: (data) ->
if not (this instanceof HLSPlayer)
return new HLSPlayer(data)
@setupMeta(data)
super(data)
load: (data) ->
@setupMeta(data)
super(data)
setupMeta: (data) ->
data.meta.direct =
# Quality is required for data.meta.direct processing but doesn't
# matter here because it's dictated by the stream. Arbitrarily
# choose 480.
480: [
{
link: data.id
contentType: 'application/x-mpegURL'
}
]

View file

@ -2,7 +2,7 @@ TYPE_MAP =
yt: YouTubePlayer
vi: VimeoPlayer
dm: DailymotionPlayer
gd: GoogleDriveYouTubePlayer
gd: GoogleDrivePlayer
gp: VideoJSPlayer
fi: FilePlayer
jw: FilePlayer
@ -15,6 +15,7 @@ TYPE_MAP =
us: UstreamPlayer
im: ImgurPlayer
vm: VideoJSPlayer
hl: HLSPlayer
sb: VideoJSPlayer
window.loadMediaPlayer = (data) ->
@ -29,6 +30,14 @@ window.loadMediaPlayer = (data) ->
window.PLAYER = new VideoJSPlayer(data)
catch e
console.error e
else if data.type is 'gd'
try
if data.meta.html5hack or window.hasDriveUserscript
window.PLAYER = new GoogleDrivePlayer(data)
else
window.PLAYER = new GoogleDriveYouTubePlayer(data)
catch e
console.error e
else if data.type of TYPE_MAP
try
window.PLAYER = TYPE_MAP[data.type](data)

View file

@ -43,8 +43,7 @@ window.VideoJSPlayer = class VideoJSPlayer extends Player
if not (this instanceof VideoJSPlayer)
return new VideoJSPlayer(data)
@setMediaProperties(data)
@loadPlayer(data)
@load(data)
loadPlayer: (data) ->
waitUntilDefined(window, 'videojs', =>
@ -53,14 +52,15 @@ window.VideoJSPlayer = class VideoJSPlayer extends Player
.attr(width: '100%', height: '100%')
removeOld(video)
sources = sortSources(data.meta.direct)
if sources.length == 0
@sources = sortSources(data.meta.direct)
if @sources.length == 0
console.error('VideoJSPlayer::constructor(): data.meta.direct
has no sources!')
@mediaType = null
return
sources.forEach((source) ->
@sourceIdx = 0
@sources.forEach((source) ->
$('<source/>').attr(
src: source.src
type: source.type
@ -84,6 +84,18 @@ window.VideoJSPlayer = class VideoJSPlayer extends Player
@player = videojs(video[0], autoplay: true, controls: true)
@player.ready(=>
@player.on('error', =>
err = @player.error()
if err and err.code == 4
console.error('Caught error, trying next source')
@sourceIdx++
if @sourceIdx < @sources.length
@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', ->
if CLIENT.leader
@ -114,12 +126,13 @@ window.VideoJSPlayer = class VideoJSPlayer extends Player
# not to run until the ready() function returns.
setTimeout(->
$('#ytapiplayer .vjs-subtitles-button .vjs-menu-item').each((i, elem) ->
if elem.textContent == localStorage.lastSubtitle
textNode = elem.childNodes[0]
if textNode.textContent == localStorage.lastSubtitle
elem.click()
elem.onclick = ->
if elem.attributes['aria-selected'].value == 'true'
localStorage.lastSubtitle = elem.textContent
if elem.attributes['aria-checked'].value == 'true'
localStorage.lastSubtitle = textNode.textContent
)
, 1)
)

View file

@ -675,7 +675,7 @@ PlaylistModule.prototype.handlePlayNext = function (user) {
title = this.current.media.title;
}
this.channel.logger.log("[playlist] " + user.getName() + " skipped" + title);
this.channel.logger.log("[playlist] " + user.getName() + " skipped " + title);
this._playNext();
};

View file

@ -120,6 +120,9 @@ var defaults = {
"service-socket": {
enabled: false,
socket: "service.sock"
},
"google-drive": {
"html5-hack-enabled": false
}
};

View file

@ -12,6 +12,7 @@ var YouTube = require("cytube-mediaquery/lib/provider/youtube");
var Vimeo = require("cytube-mediaquery/lib/provider/vimeo");
var Vidme = require("cytube-mediaquery/lib/provider/vidme");
var Streamable = require("cytube-mediaquery/lib/provider/streamable");
var GoogleDrive = require("cytube-mediaquery/lib/provider/googledrive");
/*
* Preference map of quality => youtube formats.
@ -458,6 +459,13 @@ var Getters = {
callback(false, media);
},
/* HLS stream */
hl: function (id, callback) {
var title = "Livestream";
var media = new Media(id, title, "--:--", "hl");
callback(false, media);
},
/* imgur.com albums */
im: function (id, callback) {
/**
@ -493,6 +501,7 @@ var Getters = {
/* google docs */
gd: function (id, callback) {
GoogleDrive.setHTML5HackEnabled(Config.get("google-drive.html5-hack-enabled"));
var data = {
type: "googledrive",
kind: "single",

View file

@ -38,7 +38,8 @@ Media.prototype = {
bitrate: this.meta.bitrate,
scuri: this.meta.scuri,
embed: this.meta.embed,
gdrive_subtitles: this.meta.gdrive_subtitles
gdrive_subtitles: this.meta.gdrive_subtitles,
html5hack: this.meta.html5hack
}
};
},

View file

@ -119,7 +119,7 @@
});
}
return result.join(":");
return result.join(":");
},
root.formatTime = function (sec) {
@ -242,6 +242,8 @@
return id;
case "hb":
return "http://hitbox.tv/" + id;
case "hl":
return id;
case "sb":
return "https://streamable.com/" + id;
default:
@ -259,6 +261,7 @@
case "im":
case "jw":
case "hb":
case "hl":
return true;
default:
return false;
@ -294,8 +297,6 @@
var accumulator = "";
parts = parts.map(function (segment, i) {
if (i < 2) return segment;
var part = iphash(accumulator + segment + i, 3);
accumulator += segment;
return part;
@ -311,8 +312,6 @@
var accumulator = "";
parts = parts.map(function (segment, i) {
if (i < 2) return segment;
var part = iphash(accumulator + segment + i, 4);
accumulator += segment;
return part;

View file

@ -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;

View file

@ -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')
});
}

View file

@ -178,6 +178,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()
}));

View file

@ -249,3 +249,4 @@ html(lang="en")
script(defer, src="/js/sc.js")
script(defer, src="/js/froogaloop.min.js")
script(defer, src="/js/video.js")
script(defer, src="/js/videojs-contrib-hls.min.js")

View file

@ -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
| &mdash;Install <a href="https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo" target="_blank">Tampermonkey</a>.
li
strong Firefox
| &mdash;Install <a href="https://addons.mozilla.org/en-US/firefox/addon/tampermonkey/" target="_blank">Tampermonkey</a>
| or <a href="https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/" target="_blank">Greasemonkey</a>.
li
strong Other Browsers
| &mdash;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 <a href="/js/cytube-google-drive.user.js?v=1.1" target="_blank">
install the userscript</a>. 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
<a href="https://github.com/calzoneman/sync/wiki/Google-Drive-Userscript-Installation-Guide" target="_blank">on GitHub</a>.
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("");
$("<a/>").attr("href", "mailto:" + email)
.text(email)
.insertBefore(btn);
$(btn).remove();
}

File diff suppressed because one or more lines are too long

View file

@ -216,3 +216,5 @@ function eraseCookie(name) {
/* to be implemented in callbacks.js */
function setupCallbacks() { }
window.enableCyTubeGoogleDriveUserscriptDebug = getOrDefault("cytube_drive_debug", false);

View file

@ -1,5 +1,5 @@
(function() {
var CUSTOM_EMBED_WARNING, CustomEmbedPlayer, DEFAULT_ERROR, DailymotionPlayer, EmbedPlayer, FilePlayer, GoogleDriveYouTubePlayer, HITBOX_ERROR, 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;
@ -503,26 +503,26 @@
if (!(this instanceof VideoJSPlayer)) {
return new VideoJSPlayer(data);
}
this.setMediaProperties(data);
this.loadPlayer(data);
this.load(data);
}
VideoJSPlayer.prototype.loadPlayer = function(data) {
return waitUntilDefined(window, 'videojs', (function(_this) {
return function() {
var sources, video;
var video;
video = $('<video/>').addClass('video-js vjs-default-skin embed-responsive-item').attr({
width: '100%',
height: '100%'
});
removeOld(video);
sources = sortSources(data.meta.direct);
if (sources.length === 0) {
_this.sources = sortSources(data.meta.direct);
if (_this.sources.length === 0) {
console.error('VideoJSPlayer::constructor(): data.meta.direct has no sources!');
_this.mediaType = null;
return;
}
sources.forEach(function(source) {
_this.sourceIdx = 0;
_this.sources.forEach(function(source) {
return $('<source/>').attr({
src: source.src,
type: source.type,
@ -549,6 +549,22 @@
controls: true
});
return _this.player.ready(function() {
_this.player.on('error', function() {
var err;
err = _this.player.error();
if (err && err.code === 4) {
console.error('Caught error, trying next source');
_this.sourceIdx++;
if (_this.sourceIdx < _this.sources.length) {
return _this.player.src(_this.sources[_this.sourceIdx]);
} else {
console.error('Out of sources, video will not play');
if (_this.mediaType === 'gd' && !window.hasDriveUserscript) {
return window.promptToInstallDriveUserscript();
}
}
}
});
_this.setVolume(VOLUME);
_this.player.on('ended', function() {
if (CLIENT.leader) {
@ -572,12 +588,14 @@
});
return setTimeout(function() {
return $('#ytapiplayer .vjs-subtitles-button .vjs-menu-item').each(function(i, elem) {
if (elem.textContent === localStorage.lastSubtitle) {
var textNode;
textNode = elem.childNodes[0];
if (textNode.textContent === localStorage.lastSubtitle) {
elem.click();
}
return elem.onclick = function() {
if (elem.attributes['aria-selected'].value === 'true') {
return localStorage.lastSubtitle = elem.textContent;
if (elem.attributes['aria-checked'].value === 'true') {
return localStorage.lastSubtitle = textNode.textContent;
}
};
});
@ -650,6 +668,42 @@
})(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);
}
GoogleDrivePlayer.prototype.load = function(data) {
if (typeof window.getGoogleDriveMetadata === 'function') {
return window.getGoogleDriveMetadata(data.id, (function(_this) {
return function(error, metadata) {
var alertBox;
if (error) {
console.error(error);
alertBox = window.document.createElement('div');
alertBox.className = 'alert alert-danger';
alertBox.textContent = error.message;
return document.getElementById('ytapiplayer').appendChild(alertBox);
} else {
data.meta.direct = metadata.videoMap;
return GoogleDrivePlayer.__super__.load.call(_this, data);
}
};
})(this));
} else {
return GoogleDrivePlayer.__super__.load.call(this, data);
}
};
return GoogleDrivePlayer;
})(VideoJSPlayer);
codecToMimeType = function(codec) {
switch (codec) {
case 'mov/h264':
@ -1128,6 +1182,7 @@
GoogleDriveYouTubePlayer.prototype.init = function(data) {
var embed;
window.promptToInstallDriveUserscript();
embed = $('<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",
@ -1257,11 +1312,68 @@
})(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 = '&times;';
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);
function HLSPlayer(data) {
if (!(this instanceof HLSPlayer)) {
return new HLSPlayer(data);
}
this.setupMeta(data);
HLSPlayer.__super__.constructor.call(this, data);
}
HLSPlayer.prototype.load = function(data) {
this.setupMeta(data);
return HLSPlayer.__super__.load.call(this, data);
};
HLSPlayer.prototype.setupMeta = function(data) {
return data.meta.direct = {
480: [
{
link: data.id,
contentType: 'application/x-mpegURL'
}
]
};
};
return HLSPlayer;
})(VideoJSPlayer);
TYPE_MAP = {
yt: YouTubePlayer,
vi: VimeoPlayer,
dm: DailymotionPlayer,
gd: GoogleDriveYouTubePlayer,
gd: GoogleDrivePlayer,
gp: VideoJSPlayer,
fi: FilePlayer,
jw: FilePlayer,
@ -1274,11 +1386,12 @@
us: UstreamPlayer,
im: ImgurPlayer,
vm: VideoJSPlayer,
hl: HLSPlayer,
sb: VideoJSPlayer
};
window.loadMediaPlayer = function(data) {
var e, error, error1, error2, error3;
var e, error, error1, error2, error3, error4;
try {
if (window.PLAYER) {
window.PLAYER.destroy();
@ -1294,11 +1407,22 @@
e = error2;
return console.error(e);
}
} else if (data.type === 'gd') {
try {
if (data.meta.html5hack || window.hasDriveUserscript) {
return window.PLAYER = new GoogleDrivePlayer(data);
} else {
return window.PLAYER = new GoogleDriveYouTubePlayer(data);
}
} catch (error3) {
e = error3;
return console.error(e);
}
} else if (data.type in TYPE_MAP) {
try {
return window.PLAYER = TYPE_MAP[data.type](data);
} catch (error3) {
e = error3;
} catch (error4) {
e = error4;
return console.error(e);
}
}

View file

@ -52,6 +52,8 @@ function formatURL(data) {
return data.id;
case "hb":
return "http://hitbox.tv/" + data.id;
case "hl":
return data.id;
case "sb":
return "https://streamable.com/" + data.id;
default:
@ -1375,6 +1377,13 @@ function parseMediaLink(url) {
};
}
if ((m = url.match(/(.*\.m3u8)/))) {
return {
id: m[1],
type: "hl"
};
}
if((m = url.match(/streamable\.com\/([\w-]+)/))) {
return {
id: m[1],

File diff suppressed because it is too large Load diff

12
www/js/videojs-contrib-hls.min.js vendored Normal file

File diff suppressed because one or more lines are too long