RSS: parallel-fetch the stream urls

## Issue
Users are seeing timeout with the RSS calls.
But oddly, odysee.com is significantly slower than the dev instances.
This commit is contained in:
infinite-persistence 2022-05-27 14:23:22 +08:00 committed by Thomas Zarebczan
parent 7504cf07b3
commit b8cf1a6c4c

View file

@ -62,37 +62,54 @@ function encodeWithSpecialCharEncode(string) {
return encodeURIComponent(string).replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29'); return encodeURIComponent(string).replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29');
} }
async function generateEnclosureForClaimContent(claim) { /**
* Returns an array of stream-url promise results corresponding to the same
* order as the given 'claims' array, or null if there is an error.
*
* Clients must check the promise 'status' for each entry, as some could be a
* failed fetch (i.e. 'rejected').
*
* @param claims Array of freaking claims.
* @returns {Array<{status, value}> | null}
*/
async function fetchStreamUrls(claims) {
return Promise.allSettled(claims.map((c) => fetchStreamUrl(c.name, c.claim_id)))
.then((results) => results)
.catch(() => null);
}
async function generateEnclosureForClaimContent(claim, streamUrl) {
const value = claim.value; const value = claim.value;
if (!value || !value.stream_type) { if (!value || !value.stream_type) {
return undefined; return undefined;
} }
const fileExt = value.source && value.source.media_type && '.' + Mime.extension(value.source.media_type); const fileExt = value.source && value.source.media_type && '.' + Mime.extension(value.source.media_type);
switch (value.stream_type) { switch (value.stream_type) {
case 'video': case 'video':
return { return {
url: (await fetchStreamUrl(claim.name, claim.claim_id)).replace('/v4/', '/v3/') + (fileExt || '.mp4'), // v3 = mp4 always, v4 may redirect to m3u8 url: streamUrl.replace('/v4/', '/v3/') + (fileExt || '.mp4'), // v3 = mp4 always, v4 may redirect to m3u8
type: (value.source && value.source.media_type) || 'video/mp4', type: (value.source && value.source.media_type) || 'video/mp4',
size: (value.source && value.source.size) || 0, // Per spec, 0 is a valid fallback. size: (value.source && value.source.size) || 0, // Per spec, 0 is a valid fallback.
}; };
case 'audio': case 'audio':
return { return {
url: (await fetchStreamUrl(claim.name, claim.claim_id)) + ((fileExt === '.mpga' ? '.mp3' : fileExt) || '.mp3'), url: streamUrl + ((fileExt === '.mpga' ? '.mp3' : fileExt) || '.mp3'),
type: (value.source && value.source.media_type) || 'audio/mpeg', type: (value.source && value.source.media_type) || 'audio/mpeg',
size: (value.source && value.source.size) || 0, // Per spec, 0 is a valid fallback. size: (value.source && value.source.size) || 0, // Per spec, 0 is a valid fallback.
}; };
case 'image': case 'image':
return { return {
url: (await fetchStreamUrl(claim.name, claim.claim_id)) + (fileExt || '.jpeg'), url: streamUrl + (fileExt || '.jpeg'),
type: (value.source && value.source.media_type) || 'image/jpeg', type: (value.source && value.source.media_type) || 'image/jpeg',
size: (value.source && value.source.size) || 0, // Per spec, 0 is a valid fallback. size: (value.source && value.source.size) || 0, // Per spec, 0 is a valid fallback.
}; };
case 'document': case 'document':
case 'software': case 'software':
return { return {
url: await fetchStreamUrl(claim.name, claim.claim_id), url: streamUrl,
type: (value.source && value.source.media_type) || undefined, type: (value.source && value.source.media_type) || undefined,
size: (value.source && value.source.size) || 0, // Per spec, 0 is a valid fallback. size: (value.source && value.source.size) || 0, // Per spec, 0 is a valid fallback.
}; };
@ -174,7 +191,7 @@ const getItunesCategory = (claim) => {
// "Note: Although you can specify more than one category and subcategory // "Note: Although you can specify more than one category and subcategory
// in your RSS feed, Apple Podcasts only recognizes the first category and // in your RSS feed, Apple Podcasts only recognizes the first category and
// subcategory." // subcategory."
// --> The only parse the first found tag. // --> They only parse the first found tag.
return itunesCategory.replace('&', '&amp;'); return itunesCategory.replace('&', '&amp;');
} }
} }
@ -244,6 +261,9 @@ async function generateFeed(feedLink, channelClaim, claimsInChannel) {
], ],
}); });
// --- Parallel pre-fetch of stream url ---
const streamUrls = await fetchStreamUrls(claimsInChannel);
// --- Content --- // --- Content ---
for (let i = 0; i < claimsInChannel.length; ++i) { for (let i = 0; i < claimsInChannel.length; ++i) {
const c = claimsInChannel[i]; const c = claimsInChannel[i];
@ -258,6 +278,9 @@ async function generateFeed(feedLink, channelClaim, claimsInChannel) {
const date = const date =
c.value && c.value.release_time ? c.value.release_time * 1000 : c.meta && c.meta.creation_timestamp * 1000; c.value && c.value.release_time ? c.value.release_time * 1000 : c.meta && c.meta.creation_timestamp * 1000;
const claimStreamUrl = streamUrls ? streamUrls[i] : '';
const streamUrl = claimStreamUrl.status === 'fulfilled' ? claimStreamUrl.value : '';
feed.item({ feed.item({
title: title, title: title,
description: description, description: description,
@ -265,7 +288,7 @@ async function generateFeed(feedLink, channelClaim, claimsInChannel) {
guid: undefined, // defaults to 'url' guid: undefined, // defaults to 'url'
author: undefined, // defaults feed author property author: undefined, // defaults feed author property
date: new Date(date), date: new Date(date),
enclosure: await generateEnclosureForClaimContent(c), enclosure: await generateEnclosureForClaimContent(c, streamUrl),
custom_elements: [ custom_elements: [
{ 'itunes:title': title }, { 'itunes:title': title },
{ 'itunes:author': channelTitle }, { 'itunes:author': channelTitle },