diff --git a/src/routes/api/video/[filename].ts b/src/routes/api/video/[filename].ts new file mode 100644 index 00000000..2dd40df7 --- /dev/null +++ b/src/routes/api/video/[filename].ts @@ -0,0 +1,69 @@ +import { promises as fsPromises } from 'fs'; +import fs from 'fs'; +import fetch from 'node-fetch'; +import type { RequestHandler } from '@sveltejs/kit'; + +const CHUNK_SIZE = 1 * 1024 * 1024; // 1MB in bytes + +const parseRange = (range: string) => { + const [start, end] = range.replace(/bytes=/, '').split('-').map(Number); + return { start, end: end || undefined }; +}; + +const createResponse = (body: ReadableStream | Buffer, headers: Record, status = 206) => + new Response(body, { status, headers }); + +const streamRemoteFile = async (url: string, range: string) => { + const { start, end } = parseRange(range); + const response = await fetch(url, { + headers: { Range: `bytes=${start}-${end || ''}` } + }); + + return createResponse(response.body, { + 'Content-Range': response.headers.get('Content-Range') || '', + 'Accept-Ranges': 'bytes', + 'Content-Length': response.headers.get('Content-Length') || '', + 'Content-Type': response.headers.get('Content-Type') || '', + }); +}; + +const streamLocalFile = async (filePath: string, range: string) => { + const fileSize = (await fsPromises.stat(filePath)).size; + const { start, end } = parseRange(range); + const chunkEnd = end || Math.min(start + CHUNK_SIZE - 1, fileSize - 1); + + if (start >= fileSize) { + return new Response(`Requested range not satisfiable\n${start} >= ${fileSize}`, { + status: 416, + headers: { 'Content-Range': `bytes */${fileSize}` }, + }); + } + + const stream = fs.createReadStream(filePath, { start, end: chunkEnd }); + + return createResponse(stream, { + 'Content-Range': `bytes ${start}-${chunkEnd}/${fileSize}`, + 'Accept-Ranges': 'bytes', + 'Content-Length': (chunkEnd - start + 1).toString(), + 'Content-Type': 'video/mp4', + }); +}; + +export const GET: RequestHandler = async ({ params, request }) => { + const { filename } = params; + const range = request.headers.get('range'); + + if (!range) { + return new Response('Requires Range header', { status: 400 }); + } + + try { + const videoPath = filename.startsWith('http') ? filename : `static/${filename}`; + return filename.startsWith('http') ? + await streamRemoteFile(videoPath, range) : + await streamLocalFile(videoPath, range); + } catch (error) { + console.error('Error streaming video:', error); + return new Response('File not found or error streaming video', { status: 404 }); + } +}; \ No newline at end of file diff --git a/src/routes/terms-of-use/+page.svelte b/src/routes/terms-of-use/+page.svelte index 65bddf35..2555be86 100644 --- a/src/routes/terms-of-use/+page.svelte +++ b/src/routes/terms-of-use/+page.svelte @@ -165,7 +165,10 @@

You may be held accountable for damages (including costs and attorneys' fees) for misrepresentation or bad-faith claims on the infringement of any Content found on and/or through Service on your copyright. -

+
+
+ On this website we use some images on this website from FreePik. +