import { Stream } from '@/providers/streams'; import { IndividualEmbedRunnerOptions } from '@/runners/individualRunner'; import { ProviderRunnerOptions } from '@/runners/runner'; export function isValidStream(stream: Stream | undefined): boolean { if (!stream) return false; if (stream.type === 'hls') { if (!stream.playlist) return false; return true; } if (stream.type === 'file') { const validQualities = Object.values(stream.qualities).filter((v) => v.url.length > 0); if (validQualities.length === 0) return false; return true; } // unknown file type return false; } export async function validatePlayableStream( stream: Stream, ops: ProviderRunnerOptions | IndividualEmbedRunnerOptions, ): Promise { if (stream.type === 'hls') { const result = await ops.proxiedFetcher.full(stream.playlist, { method: 'GET', headers: { ...stream.preferredHeaders, ...stream.headers, }, }); if (result.statusCode < 200 || result.statusCode >= 400) return null; return stream; } if (stream.type === 'file') { const validQualitiesResults = await Promise.all( Object.values(stream.qualities).map((quality) => ops.proxiedFetcher.full(quality.url, { method: 'GET', headers: { ...stream.preferredHeaders, ...stream.headers, Range: 'bytes=0-1', }, }), ), ); // remove invalid qualities from the stream const validQualities = stream.qualities; Object.keys(stream.qualities).forEach((quality, index) => { if (validQualitiesResults[index].statusCode < 200 || validQualitiesResults[index].statusCode >= 400) { delete validQualities[quality as keyof typeof stream.qualities]; } }); if (Object.keys(validQualities).length === 0) return null; return { ...stream, qualities: validQualities }; } return null; } export async function validatePlayableStreams( streams: Stream[], ops: ProviderRunnerOptions | IndividualEmbedRunnerOptions, ): Promise { return (await Promise.all(streams.map((stream) => validatePlayableStream(stream, ops)))).filter( (v) => v !== null, ) as Stream[]; }