From bb4816c4b39c06e587762cae5aa764932c466827 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 14 Jan 2021 00:55:31 +1300 Subject: [PATCH] Support t parameter using media fragments --- api/video.js | 9 ++++++--- pug/video.pug | 2 +- utils/converters.js | 49 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/api/video.js b/api/video.js index bee1df4..401a3d6 100644 --- a/api/video.js +++ b/api/video.js @@ -122,20 +122,23 @@ module.exports = [ route: "/watch", methods: ["GET", "POST"], upload: true, code: async ({req, url, body}) => { const user = getUser(req) const settings = user.getSettingsOrDefaults() + const id = url.searchParams.get("v") if (req.method === "GET") { - const id = url.searchParams.get("v") + const t = url.searchParams.get("t") + let mediaFragment = converters.tToMediaFragment(t) if (!settings.local) { const instanceOrigin = settings.instance const outURL = `${instanceOrigin}/api/v1/videos/${id}` const videoPromise = request(outURL).then(res => res.json()) - return renderVideo(videoPromise, {user, id, instanceOrigin}) + return renderVideo(videoPromise, {user, id, instanceOrigin}, {mediaFragment}) } else { return render(200, "pug/local-video.pug", {id}) } } else { // req.method === "POST" const video = JSON.parse(new URLSearchParams(body.toString()).get("video")) const videoPromise = Promise.resolve(video) - return renderVideo(videoPromise, {user}) + const instanceOrigin = "http://localhost:3000" + return renderVideo(videoPromise, {user, id, instanceOrigin}) } } } diff --git a/pug/video.pug b/pug/video.pug index cfe444a..4a38c2c 100644 --- a/pug/video.pug +++ b/pug/video.pug @@ -22,7 +22,7 @@ block content - const format = sortedFormatStreams[0] if format video(controls preload="auto" poster=`/vi/${video.videoId}/maxresdefault.jpg` width=format.second__width height=format.second__height data-itag=format.itag)#video.video - source(src=format.url type=format.type) + source(src=format.url+mediaFragment type=format.type) else video(src="")#video.video .stream-notice The server provided no playback streams. diff --git a/utils/converters.js b/utils/converters.js index 5f54680..f1b61bd 100644 --- a/utils/converters.js +++ b/utils/converters.js @@ -47,6 +47,55 @@ function normaliseVideoInfo(video) { } } +/** + * YT supports a time parameter t in these possible formats: + * - [digits] -> seconds + * - ([digits]:)?[digits]:[digits] -> hours?, minutes, seconds + * - ([digits]h)?([digits]m)?([digits]s)? -> hours?, minutes?, seconds + * + * Try our best to get something suitable out. Fail by returning empty + * string, meaning nothing suitable was recognised. + */ +function tToMediaFragment(t) { + let resolved = "" + + console.log(t) + if (!t || t.length > 10) { // don't parse missing values, don't pass too long strings + // skip processing + } else if (t.match(/^[0-9.,]+$/)) { + resolved = t + } else if (t.includes(":")) { + resolved = t.split(":").map(s => s.padStart(2, "0")).join(":") // need to pad each to length 2 for npt + } else if (t.match(/[hms]/)) { + let buffer = "" + let sum = 0 + const multipliers = new Map([ + ["h", 60*60], + ["m", 60], + ["s", 1] + ]) + for (const char of t) { + console.log(char) + if (char.match(/[0-9]/)) { + buffer += char + } else if (char.match(/[hms]/)) { + sum += +buffer * multipliers.get(char) + buffer = "" + } else { + buffer = "" + } + } + resolved = String(sum) + } + + if (resolved) { + return "#t=npt:" + resolved + } else { + return "" + } +} + module.exports.timeToPastText = timeToPastText module.exports.lengthSecondsToLengthText = lengthSecondsToLengthText module.exports.normaliseVideoInfo = normaliseVideoInfo +module.exports.tToMediaFragment = tToMediaFragment