From 04c9d487794d89cfc30a8072966330a498e0b945 Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Tue, 8 Aug 2017 20:35:17 -0700 Subject: [PATCH] custom-media: implement queueing and playback changes --- package.json | 2 +- player/update.coffee | 1 + player/videojs.coffee | 14 ++++++++++++++ src/channel/playlist.js | 2 +- src/custom-media.js | 29 ++++++++++++++++++++--------- src/get-info.js | 11 +++++++++++ src/utilities.js | 2 ++ test/custom-media.js | 25 ++++++++++++++++--------- www/js/player.js | 15 ++++++++++++++- www/js/util.js | 13 +++++++++++++ 10 files changed, 93 insertions(+), 21 deletions(-) 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,