mirror of https://github.com/calzoneman/sync.git
Merge pull request #323 from calzoneman/vimeoisadouchecopter
Workaround for Vimeo refusing to unblock my domain
This commit is contained in:
commit
97fd06f7a6
|
@ -45,6 +45,7 @@ var defaults = {
|
||||||
"ytv3apikey" : "",
|
"ytv3apikey" : "",
|
||||||
"enable-ytv3" : false,
|
"enable-ytv3" : false,
|
||||||
"ytv2devkey" : "",
|
"ytv2devkey" : "",
|
||||||
|
"vimeo-workaround" : false,
|
||||||
"stat-interval" : 3600000,
|
"stat-interval" : 3600000,
|
||||||
"stat-max-age" : 86400000,
|
"stat-max-age" : 86400000,
|
||||||
"alias-purge-interval" : 3600000,
|
"alias-purge-interval" : 3600000,
|
||||||
|
|
|
@ -357,7 +357,7 @@ var Getters = {
|
||||||
callback("API failure", null);
|
callback("API failure", null);
|
||||||
return;
|
return;
|
||||||
} else if(status !== 200) {
|
} else if(status !== 200) {
|
||||||
callback("YTv2 HTTP " + status, null);
|
callback("Vimeo HTTP " + status, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -702,6 +702,89 @@ var Getters = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function name like this deserves an explanation.
|
||||||
|
*
|
||||||
|
* Vimeo blocked my domain from using their embed API, citing "volume of abuse" as the
|
||||||
|
* reason. After attempts to reach a compromise, Vimeo refused to unblock my domain.
|
||||||
|
*
|
||||||
|
* This function downloads the HTML for the embedded player and extracts direct links
|
||||||
|
* to the h264 encoded MP4 video files (with a session hash required for downloading it).
|
||||||
|
*
|
||||||
|
* This rivals the Google Drive playback functionality as quite possibly the hackiest code
|
||||||
|
* in CyTube. Thanks Vimeo, and may you never see a dime in advertising revenue from a
|
||||||
|
* CyTube client again.
|
||||||
|
*/
|
||||||
|
function VimeoIsADoucheCopter(id, cb) {
|
||||||
|
var failcount = 0;
|
||||||
|
|
||||||
|
var inner = function () {
|
||||||
|
var options = {
|
||||||
|
host: "player.vimeo.com",
|
||||||
|
path: "/video/" + id,
|
||||||
|
headers: {
|
||||||
|
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:29.0) Gecko/20100101 Firefox/29.0",
|
||||||
|
"Referrer": "player.vimeo.com"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var parse = function (data) {
|
||||||
|
var i = data.indexOf("a={");
|
||||||
|
var j = data.indexOf("};", i);
|
||||||
|
var json = data.substring(i+2, j+1);
|
||||||
|
try {
|
||||||
|
json = JSON.parse(json);
|
||||||
|
var codec = json.request.files.codecs[0];
|
||||||
|
var files = json.request.files[codec];
|
||||||
|
setImmediate(function () {
|
||||||
|
cb(files);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// Not sure why the response sometimes differs. Trying to weed out
|
||||||
|
// search engines perhaps?
|
||||||
|
// Maybe adding the User-Agent above will fix this, I dunno
|
||||||
|
if (data.indexOf("crawler") !== -1) {
|
||||||
|
Logger.syslog.log("Warning: VimeoIsADoucheCopter got crawler response");
|
||||||
|
failcount++;
|
||||||
|
if (failcount > 4) {
|
||||||
|
Logger.errlog.log("VimeoIsADoucheCopter got bad response 5 times!"+
|
||||||
|
" Giving up.");
|
||||||
|
setImmediate(function () {
|
||||||
|
cb({});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setImmediate(function () {
|
||||||
|
inner();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Logger.errlog.log("Vimeo workaround error: ");
|
||||||
|
Logger.errlog.log(e);
|
||||||
|
Logger.errlog.log(data);
|
||||||
|
setImmediate(function () {
|
||||||
|
cb({});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
http.get(options, function (res) {
|
||||||
|
res.setEncoding("utf-8");
|
||||||
|
var buffer = "";
|
||||||
|
|
||||||
|
res.on("data", function (data) {
|
||||||
|
buffer += data;
|
||||||
|
});
|
||||||
|
|
||||||
|
res.on("end", function () {
|
||||||
|
parse(buffer);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
inner();
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Getters: Getters,
|
Getters: Getters,
|
||||||
getMedia: function (id, type, callback) {
|
getMedia: function (id, type, callback) {
|
||||||
|
@ -710,5 +793,6 @@ module.exports = {
|
||||||
} else {
|
} else {
|
||||||
callback("Unknown media type '" + type + "'", null);
|
callback("Unknown media type '" + type + "'", null);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
VimeoIsADoucheCopter: VimeoIsADoucheCopter
|
||||||
};
|
};
|
||||||
|
|
|
@ -68,6 +68,9 @@ Media.prototype.fullupdate = function() {
|
||||||
if (this.params) {
|
if (this.params) {
|
||||||
x.params = this.params;
|
x.params = this.params;
|
||||||
}
|
}
|
||||||
|
if (this.direct) {
|
||||||
|
x.direct = this.direct;
|
||||||
|
}
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,4 +82,9 @@ Media.prototype.timeupdate = function() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Media.prototype.reset = function () {
|
||||||
|
delete this.currentTime;
|
||||||
|
delete this.direct;
|
||||||
|
};
|
||||||
|
|
||||||
exports.Media = Media;
|
exports.Media = Media;
|
||||||
|
|
|
@ -13,6 +13,8 @@ ULList = require("./ullist").ULList;
|
||||||
var AsyncQueue = require("./asyncqueue");
|
var AsyncQueue = require("./asyncqueue");
|
||||||
var Media = require("./media").Media;
|
var Media = require("./media").Media;
|
||||||
var AllPlaylists = {};
|
var AllPlaylists = {};
|
||||||
|
var Server = require("./server");
|
||||||
|
var VimeoIsADoucheCopter = require("./get-info").VimeoIsADoucheCopter;
|
||||||
|
|
||||||
function PlaylistItem(media, uid) {
|
function PlaylistItem(media, uid) {
|
||||||
this.media = media;
|
this.media = media;
|
||||||
|
@ -212,6 +214,7 @@ Playlist.prototype.addMedia = function (data) {
|
||||||
var m = new Media(data.id, data.title, data.seconds, data.type);
|
var m = new Media(data.id, data.title, data.seconds, data.type);
|
||||||
m.object = data.object;
|
m.object = data.object;
|
||||||
m.params = data.params;
|
m.params = data.params;
|
||||||
|
m.direct = data.direct;
|
||||||
var item = this.makeItem(m);
|
var item = this.makeItem(m);
|
||||||
item.queueby = data.queueby;
|
item.queueby = data.queueby;
|
||||||
item.temp = data.temp;
|
item.temp = data.temp;
|
||||||
|
@ -257,6 +260,7 @@ Playlist.prototype.next = function() {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var it = this.current;
|
var it = this.current;
|
||||||
|
it.media.reset();
|
||||||
|
|
||||||
if (it.temp) {
|
if (it.temp) {
|
||||||
if (this.remove(it.uid)) {
|
if (this.remove(it.uid)) {
|
||||||
|
@ -290,6 +294,7 @@ Playlist.prototype.jump = function(uid) {
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var it = this.current;
|
var it = this.current;
|
||||||
|
it.media.reset();
|
||||||
|
|
||||||
this.current = jmp;
|
this.current = jmp;
|
||||||
|
|
||||||
|
@ -338,29 +343,41 @@ Playlist.prototype.lead = function(lead) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Playlist.prototype.startPlayback = function (time) {
|
Playlist.prototype.startPlayback = function (time) {
|
||||||
if(!this.current || !this.current.media)
|
var self = this;
|
||||||
|
if (!self.current || !self.current.media) {
|
||||||
return false;
|
return false;
|
||||||
if (!this.leading) {
|
}
|
||||||
this.current.media.paused = false;
|
|
||||||
this.current.media.currentTime = time || 0;
|
if (self.current.media.type === "vi" &&
|
||||||
this.on("changeMedia")(this.current.media);
|
!self.current.media.direct &&
|
||||||
|
Server.getServer().cfg["vimeo-workaround"]) {
|
||||||
|
VimeoIsADoucheCopter(self.current.media.id, function (direct) {
|
||||||
|
self.current.media.direct = direct;
|
||||||
|
self.startPlayback(time);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!self.leading) {
|
||||||
|
self.current.media.paused = false;
|
||||||
|
self.current.media.currentTime = time || 0;
|
||||||
|
self.on("changeMedia")(self.current.media);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
time = time || -3;
|
time = time || -3;
|
||||||
this.current.media.paused = time < 0;
|
self.current.media.paused = time < 0;
|
||||||
this.current.media.currentTime = time;
|
self.current.media.currentTime = time;
|
||||||
|
|
||||||
var pl = this;
|
if(self._leadInterval) {
|
||||||
if(this._leadInterval) {
|
clearInterval(self._leadInterval);
|
||||||
clearInterval(this._leadInterval);
|
self._leadInterval = false;
|
||||||
this._leadInterval = false;
|
|
||||||
}
|
}
|
||||||
this.on("changeMedia")(this.current.media);
|
self.on("changeMedia")(self.current.media);
|
||||||
if(!isLive(this.current.media.type)) {
|
if(!isLive(self.current.media.type)) {
|
||||||
this._lastUpdate = Date.now();
|
self._lastUpdate = Date.now();
|
||||||
this._leadInterval = setInterval(function() {
|
self._leadInterval = setInterval(function() {
|
||||||
pl._leadLoop();
|
self._leadLoop();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1011,8 +1011,10 @@ Callbacks = {
|
||||||
changeMedia: function(data) {
|
changeMedia: function(data) {
|
||||||
if (PLAYER) {
|
if (PLAYER) {
|
||||||
PLAYER.getVolume(function (v) {
|
PLAYER.getVolume(function (v) {
|
||||||
|
if (typeof v === "number") {
|
||||||
VOLUME = v;
|
VOLUME = v;
|
||||||
setOpt("volume", VOLUME);
|
setOpt("volume", VOLUME);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1032,6 +1034,31 @@ Callbacks = {
|
||||||
$("#ytapiplayer_wrapper").remove();
|
$("#ytapiplayer_wrapper").remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
VIMEO SIMULATOR 2014
|
||||||
|
|
||||||
|
Vimeo decided to block my domain. After repeated emails, they refused to
|
||||||
|
unblock it. Rather than give in to their demands, there is a serverside
|
||||||
|
option which extracts direct links to the h264 encoded MP4 video files.
|
||||||
|
These files can be loaded in a custom player to allow Vimeo playback without
|
||||||
|
triggering their dumb API domain block.
|
||||||
|
|
||||||
|
It's a little bit hacky, but my only other option is to keep buying new
|
||||||
|
domains every time one gets blocked. No thanks to Vimeo, who were of no help
|
||||||
|
and unwilling to compromise on the issue.
|
||||||
|
*/
|
||||||
|
if (NO_VIMEO && data.type === "vi" && data.direct && data.direct.sd) {
|
||||||
|
// For browsers that don't support native h264 playback
|
||||||
|
if (USEROPTS.no_h264) {
|
||||||
|
data.type = "jw";
|
||||||
|
} else {
|
||||||
|
data.type = "rv";
|
||||||
|
}
|
||||||
|
// Right now only plays standard definition.
|
||||||
|
// In the future, I may add a quality selector for mobile/standard/HD
|
||||||
|
data.url = data.direct.sd.url;
|
||||||
|
}
|
||||||
|
|
||||||
if (data.type != PLAYER.type) {
|
if (data.type != PLAYER.type) {
|
||||||
loadMediaPlayer(data);
|
loadMediaPlayer(data);
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,6 +97,15 @@ function getOrDefault(k, def) {
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function default_noh264() {
|
||||||
|
var ua = navigator.userAgent + "";
|
||||||
|
if (ua.match(/Firefox\/29.0|Chrome|Chromium/)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var USEROPTS = {
|
var USEROPTS = {
|
||||||
theme : getOrDefault("theme", "default"),
|
theme : getOrDefault("theme", "default"),
|
||||||
css : getOrDefault("css", ""),
|
css : getOrDefault("css", ""),
|
||||||
|
@ -120,12 +129,15 @@ var USEROPTS = {
|
||||||
sort_afk : getOrDefault("sort_afk", false),
|
sort_afk : getOrDefault("sort_afk", false),
|
||||||
default_quality : getOrDefault("default_quality", "#quality_auto"),
|
default_quality : getOrDefault("default_quality", "#quality_auto"),
|
||||||
boop : getOrDefault("boop", false),
|
boop : getOrDefault("boop", false),
|
||||||
secure_connection : getOrDefault("secure_connection", false)
|
secure_connection : getOrDefault("secure_connection", false),
|
||||||
|
no_h264 : getOrDefault("no_h264", default_noh264())
|
||||||
};
|
};
|
||||||
|
|
||||||
var VOLUME = getOrDefault("volume", 1);
|
var VOLUME = parseFloat(getOrDefault("volume", 1));
|
||||||
|
|
||||||
var NO_WEBSOCKETS = USEROPTS.altsocket;
|
var NO_WEBSOCKETS = USEROPTS.altsocket;
|
||||||
|
// Change this to include your own domain if you enable Vimeo fallback
|
||||||
|
var NO_VIMEO = Boolean(location.host.match("cytu.be"));
|
||||||
|
|
||||||
var Rank = {
|
var Rank = {
|
||||||
Guest: 0,
|
Guest: 0,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/*
|
/*}
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
Copyright (c) 2013 Calvin Montgomery
|
Copyright (c) 2013 Calvin Montgomery
|
||||||
|
|
||||||
|
@ -731,12 +731,17 @@ var RTMPPlayer = function (data) {
|
||||||
var JWPlayer = function (data) {
|
var JWPlayer = function (data) {
|
||||||
var self = this;
|
var self = this;
|
||||||
self.videoId = data.id;
|
self.videoId = data.id;
|
||||||
|
if (data.url) {
|
||||||
|
self.videoURL = data.url;
|
||||||
|
} else {
|
||||||
|
self.videoURL = data.id;
|
||||||
|
}
|
||||||
self.videoLength = data.seconds;
|
self.videoLength = data.seconds;
|
||||||
self.init = function () {
|
self.init = function () {
|
||||||
removeOld();
|
removeOld();
|
||||||
|
|
||||||
jwplayer("ytapiplayer").setup({
|
jwplayer("ytapiplayer").setup({
|
||||||
file: self.videoId,
|
file: self.videoURL,
|
||||||
width: VWIDTH,
|
width: VWIDTH,
|
||||||
height: VHEIGHT,
|
height: VHEIGHT,
|
||||||
autostart: true
|
autostart: true
|
||||||
|
@ -990,6 +995,77 @@ var GoogleDocsPlayer = function (data) {
|
||||||
self.init(data);
|
self.init(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function RawVideoPlayer(data) {
|
||||||
|
var self = this;
|
||||||
|
self.init = function (data) {
|
||||||
|
self.videoId = data.id;
|
||||||
|
self.videoURL = data.url;
|
||||||
|
var video = $("<video/>")
|
||||||
|
.attr("src", self.videoURL)
|
||||||
|
.attr("controls", "controls")
|
||||||
|
.attr("id", "#ytapiplayer")
|
||||||
|
.attr("width", VWIDTH)
|
||||||
|
.attr("height", VHEIGHT)
|
||||||
|
.html("Your browser does not support HTML5 <code><video></code> tags :(");
|
||||||
|
removeOld(video);
|
||||||
|
self.player = video[0];
|
||||||
|
self.setVolume(VOLUME);
|
||||||
|
};
|
||||||
|
|
||||||
|
self.load = function (data) {
|
||||||
|
self.init(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
self.pause = function () {
|
||||||
|
if (self.player) {
|
||||||
|
self.player.pause();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.play = function () {
|
||||||
|
if (self.player) {
|
||||||
|
self.player.play();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.isPaused = function (callback) {
|
||||||
|
if (self.player) {
|
||||||
|
callback(self.player.paused);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.getTime = function (callback) {
|
||||||
|
if (self.player) {
|
||||||
|
callback(self.player.currentTime);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.seek = function (time) {
|
||||||
|
if (self.player) {
|
||||||
|
self.player.currentTime = time;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.getVolume = function (cb) {
|
||||||
|
if (self.player) {
|
||||||
|
if (self.player.muted) {
|
||||||
|
cb(0);
|
||||||
|
} else {
|
||||||
|
cb(self.player.volume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.setVolume = function (vol) {
|
||||||
|
if (self.player) {
|
||||||
|
self.player.volume = vol;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.init(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
function handleMediaUpdate(data) {
|
function handleMediaUpdate(data) {
|
||||||
// Don't update if the position is past the video length, but
|
// Don't update if the position is past the video length, but
|
||||||
// make an exception when the video length is 0 seconds
|
// make an exception when the video length is 0 seconds
|
||||||
|
@ -1086,7 +1162,8 @@ var constructors = {
|
||||||
"jw": JWPlayer,
|
"jw": JWPlayer,
|
||||||
"im": ImgurPlayer,
|
"im": ImgurPlayer,
|
||||||
"cu": CustomPlayer,
|
"cu": CustomPlayer,
|
||||||
"gd": GoogleDocsPlayer
|
"gd": GoogleDocsPlayer,
|
||||||
|
"rv": RawVideoPlayer
|
||||||
};
|
};
|
||||||
|
|
||||||
function loadMediaPlayer(data) {
|
function loadMediaPlayer(data) {
|
||||||
|
|
|
@ -662,6 +662,9 @@ function showOptionsMenu() {
|
||||||
"issues on some systems");
|
"issues on some systems");
|
||||||
addOption(playback, "", pl_wmodewarn);
|
addOption(playback, "", pl_wmodewarn);
|
||||||
|
|
||||||
|
var pl_noh264 = addCheckbox(playback, "h.264", "Use JWPlayer for h.264 playback");
|
||||||
|
pl_noh264.prop("checked", USEROPTS.no_h264);
|
||||||
|
|
||||||
var pl_hide = addCheckbox(playback, "Hide Video",
|
var pl_hide = addCheckbox(playback, "Hide Video",
|
||||||
"Remove the video player");
|
"Remove the video player");
|
||||||
pl_hide.prop("checked", USEROPTS.hidevid);
|
pl_hide.prop("checked", USEROPTS.hidevid);
|
||||||
|
@ -731,6 +734,7 @@ function showOptionsMenu() {
|
||||||
USEROPTS.hidevid = pl_hide.prop("checked");
|
USEROPTS.hidevid = pl_hide.prop("checked");
|
||||||
USEROPTS.qbtn_hide = pl_hidebtn.prop("checked");
|
USEROPTS.qbtn_hide = pl_hidebtn.prop("checked");
|
||||||
USEROPTS.qbtn_idontlikechange = pl_oldbtn.prop("checked");
|
USEROPTS.qbtn_idontlikechange = pl_oldbtn.prop("checked");
|
||||||
|
USEROPTS.no_h264 = pl_noh264.prop("checked");
|
||||||
USEROPTS.show_timestamps = chat_time.prop("checked");
|
USEROPTS.show_timestamps = chat_time.prop("checked");
|
||||||
USEROPTS.sort_rank = chat_sort_rank.prop("checked");
|
USEROPTS.sort_rank = chat_sort_rank.prop("checked");
|
||||||
USEROPTS.sort_afk = chat_sort_afk.prop("checked");
|
USEROPTS.sort_afk = chat_sort_afk.prop("checked");
|
||||||
|
|
Loading…
Reference in New Issue