mirror of https://github.com/calzoneman/sync.git
custom-media: implement queueing and playback changes
This commit is contained in:
parent
a6de8731b3
commit
04c9d48779
|
@ -2,7 +2,7 @@
|
||||||
"author": "Calvin Montgomery",
|
"author": "Calvin Montgomery",
|
||||||
"name": "CyTube",
|
"name": "CyTube",
|
||||||
"description": "Online media synchronizer and chat",
|
"description": "Online media synchronizer and chat",
|
||||||
"version": "3.44.4",
|
"version": "3.45.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"url": "http://github.com/calzoneman/sync"
|
"url": "http://github.com/calzoneman/sync"
|
||||||
},
|
},
|
||||||
|
|
|
@ -19,6 +19,7 @@ TYPE_MAP =
|
||||||
hl: HLSPlayer
|
hl: HLSPlayer
|
||||||
sb: VideoJSPlayer
|
sb: VideoJSPlayer
|
||||||
tc: VideoJSPlayer
|
tc: VideoJSPlayer
|
||||||
|
cm: VideoJSPlayer
|
||||||
|
|
||||||
window.loadMediaPlayer = (data) ->
|
window.loadMediaPlayer = (data) ->
|
||||||
try
|
try
|
||||||
|
|
|
@ -69,6 +69,9 @@ window.VideoJSPlayer = class VideoJSPlayer extends Player
|
||||||
).appendTo(video)
|
).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
|
if data.meta.gdrive_subtitles
|
||||||
data.meta.gdrive_subtitles.available.forEach((subt) ->
|
data.meta.gdrive_subtitles.available.forEach((subt) ->
|
||||||
label = subt.lang_original
|
label = subt.lang_original
|
||||||
|
@ -83,6 +86,17 @@ window.VideoJSPlayer = class VideoJSPlayer extends Player
|
||||||
).appendTo(video)
|
).appendTo(video)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if data.meta.textTracks
|
||||||
|
data.meta.textTracks.forEach((track) ->
|
||||||
|
label = track.name
|
||||||
|
$('<track/>').attr(
|
||||||
|
src: track.url
|
||||||
|
kind: 'subtitles'
|
||||||
|
type: track.type
|
||||||
|
label: label
|
||||||
|
).appendTo(video)
|
||||||
|
)
|
||||||
|
|
||||||
@player = videojs(video[0],
|
@player = videojs(video[0],
|
||||||
autoplay: true,
|
autoplay: true,
|
||||||
controls: true,
|
controls: true,
|
||||||
|
|
|
@ -386,7 +386,7 @@ PlaylistModule.prototype.handleQueue = function (user, data) {
|
||||||
id: id
|
id: id
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
} else if (type === "fi" && !perms.canAddRawFile(user)) {
|
} else if ((type === "fi" || type === "cm") && !perms.canAddRawFile(user)) {
|
||||||
user.socket.emit("queueFail", {
|
user.socket.emit("queueFail", {
|
||||||
msg: "You don't have permission to add raw video files",
|
msg: "You don't have permission to add raw video files",
|
||||||
link: link,
|
link: link,
|
||||||
|
|
|
@ -4,6 +4,9 @@ import net from 'net';
|
||||||
import Media from './media';
|
import Media from './media';
|
||||||
import { hash } from './util/hash';
|
import { hash } from './util/hash';
|
||||||
import { get as httpGet } from 'http';
|
import { get as httpGet } from 'http';
|
||||||
|
import { get as httpsGet } from 'https';
|
||||||
|
|
||||||
|
const LOGGER = require('@calzoneman/jsli')('custom-media');
|
||||||
|
|
||||||
const SOURCE_QUALITIES = new Set([
|
const SOURCE_QUALITIES = new Set([
|
||||||
240,
|
240,
|
||||||
|
@ -39,7 +42,20 @@ export function lookup(url, opts) {
|
||||||
|
|
||||||
Object.assign(options, parseURL(url));
|
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, () => {
|
req.setTimeout(opts.timeout, () => {
|
||||||
const error = new Error('Request timed out');
|
const error = new Error('Request timed out');
|
||||||
|
@ -48,6 +64,7 @@ export function lookup(url, opts) {
|
||||||
});
|
});
|
||||||
|
|
||||||
req.on('error', error => {
|
req.on('error', error => {
|
||||||
|
LOGGER.warn('Request for %s failed: %s', url, error);
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -89,11 +106,11 @@ export function lookup(url, opts) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}).then(body => {
|
}).then(body => {
|
||||||
return convert(JSON.parse(body));
|
return convert(url, JSON.parse(body));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convert(data) {
|
export function convert(id, data) {
|
||||||
validate(data);
|
validate(data);
|
||||||
|
|
||||||
if (data.live) data.duration = 0;
|
if (data.live) data.duration = 0;
|
||||||
|
@ -118,12 +135,6 @@ export function convert(data) {
|
||||||
live: !!data.live // Currently ignored by Media
|
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);
|
return new Media(id, data.title, data.duration, 'cm', meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ var GoogleDrive = require("cytube-mediaquery/lib/provider/googledrive");
|
||||||
var TwitchVOD = require("cytube-mediaquery/lib/provider/twitch-vod");
|
var TwitchVOD = require("cytube-mediaquery/lib/provider/twitch-vod");
|
||||||
var TwitchClip = require("cytube-mediaquery/lib/provider/twitch-clip");
|
var TwitchClip = require("cytube-mediaquery/lib/provider/twitch-clip");
|
||||||
import { Counter } from 'prom-client';
|
import { Counter } from 'prom-client';
|
||||||
|
import { lookup as lookupCustomMetadata } from './custom-media';
|
||||||
|
|
||||||
const LOGGER = require('@calzoneman/jsli')('get-info');
|
const LOGGER = require('@calzoneman/jsli')('get-info');
|
||||||
const lookupCounter = new Counter({
|
const lookupCounter = new Counter({
|
||||||
|
@ -539,6 +540,16 @@ var Getters = {
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
callback(err.message || err, null);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -246,6 +246,8 @@
|
||||||
return "https://streamable.com/" + id;
|
return "https://streamable.com/" + id;
|
||||||
case "tc":
|
case "tc":
|
||||||
return "https://clips.twitch.tv/" + id;
|
return "https://clips.twitch.tv/" + id;
|
||||||
|
case "cm":
|
||||||
|
return id;
|
||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
|
@ -207,9 +207,11 @@ describe('custom-media', () => {
|
||||||
|
|
||||||
describe('#convert', () => {
|
describe('#convert', () => {
|
||||||
let expected;
|
let expected;
|
||||||
|
let id = 'testing';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
expected = {
|
expected = {
|
||||||
|
id: 'testing',
|
||||||
title: 'Test Video',
|
title: 'Test Video',
|
||||||
seconds: 10,
|
seconds: 10,
|
||||||
duration: '00:10',
|
duration: '00:10',
|
||||||
|
@ -237,7 +239,6 @@ describe('custom-media', () => {
|
||||||
|
|
||||||
function cleanForComparison(actual) {
|
function cleanForComparison(actual) {
|
||||||
actual = actual.pack();
|
actual = actual.pack();
|
||||||
delete actual.id;
|
|
||||||
|
|
||||||
// Strip out extraneous undefineds
|
// Strip out extraneous undefineds
|
||||||
for (let key in actual.meta) {
|
for (let key in actual.meta) {
|
||||||
|
@ -248,10 +249,7 @@ describe('custom-media', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
it('converts custom metadata to a CyTube Media object', () => {
|
it('converts custom metadata to a CyTube Media object', () => {
|
||||||
const media = convert(valid);
|
const media = convert(id, valid);
|
||||||
|
|
||||||
assert(media.id != null, 'should have generated id');
|
|
||||||
|
|
||||||
const actual = cleanForComparison(media);
|
const actual = cleanForComparison(media);
|
||||||
|
|
||||||
assert.deepStrictEqual(actual, expected);
|
assert.deepStrictEqual(actual, expected);
|
||||||
|
@ -262,10 +260,7 @@ describe('custom-media', () => {
|
||||||
expected.duration = '00:00';
|
expected.duration = '00:00';
|
||||||
expected.seconds = 0;
|
expected.seconds = 0;
|
||||||
|
|
||||||
const media = convert(valid);
|
const media = convert(id, valid);
|
||||||
|
|
||||||
assert(media.id != null, 'should have generated id');
|
|
||||||
|
|
||||||
const actual = cleanForComparison(media);
|
const actual = cleanForComparison(media);
|
||||||
|
|
||||||
assert.deepStrictEqual(actual, expected);
|
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', () => {
|
it('rejects invalid URLs', () => {
|
||||||
return lookup('not valid').then(() => {
|
return lookup('not valid').then(() => {
|
||||||
throw new Error('Expected failure due to invalid URL');
|
throw new Error('Expected failure due to invalid URL');
|
||||||
|
|
|
@ -545,6 +545,18 @@
|
||||||
}).appendTo(video);
|
}).appendTo(video);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (data.meta.textTracks) {
|
||||||
|
data.meta.textTracks.forEach(function(track) {
|
||||||
|
var label;
|
||||||
|
label = track.name;
|
||||||
|
return $('<track/>').attr({
|
||||||
|
src: track.url,
|
||||||
|
kind: 'subtitles',
|
||||||
|
type: track.type,
|
||||||
|
label: label
|
||||||
|
}).appendTo(video);
|
||||||
|
});
|
||||||
|
}
|
||||||
_this.player = videojs(video[0], {
|
_this.player = videojs(video[0], {
|
||||||
autoplay: true,
|
autoplay: true,
|
||||||
controls: true,
|
controls: true,
|
||||||
|
@ -1529,7 +1541,8 @@
|
||||||
vm: VideoJSPlayer,
|
vm: VideoJSPlayer,
|
||||||
hl: HLSPlayer,
|
hl: HLSPlayer,
|
||||||
sb: VideoJSPlayer,
|
sb: VideoJSPlayer,
|
||||||
tc: VideoJSPlayer
|
tc: VideoJSPlayer,
|
||||||
|
cm: VideoJSPlayer
|
||||||
};
|
};
|
||||||
|
|
||||||
window.loadMediaPlayer = function(data) {
|
window.loadMediaPlayer = function(data) {
|
||||||
|
|
|
@ -56,6 +56,8 @@ function formatURL(data) {
|
||||||
return "https://streamable.com/" + data.id;
|
return "https://streamable.com/" + data.id;
|
||||||
case "tc":
|
case "tc":
|
||||||
return "https://clips.twitch.tv/" + data.id;
|
return "https://clips.twitch.tv/" + data.id;
|
||||||
|
case "cm":
|
||||||
|
return data.id;
|
||||||
default:
|
default:
|
||||||
return "#";
|
return "#";
|
||||||
}
|
}
|
||||||
|
@ -1413,6 +1415,12 @@ function parseMediaLink(url) {
|
||||||
type: "fi"
|
type: "fi"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if ((m = url.match(/^cm:(.*)/))) {
|
||||||
|
return {
|
||||||
|
id: m[1],
|
||||||
|
type: "cm"
|
||||||
|
};
|
||||||
|
}
|
||||||
// Generic for the rest.
|
// Generic for the rest.
|
||||||
if ((m = url.match(/^([a-z]{2}):([^\?&#]+)/))) {
|
if ((m = url.match(/^([a-z]{2}):([^\?&#]+)/))) {
|
||||||
return {
|
return {
|
||||||
|
@ -1430,6 +1438,11 @@ function parseMediaLink(url) {
|
||||||
msg: "Raw files must begin with 'https'. Plain http is not supported."
|
msg: "Raw files must begin with 'https'. Plain http is not supported."
|
||||||
});
|
});
|
||||||
throw new Error("ERROR_QUEUE_HTTP");
|
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)$/)) {
|
} else if (tmp.match(/\.(mp4|flv|webm|og[gv]|mp3|mov|m4a)$/)) {
|
||||||
return {
|
return {
|
||||||
id: url,
|
id: url,
|
||||||
|
|
Loading…
Reference in New Issue