diff --git a/package.json b/package.json
index cd34f429..a69a4a7a 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"author": "Calvin Montgomery",
"name": "CyTube",
"description": "Online media synchronizer and chat",
- "version": "3.44.4",
+ "version": "3.45.0",
"repository": {
"url": "http://github.com/calzoneman/sync"
},
diff --git a/player/update.coffee b/player/update.coffee
index 858f04a9..6f17643b 100644
--- a/player/update.coffee
+++ b/player/update.coffee
@@ -19,6 +19,7 @@ TYPE_MAP =
hl: HLSPlayer
sb: VideoJSPlayer
tc: VideoJSPlayer
+ cm: VideoJSPlayer
window.loadMediaPlayer = (data) ->
try
diff --git a/player/videojs.coffee b/player/videojs.coffee
index f3168004..5c294e1e 100644
--- a/player/videojs.coffee
+++ b/player/videojs.coffee
@@ -69,6 +69,9 @@ window.VideoJSPlayer = class VideoJSPlayer extends Player
).appendTo(video)
)
+ # TODO: Refactor VideoJSPlayer to use a preLoad()/load()/postLoad() pattern
+ # VideoJSPlayer should provide the core functionality and logic for specific
+ # dependent player types (gdrive) should be an extension
if data.meta.gdrive_subtitles
data.meta.gdrive_subtitles.available.forEach((subt) ->
label = subt.lang_original
@@ -83,6 +86,17 @@ window.VideoJSPlayer = class VideoJSPlayer extends Player
).appendTo(video)
)
+ if data.meta.textTracks
+ data.meta.textTracks.forEach((track) ->
+ label = track.name
+ $('').attr(
+ src: track.url
+ kind: 'subtitles'
+ type: track.type
+ label: label
+ ).appendTo(video)
+ )
+
@player = videojs(video[0],
autoplay: true,
controls: true,
diff --git a/src/channel/playlist.js b/src/channel/playlist.js
index ff34a960..a493b97e 100644
--- a/src/channel/playlist.js
+++ b/src/channel/playlist.js
@@ -386,7 +386,7 @@ PlaylistModule.prototype.handleQueue = function (user, data) {
id: id
});
return;
- } else if (type === "fi" && !perms.canAddRawFile(user)) {
+ } else if ((type === "fi" || type === "cm") && !perms.canAddRawFile(user)) {
user.socket.emit("queueFail", {
msg: "You don't have permission to add raw video files",
link: link,
diff --git a/src/custom-media.js b/src/custom-media.js
index 0d95c023..149f6b0d 100644
--- a/src/custom-media.js
+++ b/src/custom-media.js
@@ -4,6 +4,9 @@ import net from 'net';
import Media from './media';
import { hash } from './util/hash';
import { get as httpGet } from 'http';
+import { get as httpsGet } from 'https';
+
+const LOGGER = require('@calzoneman/jsli')('custom-media');
const SOURCE_QUALITIES = new Set([
240,
@@ -39,7 +42,20 @@ export function lookup(url, opts) {
Object.assign(options, parseURL(url));
- const req = httpGet(options);
+ if (!/^https?:$/.test(options.protocol)) {
+ reject(new ValidationError(
+ `Unacceptable protocol "${options.protocol}". Custom metadata must be`
+ + ' retrieved by HTTP or HTTPS'
+ ));
+
+ return;
+ }
+
+ LOGGER.info('Looking up %s', url);
+
+ // this is fucking stupid
+ const get = options.protocol === 'https:' ? httpsGet : httpGet;
+ const req = get(options);
req.setTimeout(opts.timeout, () => {
const error = new Error('Request timed out');
@@ -48,6 +64,7 @@ export function lookup(url, opts) {
});
req.on('error', error => {
+ LOGGER.warn('Request for %s failed: %s', url, error);
reject(error);
});
@@ -89,11 +106,11 @@ export function lookup(url, opts) {
});
});
}).then(body => {
- return convert(JSON.parse(body));
+ return convert(url, JSON.parse(body));
});
}
-export function convert(data) {
+export function convert(id, data) {
validate(data);
if (data.live) data.duration = 0;
@@ -118,12 +135,6 @@ export function convert(data) {
live: !!data.live // Currently ignored by Media
};
- const id = hash('sha256', JSON.stringify([
- data.title,
- data.duration,
- meta
- ]), 'base64');
-
return new Media(id, data.title, data.duration, 'cm', meta);
}
diff --git a/src/get-info.js b/src/get-info.js
index a6a48be6..a16fad88 100644
--- a/src/get-info.js
+++ b/src/get-info.js
@@ -14,6 +14,7 @@ var GoogleDrive = require("cytube-mediaquery/lib/provider/googledrive");
var TwitchVOD = require("cytube-mediaquery/lib/provider/twitch-vod");
var TwitchClip = require("cytube-mediaquery/lib/provider/twitch-clip");
import { Counter } from 'prom-client';
+import { lookup as lookupCustomMetadata } from './custom-media';
const LOGGER = require('@calzoneman/jsli')('get-info');
const lookupCounter = new Counter({
@@ -539,6 +540,16 @@ var Getters = {
}).catch(function (err) {
callback(err.message || err, null);
});
+ },
+
+ /* custom media - https://github.com/calzoneman/sync/issues/655 */
+ cm: async function (id, callback) {
+ try {
+ const media = await lookupCustomMetadata(id);
+ process.nextTick(callback, false, media);
+ } catch (error) {
+ process.nextTick(callback, error.message);
+ }
}
};
diff --git a/src/utilities.js b/src/utilities.js
index 523cf121..566d4f87 100644
--- a/src/utilities.js
+++ b/src/utilities.js
@@ -246,6 +246,8 @@
return "https://streamable.com/" + id;
case "tc":
return "https://clips.twitch.tv/" + id;
+ case "cm":
+ return id;
default:
return "";
}
diff --git a/test/custom-media.js b/test/custom-media.js
index 81bb3216..30d8d4e9 100644
--- a/test/custom-media.js
+++ b/test/custom-media.js
@@ -207,9 +207,11 @@ describe('custom-media', () => {
describe('#convert', () => {
let expected;
+ let id = 'testing';
beforeEach(() => {
expected = {
+ id: 'testing',
title: 'Test Video',
seconds: 10,
duration: '00:10',
@@ -237,7 +239,6 @@ describe('custom-media', () => {
function cleanForComparison(actual) {
actual = actual.pack();
- delete actual.id;
// Strip out extraneous undefineds
for (let key in actual.meta) {
@@ -248,10 +249,7 @@ describe('custom-media', () => {
}
it('converts custom metadata to a CyTube Media object', () => {
- const media = convert(valid);
-
- assert(media.id != null, 'should have generated id');
-
+ const media = convert(id, valid);
const actual = cleanForComparison(media);
assert.deepStrictEqual(actual, expected);
@@ -262,10 +260,7 @@ describe('custom-media', () => {
expected.duration = '00:00';
expected.seconds = 0;
- const media = convert(valid);
-
- assert(media.id != null, 'should have generated id');
-
+ const media = convert(id, valid);
const actual = cleanForComparison(media);
assert.deepStrictEqual(actual, expected);
@@ -404,6 +399,18 @@ describe('custom-media', () => {
});
});
+ it('rejects URLs with non-http(s) protocols', () => {
+ return lookup('ftp://127.0.0.1:10111/').then(() => {
+ throw new Error('Expected failure due to unacceptable URL protocol');
+ }).catch(error => {
+ assert.strictEqual(
+ error.message,
+ 'Unacceptable protocol "ftp:". Custom metadata must be retrieved'
+ + ' by HTTP or HTTPS'
+ );
+ });
+ });
+
it('rejects invalid URLs', () => {
return lookup('not valid').then(() => {
throw new Error('Expected failure due to invalid URL');
diff --git a/www/js/player.js b/www/js/player.js
index 4b6a4c27..fccd7573 100644
--- a/www/js/player.js
+++ b/www/js/player.js
@@ -545,6 +545,18 @@
}).appendTo(video);
});
}
+ if (data.meta.textTracks) {
+ data.meta.textTracks.forEach(function(track) {
+ var label;
+ label = track.name;
+ return $('').attr({
+ src: track.url,
+ kind: 'subtitles',
+ type: track.type,
+ label: label
+ }).appendTo(video);
+ });
+ }
_this.player = videojs(video[0], {
autoplay: true,
controls: true,
@@ -1529,7 +1541,8 @@
vm: VideoJSPlayer,
hl: HLSPlayer,
sb: VideoJSPlayer,
- tc: VideoJSPlayer
+ tc: VideoJSPlayer,
+ cm: VideoJSPlayer
};
window.loadMediaPlayer = function(data) {
diff --git a/www/js/util.js b/www/js/util.js
index f2a40145..0330637f 100644
--- a/www/js/util.js
+++ b/www/js/util.js
@@ -56,6 +56,8 @@ function formatURL(data) {
return "https://streamable.com/" + data.id;
case "tc":
return "https://clips.twitch.tv/" + data.id;
+ case "cm":
+ return data.id;
default:
return "#";
}
@@ -1413,6 +1415,12 @@ function parseMediaLink(url) {
type: "fi"
};
}
+ if ((m = url.match(/^cm:(.*)/))) {
+ return {
+ id: m[1],
+ type: "cm"
+ };
+ }
// Generic for the rest.
if ((m = url.match(/^([a-z]{2}):([^\?]+)/))) {
return {
@@ -1430,6 +1438,11 @@ function parseMediaLink(url) {
msg: "Raw files must begin with 'https'. Plain http is not supported."
});
throw new Error("ERROR_QUEUE_HTTP");
+ } else if (tmp.match(/\.json$/)) {
+ return {
+ id: url,
+ type: "cm"
+ };
} else if (tmp.match(/\.(mp4|flv|webm|og[gv]|mp3|mov|m4a)$/)) {
return {
id: url,