bunny.ts: smart generateSignedUrl checks S3 migration status (folxvideos vs Bunny fallback)
This commit is contained in:
parent
26de8896c5
commit
55df1e27f0
@ -1,4 +1,18 @@
|
|||||||
import { type Video, type InsertVideo } from "@shared/schema";
|
import { type Video, type InsertVideo } from "@shared/schema";
|
||||||
|
import { S3Client, ListObjectsV2Command } from "@aws-sdk/client-s3";
|
||||||
|
|
||||||
|
const s3 = new S3Client({
|
||||||
|
region: process.env.S3_REGION || "fsn1",
|
||||||
|
endpoint: process.env.S3_ENDPOINT || "https://fsn1.your-objectstorage.com",
|
||||||
|
forcePathStyle: true,
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: process.env.S3_ACCESS_KEY_ID || "",
|
||||||
|
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY || "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const S3_BUCKET = process.env.S3_BUCKET || "folxspeed";
|
||||||
|
const S3_FOLX_PREFIX = "folx-tv/";
|
||||||
|
|
||||||
|
|
||||||
interface BunnyVideo {
|
interface BunnyVideo {
|
||||||
guid: string;
|
guid: string;
|
||||||
@ -37,6 +51,45 @@ interface BunnyLibraryResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class BunnyService {
|
export class BunnyService {
|
||||||
|
private migratedGuids: Set<string> = new Set();
|
||||||
|
private migratedGuidsRefreshedAt: number = 0;
|
||||||
|
|
||||||
|
// Refresh list of GUIDs that exist in S3 (folx-tv/*/master.m3u8). Cached for 60s.
|
||||||
|
async refreshMigratedGuids(force: boolean = false): Promise<void> {
|
||||||
|
const ageMs = Date.now() - this.migratedGuidsRefreshedAt;
|
||||||
|
if (!force && ageMs < 60_000 && this.migratedGuids.size > 0) return;
|
||||||
|
const newSet = new Set<string>();
|
||||||
|
let continuationToken: string | undefined = undefined;
|
||||||
|
try {
|
||||||
|
do {
|
||||||
|
const cmd: any = new ListObjectsV2Command({
|
||||||
|
Bucket: S3_BUCKET,
|
||||||
|
Prefix: S3_FOLX_PREFIX,
|
||||||
|
MaxKeys: 1000,
|
||||||
|
ContinuationToken: continuationToken,
|
||||||
|
});
|
||||||
|
const res: any = await s3.send(cmd);
|
||||||
|
for (const obj of res.Contents || []) {
|
||||||
|
const k = obj.Key || "";
|
||||||
|
if (k.endsWith("/master.m3u8")) {
|
||||||
|
const guid = k.slice(S3_FOLX_PREFIX.length, -"/master.m3u8".length);
|
||||||
|
if (guid) newSet.add(guid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continuationToken = res.IsTruncated ? res.NextContinuationToken : undefined;
|
||||||
|
} while (continuationToken);
|
||||||
|
this.migratedGuids = newSet;
|
||||||
|
this.migratedGuidsRefreshedAt = Date.now();
|
||||||
|
console.log(`📦 Refreshed migrated GUIDs: ${newSet.size} videos available in S3`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to refresh migrated GUIDs:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isMigrated(videoId: string): boolean {
|
||||||
|
return this.migratedGuids.has(videoId);
|
||||||
|
}
|
||||||
|
|
||||||
private apiKey: string;
|
private apiKey: string;
|
||||||
private libraryId: string;
|
private libraryId: string;
|
||||||
private hostname: string;
|
private hostname: string;
|
||||||
@ -254,7 +307,14 @@ export class BunnyService {
|
|||||||
|
|
||||||
// Generate signed URL for private video access
|
// Generate signed URL for private video access
|
||||||
generateSignedUrl(videoId: string, expirationTime: number = 3600): string {
|
generateSignedUrl(videoId: string, expirationTime: number = 3600): string {
|
||||||
// Switched to folxvideos.b-cdn.net (S3-folxspeed origin) - public, no token required
|
// If video is migrated to S3, use folxvideos.b-cdn.net (no token).
|
||||||
return `https://folxvideos.b-cdn.net/folx-tv/${videoId}/master.m3u8`;
|
// Otherwise fall back to legacy Bunny Stream URL with token.
|
||||||
|
if (this.migratedGuids.has(videoId)) {
|
||||||
|
return `https://folxvideos.b-cdn.net/folx-tv/${videoId}/master.m3u8`;
|
||||||
|
}
|
||||||
|
const baseUrl = `https://${this.hostname}/${videoId}/playlist.m3u8`;
|
||||||
|
const expires = Math.floor(Date.now() / 1000) + expirationTime;
|
||||||
|
const token = Buffer.from(`${videoId}:${expires}:${this.apiKey.substring(0, 8)}`).toString("base64");
|
||||||
|
return `${baseUrl}?token=${token}&expires=${expires}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -23,6 +23,9 @@ class VideoSyncService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async getAllVideos(): Promise<any[]> {
|
private async getAllVideos(): Promise<any[]> {
|
||||||
|
// Refresh list of migrated GUIDs from S3 before fetching, so generateSignedUrl picks correct URL
|
||||||
|
try { await this.bunnyService.refreshMigratedGuids(); } catch {}
|
||||||
|
|
||||||
let allVideos: any[] = [];
|
let allVideos: any[] = [];
|
||||||
let page = 1;
|
let page = 1;
|
||||||
const itemsPerPage = 100; // Maximum per request
|
const itemsPerPage = 100; // Maximum per request
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user