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 { 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 {
|
||||
guid: string;
|
||||
@ -37,6 +51,45 @@ interface BunnyLibraryResponse {
|
||||
}
|
||||
|
||||
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 libraryId: string;
|
||||
private hostname: string;
|
||||
@ -254,7 +307,14 @@ export class BunnyService {
|
||||
|
||||
// Generate signed URL for private video access
|
||||
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).
|
||||
// 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[]> {
|
||||
// Refresh list of migrated GUIDs from S3 before fetching, so generateSignedUrl picks correct URL
|
||||
try { await this.bunnyService.refreshMigratedGuids(); } catch {}
|
||||
|
||||
let allVideos: any[] = [];
|
||||
let page = 1;
|
||||
const itemsPerPage = 100; // Maximum per request
|
||||
|
||||
Loading…
Reference in New Issue
Block a user