Skip to content

Commit 613abd8

Browse files
committed
feat(web): use media server for Loom streaming download when configured
Made-with: Cursor
1 parent 63f50e0 commit 613abd8

File tree

1 file changed

+75
-0
lines changed
  • apps/web/app/api/tools/loom-download

1 file changed

+75
-0
lines changed

apps/web/app/api/tools/loom-download/route.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { randomUUID } from "node:crypto";
22
import { type NextRequest, NextResponse } from "next/server";
3+
import {
4+
fetchConvertedVideoViaMediaServer,
5+
isMediaServerConfigured,
6+
} from "@/lib/media-client";
37
import { convertRemoteVideoToMp4Buffer } from "@/lib/video-convert";
48

59
function isHlsUrl(url: string): boolean {
@@ -14,6 +18,24 @@ function isStreamingUrl(url: string): boolean {
1418
return isHlsUrl(url) || isMpdUrl(url);
1519
}
1620

21+
function getInputExtension(url: string): string | undefined {
22+
const pathname = new URL(url).pathname.toLowerCase();
23+
24+
if (pathname.endsWith(".m3u8")) {
25+
return ".m3u8";
26+
}
27+
28+
if (pathname.endsWith(".mpd")) {
29+
return ".mpd";
30+
}
31+
32+
if (pathname.endsWith(".mp4")) {
33+
return ".mp4";
34+
}
35+
36+
return undefined;
37+
}
38+
1739
async function fetchLoomCdnUrl(
1840
videoId: string,
1941
endpoint: string,
@@ -226,6 +248,51 @@ export async function GET(request: NextRequest) {
226248
);
227249
if (mp4Result) return mp4Result;
228250

251+
if (isMediaServerConfigured()) {
252+
try {
253+
const convertedResponse = await fetchConvertedVideoViaMediaServer(
254+
cdnUrl,
255+
getInputExtension(cdnUrl),
256+
);
257+
258+
if (convertedResponse.ok && convertedResponse.body) {
259+
return new NextResponse(convertedResponse.body, {
260+
headers: {
261+
"Content-Type": "video/mp4",
262+
"Content-Disposition": `attachment; filename="${mp4Filename}"`,
263+
"Cache-Control": "no-store",
264+
},
265+
});
266+
}
267+
268+
let errorMessage = "Failed to convert streaming video";
269+
try {
270+
const errorData = (await convertedResponse.json()) as {
271+
error?: string;
272+
details?: string;
273+
};
274+
errorMessage =
275+
errorData.details ||
276+
errorData.error ||
277+
"Failed to convert streaming video";
278+
} catch {}
279+
280+
throw new Error(errorMessage);
281+
} catch (error) {
282+
if (!errorMessageShouldFallback(error)) {
283+
return NextResponse.json(
284+
{
285+
error:
286+
error instanceof Error
287+
? error.message
288+
: "Failed to convert streaming video",
289+
},
290+
{ status: 502 },
291+
);
292+
}
293+
}
294+
}
295+
229296
try {
230297
const mp4Buffer = await convertRemoteVideoToMp4Buffer(cdnUrl);
231298

@@ -248,3 +315,11 @@ export async function GET(request: NextRequest) {
248315
);
249316
}
250317
}
318+
319+
function errorMessageShouldFallback(error: unknown): boolean {
320+
if (!(error instanceof Error)) {
321+
return true;
322+
}
323+
324+
return error.message === "MEDIA_SERVER_URL is not configured";
325+
}

0 commit comments

Comments
 (0)