diff --git a/lib/get-info.js b/lib/get-info.js
index 52a36813..f610afe4 100644
--- a/lib/get-info.js
+++ b/lib/get-info.js
@@ -7,7 +7,7 @@ var CustomEmbedFilter = require("./customembed").filter;
var Server = require("./server");
var Config = require("./config");
var ffmpeg = require("./ffmpeg");
-require("cytube-mediaquery"); // Initialize sourcemaps
+var mediaquery = require("cytube-mediaquery");
var YouTube = require("cytube-mediaquery/lib/provider/youtube");
/*
@@ -62,6 +62,17 @@ var urlRetrieve = function (transport, options, callback) {
req.end();
};
+var mediaTypeMap = {
+ "youtube": "yt",
+ "googledrive": "gd",
+ "google+": "gp"
+};
+
+function convertMedia(media) {
+ return new Media(media.id, media.title, media.duration, mediaTypeMap[media.type],
+ media.meta);
+}
+
var Getters = {
/* youtube.com */
yt: function (id, callback) {
@@ -506,96 +517,16 @@ var Getters = {
/* google docs */
gd: function (id, callback) {
- /* WARNING: hacks inbound */
- var options = {
- host: "docs.google.com",
- path: "/file/d/" + id + "/get_video_info?sle=true",
- port: 443
+ var data = {
+ type: "googledrive",
+ kind: "single",
+ id: id
};
- urlRetrieve(https, options, function (status, res) {
- switch (status) {
- case 200:
- break; /* Request is OK, skip to handling data */
- case 400:
- return callback("Invalid request", null);
- case 403:
- return callback("Private video", null);
- case 404:
- return callback("Video not found", null);
- case 500:
- case 503:
- return callback("Service unavailable", null);
- default:
- return callback("HTTP " + status, null);
- }
-
- try {
-
- var data = {};
- res.split("&").forEach(function (urlparam) {
- var pair = urlparam.split("=").map(decodeURIComponent).map(
- function (s) { return s.replace(/\+/g, ' '); });
- data[pair[0]] = pair[1];
- });
-
- if (data.hasOwnProperty("reason")) {
- var reason = data.reason;
- if (reason.indexOf("Unable to play this video at this time.") === 0) {
- reason = "There is currently a bug with Google Drive which prevents playback " +
- "of videos 1 hour long or longer.";
- } else if (reason.indexOf(
- "You must be signed in to access this video") >= 0) {
- reason = "This video is not shared properly";
- }
-
-
- return callback(reason);
- }
-
- if (!data.hasOwnProperty("title")) {
- return callback("Returned HTML is missing the video title. Are you " +
- "sure the video is done processing?");
- }
-
- if (!data.hasOwnProperty("length_seconds")) {
- return callback("Returned HTML is missing the video duration. Are you " +
- "sure the video is done processing?");
- }
-
- var title = data.title;
- var seconds = parseInt(data.length_seconds);
-
- var videos = {};
- data.fmt_stream_map.split(",").forEach(function (stream) {
- var parts = stream.split("|");
- videos[parts[0]] = parts[1];
- });
-
- var direct = {};
-
- for (var key in GOOGLE_PREFERENCE) {
- for (var i = 0; i < GOOGLE_PREFERENCE[key].length; i++) {
- var format = GOOGLE_PREFERENCE[key][i];
-
- if (format in videos) {
- direct[key] = {
- url: videos[format],
- contentType: CONTENT_TYPES[format]
- };
- break;
- }
- }
- }
-
- if (Object.keys(direct).length === 0) {
- return callback("No valid links could be extracted", null);
- }
-
- callback(null, new Media(id, title, seconds, "gd", { gpdirect: direct }));
- } catch (e) {
- return callback("Failed to parse Google Docs output", null);
- }
+ mediaquery.lookup(data).then(function (video) {
+ callback(null, convertMedia(video));
+ }).catch(function (err) {
+ callback(err.message || err);
});
},
diff --git a/player/update.coffee b/player/update.coffee
index 4a22c42f..2f8a5cfc 100644
--- a/player/update.coffee
+++ b/player/update.coffee
@@ -2,6 +2,7 @@ TYPE_MAP =
yt: YouTubePlayer
vi: VimeoPlayer
dm: DailymotionPlayer
+ gd: VideoJSPlayer
window.loadMediaPlayer = (data) ->
if data.type of TYPE_MAP
diff --git a/player/videojs.coffee b/player/videojs.coffee
index 0def6f76..ea745146 100644
--- a/player/videojs.coffee
+++ b/player/videojs.coffee
@@ -1,6 +1,121 @@
-class VideoJSPlayer extends Player
+sortSources = (sources) ->
+ if not sources
+ console.error('sortSources() called with null source list')
+ return []
+
+ qualities = ['1080', '720', '480', '360', '240']
+ pref = String(USEROPTS.default_quality)
+ idx = qualities.indexOf(pref)
+ if idx < 0
+ pref = '480'
+
+ qualityOrder = qualities.slice(idx).concat(qualities.slice(0, idx))
+ sourceOrder = []
+ flvOrder = []
+ for quality in qualityOrder
+ if quality of sources
+ flv = []
+ nonflv = []
+ sources[quality].forEach((source) ->
+ if source.contentType == 'flv'
+ flv.push(source)
+ else
+ nonflv.push(source)
+ )
+ sourceOrder = sourceOrder.concat(nonflv)
+ flvOrder = flvOrder.concat(flv)
+
+ return sourceOrder.concat(flvOrder).map((source) ->
+ type: "video/#{source.contentType}"
+ src: source.link
+ )
+
+window.VideoJSPlayer = class VideoJSPlayer extends Player
constructor: (data) ->
+ if not (this instanceof VideoJSPlayer)
+ return new VideoJSPlayer(data)
+
+ @setMediaProperties(data)
+
+ waitUntilDefined(window, 'videojs', =>
+ 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
+ # Temporary fix for race condition caused by channel playlist
+ # sending a changeMedia in onPostUserJoin before mediarefresher
+ # has refreshed it.
+ # TODO: Actually fix this on the server side rather than this
+ # hack.
+ @mediaType = null
+ return
+
+ sources.forEach((source) ->
+ $('').attr('src', source.src)
+ .attr('type', source.type)
+ .appendTo(video)
+ )
+
+ @player = videojs(video[0], autoplay: true, controls: true)
+ @player.ready(=>
+ @player.on('ended', ->
+ if CLIENT.leader
+ socket.emit('playNext')
+ )
+
+ @player.on('pause', =>
+ @paused = true
+ if CLIENT.leader
+ sendVideoUpdate()
+ )
+
+ @player.on('play', =>
+ @paused = false
+ if CLIENT.leader
+ sendVideoUpdate()
+ )
+ )
+ )
load: (data) ->
- video = $('')
- .addClass('video-js vjs-default-skin embed-responsive-item')
+ @setMediaProperties(data)
+ if @player
+ @player.src(sortSources(data.meta.direct))
+ else
+ console.log('VideoJSPlayer::load() called but @player is undefined')
+
+ play: ->
+ @paused = false
+ if @player and @player.readyState() > 0
+ @player.play()
+
+ pause: ->
+ @paused = true
+ if @player and @player.readyState() > 0
+ @player.pause()
+
+ seekTo: (time) ->
+ if @player and @player.readyState() > 0
+ @player.currentTime(time)
+
+ setVolume: (volume) ->
+ if @player and @player.readyState() > 0
+ @player.volume(volume)
+
+ getTime: (cb) ->
+ if @player and @player.readyState() > 0
+ cb(@player.currentTime())
+ else
+ cb(0)
+
+ getVolume: (cb) ->
+ if @player and @player.readyState() > 0
+ if @player.muted()
+ cb(0)
+ else
+ cb(@player.volume())
+ else
+ cb(VOLUME)
diff --git a/templates/channel.jade b/templates/channel.jade
index e016f233..b9f4800f 100644
--- a/templates/channel.jade
+++ b/templates/channel.jade
@@ -4,6 +4,7 @@ html(lang="en")
include head
mixin head()
link(href="//code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css", rel="stylesheet")
+ link(rel="stylesheet", href="https://vjs.zencdn.net/4.12/video-js.css")
body
#wrap
nav.navbar.navbar-inverse.navbar-fixed-top(role="navigation")
@@ -228,3 +229,4 @@ html(lang="en")
script(defer, src="/js/sc.js")
script(defer, src="/js/froogaloop.min.js")
script(defer, src="/js/swf.js")
+ script(defer, src="https://vjs.zencdn.net/4.12/video.js")
diff --git a/www/js/player-new.js b/www/js/player-new.js
index cc5d9cc1..b07aea11 100644
--- a/www/js/player-new.js
+++ b/www/js/player-new.js
@@ -1,5 +1,5 @@
(function() {
- var DailymotionPlayer, Player, TYPE_MAP, VimeoPlayer, YouTubePlayer,
+ var DailymotionPlayer, Player, TYPE_MAP, VideoJSPlayer, VimeoPlayer, YouTubePlayer, 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;
@@ -444,10 +444,160 @@
})(Player);
+ sortSources = function(sources) {
+ var flv, flvOrder, i, idx, len, nonflv, pref, qualities, quality, qualityOrder, sourceOrder;
+ if (!sources) {
+ console.error('sortSources() called with null source list');
+ return [];
+ }
+ qualities = ['1080', '720', '480', '360', '240'];
+ pref = String(USEROPTS.default_quality);
+ idx = qualities.indexOf(pref);
+ if (idx < 0) {
+ pref = '480';
+ }
+ qualityOrder = qualities.slice(idx).concat(qualities.slice(0, idx));
+ sourceOrder = [];
+ flvOrder = [];
+ for (i = 0, len = qualityOrder.length; i < len; i++) {
+ quality = qualityOrder[i];
+ if (quality in sources) {
+ flv = [];
+ nonflv = [];
+ sources[quality].forEach(function(source) {
+ if (source.contentType === 'flv') {
+ return flv.push(source);
+ } else {
+ return nonflv.push(source);
+ }
+ });
+ sourceOrder = sourceOrder.concat(nonflv);
+ flvOrder = flvOrder.concat(flv);
+ }
+ }
+ return sourceOrder.concat(flvOrder).map(function(source) {
+ return {
+ type: "video/" + source.contentType,
+ src: source.link
+ };
+ });
+ };
+
+ window.VideoJSPlayer = VideoJSPlayer = (function(superClass) {
+ extend(VideoJSPlayer, superClass);
+
+ function VideoJSPlayer(data) {
+ if (!(this instanceof VideoJSPlayer)) {
+ return new VideoJSPlayer(data);
+ }
+ this.setMediaProperties(data);
+ waitUntilDefined(window, 'videojs', (function(_this) {
+ return function() {
+ var sources, 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.mediaType = null;
+ return;
+ }
+ sources.forEach(function(source) {
+ return $('').attr('src', source.src).attr('type', source.type).appendTo(video);
+ });
+ _this.player = videojs(video[0], {
+ autoplay: true,
+ controls: true
+ });
+ return _this.player.ready(function() {
+ _this.player.on('ended', function() {
+ if (CLIENT.leader) {
+ return socket.emit('playNext');
+ }
+ });
+ _this.player.on('pause', function() {
+ _this.paused = true;
+ if (CLIENT.leader) {
+ return sendVideoUpdate();
+ }
+ });
+ return _this.player.on('play', function() {
+ _this.paused = false;
+ if (CLIENT.leader) {
+ return sendVideoUpdate();
+ }
+ });
+ });
+ };
+ })(this));
+ }
+
+ VideoJSPlayer.prototype.load = function(data) {
+ this.setMediaProperties(data);
+ if (this.player) {
+ return this.player.src(sortSources(data.meta.direct));
+ } else {
+ return console.log('VideoJSPlayer::load() called but @player is undefined');
+ }
+ };
+
+ VideoJSPlayer.prototype.play = function() {
+ this.paused = false;
+ if (this.player && this.player.readyState() > 0) {
+ return this.player.play();
+ }
+ };
+
+ VideoJSPlayer.prototype.pause = function() {
+ this.paused = true;
+ if (this.player && this.player.readyState() > 0) {
+ return this.player.pause();
+ }
+ };
+
+ VideoJSPlayer.prototype.seekTo = function(time) {
+ if (this.player && this.player.readyState() > 0) {
+ return this.player.currentTime(time);
+ }
+ };
+
+ VideoJSPlayer.prototype.setVolume = function(volume) {
+ if (this.player && this.player.readyState() > 0) {
+ return this.player.volume(volume);
+ }
+ };
+
+ VideoJSPlayer.prototype.getTime = function(cb) {
+ if (this.player && this.player.readyState() > 0) {
+ return cb(this.player.currentTime());
+ } else {
+ return cb(0);
+ }
+ };
+
+ VideoJSPlayer.prototype.getVolume = function(cb) {
+ if (this.player && this.player.readyState() > 0) {
+ if (this.player.muted()) {
+ return cb(0);
+ } else {
+ return cb(this.player.volume());
+ }
+ } else {
+ return cb(VOLUME);
+ }
+ };
+
+ return VideoJSPlayer;
+
+ })(Player);
+
TYPE_MAP = {
yt: YouTubePlayer,
vi: VimeoPlayer,
- dm: DailymotionPlayer
+ dm: DailymotionPlayer,
+ gd: VideoJSPlayer
};
window.loadMediaPlayer = function(data) {