@thestatic-tv/dcl-sdk 2.5.10 → 2.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -38,11 +38,13 @@ __export(index_exports, {
38
38
  InteractionsModule: () => InteractionsModule,
39
39
  KEY_TYPE_CHANNEL: () => KEY_TYPE_CHANNEL,
40
40
  KEY_TYPE_SCENE: () => KEY_TYPE_SCENE,
41
+ NotificationBanner: () => NotificationBanner,
41
42
  SessionModule: () => SessionModule,
42
43
  StaticTVClient: () => StaticTVClient,
43
44
  fetchUserData: () => fetchUserData,
44
45
  getPlayerDisplayName: () => getPlayerDisplayName,
45
46
  getPlayerWallet: () => getPlayerWallet,
47
+ hideNotification: () => hideNotification,
46
48
  setupStaticUI: () => setupStaticUI,
47
49
  showNotification: () => showNotification
48
50
  });
@@ -668,6 +670,17 @@ var UI_DIMENSIONS = {
668
670
  buttonHeightSmall: 30,
669
671
  inputHeight: 36
670
672
  },
673
+ // Notification banner - next to DCL native chat panel
674
+ notification: {
675
+ width: 340,
676
+ height: 44,
677
+ bottom: 55,
678
+ // Above the chat input area
679
+ left: 400,
680
+ // Right of DCL native chat (chat is ~380px wide)
681
+ borderWidth: 2,
682
+ fontSize: 14
683
+ },
671
684
  // Shared
672
685
  closeButton: {
673
686
  size: 40,
@@ -2295,12 +2308,131 @@ var ChatUIModule = class {
2295
2308
  };
2296
2309
 
2297
2310
  // src/ui/admin-panel-ui.tsx
2298
- var import_react_ecs4 = __toESM(require("@dcl/sdk/react-ecs"));
2311
+ var import_react_ecs5 = __toESM(require("@dcl/sdk/react-ecs"));
2299
2312
  var import_math5 = require("@dcl/sdk/math");
2300
2313
  var import_players2 = require("@dcl/sdk/players");
2301
2314
  var import_RestrictedActions3 = require("~system/RestrictedActions");
2315
+
2316
+ // src/ui/ui-renderer.tsx
2317
+ var import_react_ecs4 = __toESM(require("@dcl/sdk/react-ecs"));
2318
+ var import_ecs2 = require("@dcl/sdk/ecs");
2319
+ var notificationText = "";
2320
+ var notificationVisible = false;
2321
+ var notificationEndTime = 0;
2322
+ var notificationInitialized = false;
2323
+ var C = THEME.colors;
2324
+ var N = UI_DIMENSIONS.notification;
2325
+ function showNotification(message, durationMs = 5e3) {
2326
+ notificationText = message;
2327
+ notificationVisible = true;
2328
+ notificationEndTime = Date.now() + durationMs;
2329
+ }
2330
+ function hideNotification() {
2331
+ notificationVisible = false;
2332
+ }
2333
+ function initNotificationSystem() {
2334
+ if (notificationInitialized) return;
2335
+ notificationInitialized = true;
2336
+ import_ecs2.engine.addSystem(() => {
2337
+ if (notificationVisible && Date.now() > notificationEndTime) {
2338
+ notificationVisible = false;
2339
+ }
2340
+ });
2341
+ }
2342
+ function NotificationBanner() {
2343
+ initNotificationSystem();
2344
+ if (!notificationVisible) return null;
2345
+ return /* @__PURE__ */ import_react_ecs4.default.createElement(
2346
+ import_react_ecs4.UiEntity,
2347
+ {
2348
+ uiTransform: {
2349
+ positionType: "absolute",
2350
+ position: { bottom: N.bottom, left: N.left },
2351
+ width: N.width,
2352
+ height: N.height,
2353
+ flexDirection: "row",
2354
+ alignItems: "center"
2355
+ },
2356
+ uiBackground: { color: C.panel }
2357
+ },
2358
+ /* @__PURE__ */ import_react_ecs4.default.createElement(
2359
+ import_react_ecs4.UiEntity,
2360
+ {
2361
+ uiTransform: {
2362
+ width: N.borderWidth,
2363
+ height: "100%"
2364
+ },
2365
+ uiBackground: { color: C.cyan }
2366
+ }
2367
+ ),
2368
+ /* @__PURE__ */ import_react_ecs4.default.createElement(
2369
+ import_react_ecs4.UiEntity,
2370
+ {
2371
+ uiTransform: {
2372
+ flexGrow: 1,
2373
+ height: "100%",
2374
+ padding: { left: 12, right: 12 },
2375
+ justifyContent: "center",
2376
+ alignItems: "flex-start"
2377
+ }
2378
+ },
2379
+ /* @__PURE__ */ import_react_ecs4.default.createElement(
2380
+ import_react_ecs4.Label,
2381
+ {
2382
+ value: notificationText,
2383
+ fontSize: N.fontSize,
2384
+ color: C.white,
2385
+ textAlign: "middle-left"
2386
+ }
2387
+ )
2388
+ ),
2389
+ /* @__PURE__ */ import_react_ecs4.default.createElement(
2390
+ import_react_ecs4.UiEntity,
2391
+ {
2392
+ uiTransform: {
2393
+ width: 6,
2394
+ height: 6,
2395
+ margin: { right: 12 }
2396
+ },
2397
+ uiBackground: { color: C.cyan }
2398
+ }
2399
+ )
2400
+ );
2401
+ }
2402
+ function createStaticUI(client) {
2403
+ initNotificationSystem();
2404
+ return function StaticUI() {
2405
+ const currentScale = client.uiScale;
2406
+ const guideComponent = client.guideUI?.getComponent() ?? null;
2407
+ const chatComponent = client.chatUI?.getComponent() ?? null;
2408
+ const adminComponent = client.adminPanel?.getComponent() ?? null;
2409
+ return /* @__PURE__ */ import_react_ecs4.default.createElement(
2410
+ import_react_ecs4.UiEntity,
2411
+ {
2412
+ key: `static-ui-root-${currentScale}`,
2413
+ uiTransform: {
2414
+ width: "100%",
2415
+ height: "100%",
2416
+ positionType: "absolute",
2417
+ flexDirection: "column",
2418
+ alignItems: "center"
2419
+ }
2420
+ },
2421
+ /* @__PURE__ */ import_react_ecs4.default.createElement(NotificationBanner, null),
2422
+ guideComponent,
2423
+ chatComponent,
2424
+ adminComponent
2425
+ );
2426
+ };
2427
+ }
2428
+ function setupStaticUI(client) {
2429
+ const StaticUI = createStaticUI(client);
2430
+ import_react_ecs4.ReactEcsRenderer.setUiRenderer(StaticUI);
2431
+ }
2432
+
2433
+ // src/ui/admin-panel-ui.tsx
2302
2434
  var BAN_KICK_POSITION = import_math5.Vector3.create(16, -50, 16);
2303
- var C = {
2435
+ var C2 = {
2304
2436
  bg: import_math5.Color4.create(0.08, 0.08, 0.12, 0.98),
2305
2437
  header: import_math5.Color4.create(0.9, 0.15, 0.15, 1),
2306
2438
  tabActive: import_math5.Color4.create(0, 0.7, 0.7, 1),
@@ -2341,6 +2473,8 @@ var AdminPanelUIModule = class {
2341
2473
  this.streamControlling = false;
2342
2474
  this.streamControlStatus = "";
2343
2475
  this.pollIntervalId = null;
2476
+ this.commandPollIntervalId = null;
2477
+ this.lastCommandTimestamp = 0;
2344
2478
  this.trialClaiming = false;
2345
2479
  this.trialClaimError = "";
2346
2480
  // Mod tab state
@@ -2352,22 +2486,22 @@ var AdminPanelUIModule = class {
2352
2486
  this.modStatus = "";
2353
2487
  this.modsFetched = false;
2354
2488
  // --- UI Components ---
2355
- this.SectionHead = ({ label, color }) => /* @__PURE__ */ import_react_ecs4.default.createElement(
2356
- import_react_ecs4.UiEntity,
2489
+ this.SectionHead = ({ label, color }) => /* @__PURE__ */ import_react_ecs5.default.createElement(
2490
+ import_react_ecs5.UiEntity,
2357
2491
  {
2358
2492
  uiTransform: { width: "100%", height: this.s(UI_DIMENSIONS.admin.sectionHeadHeight), margin: { bottom: 8 }, padding: { left: 10 }, alignItems: "center" },
2359
2493
  uiBackground: { color: import_math5.Color4.create(color.r * 0.3, color.g * 0.3, color.b * 0.3, 0.5) }
2360
2494
  },
2361
- /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: label, fontSize: this.theme.sectionHead, color })
2495
+ /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Label, { value: label, fontSize: this.theme.sectionHead, color })
2362
2496
  );
2363
- this.TabBtn = ({ label, tab }) => /* @__PURE__ */ import_react_ecs4.default.createElement(
2364
- import_react_ecs4.Button,
2497
+ this.TabBtn = ({ label, tab }) => /* @__PURE__ */ import_react_ecs5.default.createElement(
2498
+ import_react_ecs5.Button,
2365
2499
  {
2366
2500
  uiTransform: { flexGrow: 1, height: this.s(UI_DIMENSIONS.admin.tabHeight), justifyContent: "center", alignItems: "center" },
2367
- uiBackground: { color: this.activeTab === tab ? C.tabActive : C.tabInactive },
2501
+ uiBackground: { color: this.activeTab === tab ? C2.tabActive : C2.tabInactive },
2368
2502
  value: label,
2369
2503
  fontSize: this.theme.tabButton,
2370
- color: this.activeTab === tab ? import_math5.Color4.Black() : C.text,
2504
+ color: this.activeTab === tab ? import_math5.Color4.Black() : C2.text,
2371
2505
  textAlign: "middle-center",
2372
2506
  onMouseDown: () => this.setActiveTab(tab)
2373
2507
  }
@@ -2377,365 +2511,368 @@ var AdminPanelUIModule = class {
2377
2511
  this.fetchStreamData();
2378
2512
  }
2379
2513
  const t = this.theme;
2380
- return /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column", width: "100%", padding: 10 } }, !this.streamData?.hasChannel && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom: 14 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(this.SectionHead, { label: "LIVE STREAM", color: C.btn }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "No streaming channel linked", fontSize: t.labelSmall, color: C.textDim, uiTransform: { margin: { bottom: 8 } } }), this.isOwner && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column" } }, this.streamData?.trialAvailable && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom: 10 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(
2381
- import_react_ecs4.Button,
2514
+ return /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { flexDirection: "column", width: "100%", padding: 10 } }, !this.streamData?.hasChannel && /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom: 14 } } }, /* @__PURE__ */ import_react_ecs5.default.createElement(this.SectionHead, { label: "LIVE STREAM", color: C2.btn }), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Label, { value: "No streaming channel linked", fontSize: t.labelSmall, color: C2.textDim, uiTransform: { margin: { bottom: 8 } } }), this.isOwner && /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { flexDirection: "column" } }, this.streamData?.trialAvailable && /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom: 10 } } }, /* @__PURE__ */ import_react_ecs5.default.createElement(
2515
+ import_react_ecs5.Button,
2382
2516
  {
2383
2517
  uiTransform: { width: this.s(200), height: this.s(UI_DIMENSIONS.admin.buttonHeight), margin: { bottom: 6 } },
2384
- uiBackground: { color: this.trialClaiming ? C.btn : C.green },
2518
+ uiBackground: { color: this.trialClaiming ? C2.btn : C2.green },
2385
2519
  value: this.trialClaiming ? "Claiming..." : "Start Free 4-Hour Trial",
2386
2520
  fontSize: t.button,
2387
- color: C.text,
2521
+ color: C2.text,
2388
2522
  onMouseDown: () => this.claimTrial()
2389
2523
  }
2390
- ), this.trialClaimError && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: this.trialClaimError, fontSize: t.status, color: C.red }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "One-time trial \u2022 4 hours of streaming", fontSize: t.labelSmall, color: C.textDim, uiTransform: { margin: { top: 4 } } })), !this.streamData?.trialAvailable && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column" } }, /* @__PURE__ */ import_react_ecs4.default.createElement(
2391
- import_react_ecs4.Button,
2524
+ ), this.trialClaimError && /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Label, { value: this.trialClaimError, fontSize: t.status, color: C2.red }), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Label, { value: "One-time trial \u2022 4 hours of streaming", fontSize: t.labelSmall, color: C2.textDim, uiTransform: { margin: { top: 4 } } })), !this.streamData?.trialAvailable && /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { flexDirection: "column" } }, /* @__PURE__ */ import_react_ecs5.default.createElement(
2525
+ import_react_ecs5.Button,
2392
2526
  {
2393
2527
  uiTransform: { width: this.s(170), height: this.s(UI_DIMENSIONS.admin.buttonHeight), margin: { bottom: 6 } },
2394
- uiBackground: { color: this.channelCreating ? C.btn : C.cyan },
2528
+ uiBackground: { color: this.channelCreating ? C2.btn : C2.cyan },
2395
2529
  value: this.channelCreating ? "Creating..." : "+ Create Channel",
2396
2530
  fontSize: t.button,
2397
- color: C.text,
2531
+ color: C2.text,
2398
2532
  onMouseDown: () => this.createChannel()
2399
2533
  }
2400
- ), this.channelCreateError && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: this.channelCreateError, fontSize: t.status, color: C.red }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "Relay tier \u2022 $25/mo \u2022 8 hours streaming", fontSize: t.labelSmall, color: C.textDim, uiTransform: { margin: { top: 4 } } })))), this.streamData?.hasChannel && this.isOwner && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom: 14 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(this.SectionHead, { label: this.streamData.isLive ? "LIVE STREAM" : "STREAM SETTINGS", color: this.streamData.isLive ? C.red : C.yellow }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 8 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(
2401
- import_react_ecs4.Label,
2534
+ ), this.channelCreateError && /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Label, { value: this.channelCreateError, fontSize: t.status, color: C2.red }), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Label, { value: "Relay tier \u2022 $25/mo \u2022 8 hours streaming", fontSize: t.labelSmall, color: C2.textDim, uiTransform: { margin: { top: 4 } } })))), this.streamData?.hasChannel && this.isOwner && /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom: 14 } } }, /* @__PURE__ */ import_react_ecs5.default.createElement(this.SectionHead, { label: this.streamData.isLive ? "LIVE STREAM" : "STREAM SETTINGS", color: this.streamData.isLive ? C2.red : C2.yellow }), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 8 } } }, /* @__PURE__ */ import_react_ecs5.default.createElement(
2535
+ import_react_ecs5.Label,
2402
2536
  {
2403
2537
  value: this.streamData.isLive ? `LIVE \u2022 ${this.streamData.currentViewers || 0} viewers` : "OFFLINE",
2404
2538
  fontSize: t.label,
2405
- color: this.streamData.isLive ? C.red : C.textDim
2539
+ color: this.streamData.isLive ? C2.red : C2.textDim
2406
2540
  }
2407
- ), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: ` \u2022 ${this.streamData.tier?.toUpperCase() || "RELAY"}`, fontSize: t.labelSmall, color: C.cyan }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: ` \u2022 ${this.streamData.sparksBalance || 0} Sparks`, fontSize: t.labelSmall, color: C.textDim })), /* @__PURE__ */ import_react_ecs4.default.createElement(
2408
- import_react_ecs4.Label,
2541
+ ), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Label, { value: ` \u2022 ${this.streamData.tier?.toUpperCase() || "RELAY"}`, fontSize: t.labelSmall, color: C2.cyan }), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Label, { value: ` \u2022 ${this.streamData.sparksBalance || 0} Sparks`, fontSize: t.labelSmall, color: C2.textDim })), /* @__PURE__ */ import_react_ecs5.default.createElement(
2542
+ import_react_ecs5.Label,
2409
2543
  {
2410
2544
  value: `Channel: ${this.streamData.channelName || this.streamData.channelId}`,
2411
2545
  fontSize: t.labelSmall,
2412
- color: C.textDim,
2546
+ color: C2.textDim,
2413
2547
  uiTransform: { margin: { bottom: 10 } }
2414
2548
  }
2415
- ), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom: 6 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "RTMP Server:", fontSize: t.labelSmall, color: C.textDim }), /* @__PURE__ */ import_react_ecs4.default.createElement(
2416
- import_react_ecs4.Label,
2549
+ ), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom: 6 } } }, /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Label, { value: "RTMP Server:", fontSize: t.labelSmall, color: C2.textDim }), /* @__PURE__ */ import_react_ecs5.default.createElement(
2550
+ import_react_ecs5.Label,
2417
2551
  {
2418
2552
  value: this.streamData.rtmpUrl || "Loading...",
2419
2553
  fontSize: t.label,
2420
- color: this.streamData.rtmpUrl ? C.text : C.textDim,
2554
+ color: this.streamData.rtmpUrl ? C2.text : C2.textDim,
2421
2555
  uiTransform: { margin: { left: 6 } }
2422
2556
  }
2423
- )), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom: 6 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "Stream Key:", fontSize: t.labelSmall, color: C.textDim }), /* @__PURE__ */ import_react_ecs4.default.createElement(
2424
- import_react_ecs4.Label,
2557
+ )), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom: 6 } } }, /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Label, { value: "Stream Key:", fontSize: t.labelSmall, color: C2.textDim }), /* @__PURE__ */ import_react_ecs5.default.createElement(
2558
+ import_react_ecs5.Label,
2425
2559
  {
2426
2560
  value: this.streamData.streamKey || "Loading...",
2427
2561
  fontSize: t.label,
2428
- color: this.streamData.streamKey ? C.cyan : C.textDim,
2562
+ color: this.streamData.streamKey ? C2.cyan : C2.textDim,
2429
2563
  uiTransform: { margin: { left: 6 } }
2430
2564
  }
2431
- )), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom: 10 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "HLS Playback:", fontSize: t.labelSmall, color: C.textDim }), /* @__PURE__ */ import_react_ecs4.default.createElement(
2432
- import_react_ecs4.Label,
2565
+ )), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom: 10 } } }, /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Label, { value: "HLS Playback:", fontSize: t.labelSmall, color: C2.textDim }), /* @__PURE__ */ import_react_ecs5.default.createElement(
2566
+ import_react_ecs5.Label,
2433
2567
  {
2434
2568
  value: this.streamData.hlsUrl || "Not available",
2435
2569
  fontSize: t.label,
2436
- color: this.streamData.hlsUrl ? C.green : C.textDim,
2570
+ color: this.streamData.hlsUrl ? C2.green : C2.textDim,
2437
2571
  uiTransform: { margin: { left: 6 } }
2438
2572
  }
2439
- )), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 6 } } }, !this.streamData.isLive && /* @__PURE__ */ import_react_ecs4.default.createElement(
2440
- import_react_ecs4.Button,
2573
+ )), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 6 } } }, !this.streamData.isLive && /* @__PURE__ */ import_react_ecs5.default.createElement(
2574
+ import_react_ecs5.Button,
2441
2575
  {
2442
2576
  uiTransform: { width: this.s(110), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2443
- uiBackground: { color: this.streamControlling ? C.btn : C.green },
2577
+ uiBackground: { color: this.streamControlling ? C2.btn : C2.green },
2444
2578
  value: this.streamControlling ? "Starting..." : "Start Stream",
2445
2579
  fontSize: t.buttonSmall,
2446
- color: C.text,
2580
+ color: C2.text,
2447
2581
  onMouseDown: () => this.startStream()
2448
2582
  }
2449
- ), this.streamData.isLive && /* @__PURE__ */ import_react_ecs4.default.createElement(
2450
- import_react_ecs4.Button,
2583
+ ), this.streamData.isLive && /* @__PURE__ */ import_react_ecs5.default.createElement(
2584
+ import_react_ecs5.Button,
2451
2585
  {
2452
2586
  uiTransform: { width: this.s(110), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2453
- uiBackground: { color: this.streamControlling ? C.btn : C.red },
2587
+ uiBackground: { color: this.streamControlling ? C2.btn : C2.red },
2454
2588
  value: this.streamControlling ? "Stopping..." : "Stop Stream",
2455
2589
  fontSize: t.buttonSmall,
2456
- color: C.text,
2590
+ color: C2.text,
2457
2591
  onMouseDown: () => this.stopStream()
2458
2592
  }
2459
- ), this.streamData.isLive && this.streamData.hlsUrl && /* @__PURE__ */ import_react_ecs4.default.createElement(
2460
- import_react_ecs4.Button,
2593
+ ), this.streamData.isLive && this.streamData.hlsUrl && /* @__PURE__ */ import_react_ecs5.default.createElement(
2594
+ import_react_ecs5.Button,
2461
2595
  {
2462
2596
  uiTransform: { width: this.s(100), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2463
- uiBackground: { color: C.cyan },
2597
+ uiBackground: { color: C2.cyan },
2464
2598
  value: "Play on Screen",
2465
2599
  fontSize: t.buttonSmall,
2466
- color: C.text,
2600
+ color: C2.text,
2467
2601
  onMouseDown: () => this.config.onVideoPlay?.(this.streamData.hlsUrl)
2468
2602
  }
2469
- )), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 6 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(
2470
- import_react_ecs4.Button,
2603
+ )), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 6 } } }, /* @__PURE__ */ import_react_ecs5.default.createElement(
2604
+ import_react_ecs5.Button,
2471
2605
  {
2472
2606
  uiTransform: { width: this.s(80), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2473
- uiBackground: { color: C.btn },
2607
+ uiBackground: { color: C2.btn },
2474
2608
  value: "Refresh",
2475
2609
  fontSize: t.buttonSmall,
2476
- color: C.text,
2610
+ color: C2.text,
2477
2611
  onMouseDown: () => this.refreshStreamStatus()
2478
2612
  }
2479
- ), /* @__PURE__ */ import_react_ecs4.default.createElement(
2480
- import_react_ecs4.Button,
2613
+ ), /* @__PURE__ */ import_react_ecs5.default.createElement(
2614
+ import_react_ecs5.Button,
2481
2615
  {
2482
2616
  uiTransform: { width: this.s(95), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2483
- uiBackground: { color: this.keyRotating ? C.btn : C.yellow },
2617
+ uiBackground: { color: this.keyRotating ? C2.btn : C2.yellow },
2484
2618
  value: this.keyRotating ? "..." : "Rotate Key",
2485
2619
  fontSize: t.buttonSmall,
2486
- color: C.text,
2620
+ color: C2.text,
2487
2621
  onMouseDown: () => this.rotateStreamKey()
2488
2622
  }
2489
- ), !this.streamData.isLive && /* @__PURE__ */ import_react_ecs4.default.createElement(
2490
- import_react_ecs4.Button,
2623
+ ), !this.streamData.isLive && /* @__PURE__ */ import_react_ecs5.default.createElement(
2624
+ import_react_ecs5.Button,
2491
2625
  {
2492
2626
  uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2493
- uiBackground: { color: this.channelDeleting ? C.btn : C.red },
2627
+ uiBackground: { color: this.channelDeleting ? C2.btn : C2.red },
2494
2628
  value: this.channelDeleting ? "..." : "Delete",
2495
2629
  fontSize: t.buttonSmall,
2496
- color: C.text,
2630
+ color: C2.text,
2497
2631
  onMouseDown: () => this.deleteChannel()
2498
2632
  }
2499
- )), this.streamControlStatus === "started" && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "Stream started - begin broadcasting in OBS", fontSize: t.status, color: C.green }), this.streamControlStatus === "stopped" && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "Stream stopped", fontSize: t.status, color: C.textDim }), this.keyRotateStatus === "success" && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "Key rotated! Update OBS", fontSize: t.status, color: C.green }), this.keyRotateStatus === "error" && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "Failed to rotate key", fontSize: t.status, color: C.red }), this.channelDeleteError && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: this.channelDeleteError, fontSize: t.status, color: C.red }), this.streamControlStatus === "error" && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "Stream control failed", fontSize: t.status, color: C.red })), /* @__PURE__ */ import_react_ecs4.default.createElement(this.SectionHead, { label: "PLAY NOW", color: C.orange }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 14 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(
2500
- import_react_ecs4.Input,
2633
+ )), this.streamControlStatus === "started" && /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Label, { value: "Stream started - begin broadcasting in OBS", fontSize: t.status, color: C2.green }), this.streamControlStatus === "stopped" && /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Label, { value: "Stream stopped", fontSize: t.status, color: C2.textDim }), this.keyRotateStatus === "success" && /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Label, { value: "Key rotated! Update OBS", fontSize: t.status, color: C2.green }), this.keyRotateStatus === "error" && /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Label, { value: "Failed to rotate key", fontSize: t.status, color: C2.red }), this.channelDeleteError && /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Label, { value: this.channelDeleteError, fontSize: t.status, color: C2.red }), this.streamControlStatus === "error" && /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Label, { value: "Stream control failed", fontSize: t.status, color: C2.red })), /* @__PURE__ */ import_react_ecs5.default.createElement(this.SectionHead, { label: "PLAY NOW", color: C2.orange }), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 14 } } }, /* @__PURE__ */ import_react_ecs5.default.createElement(
2634
+ import_react_ecs5.Input,
2501
2635
  {
2502
2636
  uiTransform: { width: this.s(230), height: this.s(UI_DIMENSIONS.admin.inputHeight) },
2503
2637
  uiBackground: { color: import_math5.Color4.create(0.15, 0.15, 0.2, 1) },
2504
2638
  placeholder: "Video URL...",
2505
- placeholderColor: C.textDim,
2506
- color: C.text,
2639
+ placeholderColor: C2.textDim,
2640
+ color: C2.text,
2507
2641
  fontSize: t.input,
2508
2642
  value: this.customVideoUrl,
2509
2643
  onChange: (val) => {
2510
2644
  this.customVideoUrl = val;
2511
2645
  }
2512
2646
  }
2513
- ), /* @__PURE__ */ import_react_ecs4.default.createElement(
2514
- import_react_ecs4.Button,
2647
+ ), /* @__PURE__ */ import_react_ecs5.default.createElement(
2648
+ import_react_ecs5.Button,
2515
2649
  {
2516
2650
  uiTransform: { width: this.s(75), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 8 } },
2517
- uiBackground: { color: C.green },
2651
+ uiBackground: { color: C2.green },
2518
2652
  value: "Play",
2519
2653
  fontSize: t.button,
2520
- color: C.text,
2654
+ color: C2.text,
2521
2655
  onMouseDown: () => {
2522
2656
  if (this.customVideoUrl) this.config.onVideoPlay?.(this.customVideoUrl);
2523
2657
  }
2524
2658
  }
2525
- ), /* @__PURE__ */ import_react_ecs4.default.createElement(
2526
- import_react_ecs4.Button,
2659
+ ), /* @__PURE__ */ import_react_ecs5.default.createElement(
2660
+ import_react_ecs5.Button,
2527
2661
  {
2528
2662
  uiTransform: { width: this.s(65), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 6 } },
2529
- uiBackground: { color: C.btn },
2663
+ uiBackground: { color: C2.btn },
2530
2664
  value: "Clear",
2531
2665
  fontSize: t.button,
2532
- color: C.text,
2666
+ color: C2.text,
2533
2667
  onMouseDown: () => {
2534
2668
  this.customVideoUrl = "";
2535
2669
  }
2536
2670
  }
2537
- )), /* @__PURE__ */ import_react_ecs4.default.createElement(this.SectionHead, { label: "PLAYBACK", color: C.green }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 14 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(
2538
- import_react_ecs4.Button,
2671
+ )), /* @__PURE__ */ import_react_ecs5.default.createElement(this.SectionHead, { label: "PLAYBACK", color: C2.green }), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 14 } } }, /* @__PURE__ */ import_react_ecs5.default.createElement(
2672
+ import_react_ecs5.Button,
2539
2673
  {
2540
2674
  uiTransform: { width: this.s(75), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2541
- uiBackground: { color: C.green },
2675
+ uiBackground: { color: C2.green },
2542
2676
  value: "Play",
2543
2677
  fontSize: t.button,
2544
- color: C.text,
2678
+ color: C2.text,
2545
2679
  onMouseDown: () => this.config.onCommand?.("videoPlay", { playing: true })
2546
2680
  }
2547
- ), /* @__PURE__ */ import_react_ecs4.default.createElement(
2548
- import_react_ecs4.Button,
2681
+ ), /* @__PURE__ */ import_react_ecs5.default.createElement(
2682
+ import_react_ecs5.Button,
2549
2683
  {
2550
2684
  uiTransform: { width: this.s(75), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2551
- uiBackground: { color: C.red },
2685
+ uiBackground: { color: C2.red },
2552
2686
  value: "Stop",
2553
2687
  fontSize: t.button,
2554
- color: C.text,
2688
+ color: C2.text,
2555
2689
  onMouseDown: () => this.config.onVideoStop?.()
2556
2690
  }
2557
- ), /* @__PURE__ */ import_react_ecs4.default.createElement(
2558
- import_react_ecs4.Button,
2691
+ ), /* @__PURE__ */ import_react_ecs5.default.createElement(
2692
+ import_react_ecs5.Button,
2559
2693
  {
2560
2694
  uiTransform: { width: this.s(100), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2561
- uiBackground: { color: C.btn },
2695
+ uiBackground: { color: C2.btn },
2562
2696
  value: "Reset Default",
2563
2697
  fontSize: t.buttonSmall,
2564
- color: C.text,
2698
+ color: C2.text,
2565
2699
  onMouseDown: () => this.config.onCommand?.("videoClear", {})
2566
2700
  }
2567
- )), /* @__PURE__ */ import_react_ecs4.default.createElement(this.SectionHead, { label: "VIDEO SLOTS", color: C.cyan }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 10 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Button, { uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 }, uiBackground: { color: C.cyan }, value: "Play 1", fontSize: t.button, color: C.text, onMouseDown: () => this.playSlot("slot1") }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Button, { uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 }, uiBackground: { color: C.cyan }, value: "Play 2", fontSize: t.button, color: C.text, onMouseDown: () => this.playSlot("slot2") }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Button, { uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 }, uiBackground: { color: C.cyan }, value: "Play 3", fontSize: t.button, color: C.text, onMouseDown: () => this.playSlot("slot3") }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Button, { uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 }, uiBackground: { color: C.cyan }, value: "Play 4", fontSize: t.button, color: C.text, onMouseDown: () => this.playSlot("slot4") }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Button, { uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 }, uiBackground: { color: C.cyan }, value: "Play 5", fontSize: t.button, color: C.text, onMouseDown: () => this.playSlot("slot5") })), /* @__PURE__ */ import_react_ecs4.default.createElement(
2568
- import_react_ecs4.Button,
2701
+ )), /* @__PURE__ */ import_react_ecs5.default.createElement(this.SectionHead, { label: "VIDEO SLOTS", color: C2.cyan }), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 10 } } }, /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Button, { uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 }, uiBackground: { color: C2.cyan }, value: "Play 1", fontSize: t.button, color: C2.text, onMouseDown: () => this.playSlot("slot1") }), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Button, { uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 }, uiBackground: { color: C2.cyan }, value: "Play 2", fontSize: t.button, color: C2.text, onMouseDown: () => this.playSlot("slot2") }), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Button, { uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 }, uiBackground: { color: C2.cyan }, value: "Play 3", fontSize: t.button, color: C2.text, onMouseDown: () => this.playSlot("slot3") }), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Button, { uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 }, uiBackground: { color: C2.cyan }, value: "Play 4", fontSize: t.button, color: C2.text, onMouseDown: () => this.playSlot("slot4") }), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Button, { uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 }, uiBackground: { color: C2.cyan }, value: "Play 5", fontSize: t.button, color: C2.text, onMouseDown: () => this.playSlot("slot5") })), /* @__PURE__ */ import_react_ecs5.default.createElement(
2702
+ import_react_ecs5.Button,
2569
2703
  {
2570
2704
  uiTransform: { height: this.s(24) },
2571
2705
  uiBackground: { color: import_math5.Color4.create(0, 0, 0, 0) },
2572
2706
  value: "Edit slots at thestatic.tv",
2573
2707
  fontSize: t.labelSmall,
2574
- color: C.cyan,
2708
+ color: C2.cyan,
2575
2709
  onMouseDown: () => (0, import_RestrictedActions3.openExternalUrl)({ url: this.config.footerLink || `https://thestatic.tv/scene/${this.config.sceneId}` })
2576
2710
  }
2577
2711
  ));
2578
2712
  };
2579
2713
  this.ModTab = () => {
2580
2714
  const t = this.theme;
2581
- return /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column", width: "100%", padding: 10 } }, this.modStatus === "loading" && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "Loading...", fontSize: t.status, color: C.yellow }), this.modStatus === "saved" && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "Saved!", fontSize: t.status, color: C.green }), this.modStatus === "error" && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "Error - check input", fontSize: t.status, color: C.red }), /* @__PURE__ */ import_react_ecs4.default.createElement(this.SectionHead, { label: "BROADCAST", color: C.orange }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 14 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(
2582
- import_react_ecs4.Input,
2715
+ return /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { flexDirection: "column", width: "100%", padding: 10 } }, this.modStatus === "loading" && /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Label, { value: "Loading...", fontSize: t.status, color: C2.yellow }), this.modStatus === "saved" && /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Label, { value: "Saved!", fontSize: t.status, color: C2.green }), this.modStatus === "error" && /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Label, { value: "Error - check input", fontSize: t.status, color: C2.red }), /* @__PURE__ */ import_react_ecs5.default.createElement(this.SectionHead, { label: "BROADCAST", color: C2.orange }), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 14 } } }, /* @__PURE__ */ import_react_ecs5.default.createElement(
2716
+ import_react_ecs5.Input,
2583
2717
  {
2584
2718
  uiTransform: { width: this.s(230), height: this.s(UI_DIMENSIONS.admin.inputHeight) },
2585
2719
  uiBackground: { color: import_math5.Color4.create(0.15, 0.15, 0.2, 1) },
2586
2720
  placeholder: "Message to all players...",
2587
- placeholderColor: C.textDim,
2588
- color: C.text,
2721
+ placeholderColor: C2.textDim,
2722
+ color: C2.text,
2589
2723
  fontSize: t.input,
2590
2724
  value: this.broadcastText,
2591
2725
  onChange: (val) => {
2592
2726
  this.broadcastText = val;
2593
2727
  }
2594
2728
  }
2595
- ), /* @__PURE__ */ import_react_ecs4.default.createElement(
2596
- import_react_ecs4.Button,
2729
+ ), /* @__PURE__ */ import_react_ecs5.default.createElement(
2730
+ import_react_ecs5.Button,
2597
2731
  {
2598
2732
  uiTransform: { width: this.s(65), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 8 } },
2599
- uiBackground: { color: C.orange },
2733
+ uiBackground: { color: C2.orange },
2600
2734
  value: "Send",
2601
2735
  fontSize: t.button,
2602
- color: C.text,
2736
+ color: C2.text,
2603
2737
  onMouseDown: () => this.sendBroadcast()
2604
2738
  }
2605
- )), /* @__PURE__ */ import_react_ecs4.default.createElement(this.SectionHead, { label: "CHAOS MODE", color: C.red }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 14 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(
2606
- import_react_ecs4.Button,
2739
+ )), /* @__PURE__ */ import_react_ecs5.default.createElement(this.SectionHead, { label: "CHAOS MODE", color: C2.red }), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 14 } } }, /* @__PURE__ */ import_react_ecs5.default.createElement(
2740
+ import_react_ecs5.Button,
2607
2741
  {
2608
2742
  uiTransform: { width: this.s(130), height: this.s(UI_DIMENSIONS.admin.buttonHeight), margin: 4 },
2609
- uiBackground: { color: C.red },
2743
+ uiBackground: { color: C2.red },
2610
2744
  value: "KICK ALL",
2611
2745
  fontSize: t.button,
2612
- color: C.text,
2746
+ color: C2.text,
2613
2747
  onMouseDown: () => {
2614
2748
  this.log("KICK ALL clicked");
2615
2749
  this.config.onCommand?.("kickAll", {});
2616
2750
  }
2617
2751
  }
2618
- )), /* @__PURE__ */ import_react_ecs4.default.createElement(this.SectionHead, { label: "SCENE ADMINS", color: C.purple }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom: 6 } } }, this.sceneAdmins.length === 0 && this.modStatus !== "loading" && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "No scene admins", fontSize: t.labelSmall, color: C.textDim }), this.sceneAdmins.map((wallet, i) => /* @__PURE__ */ import_react_ecs4.default.createElement(
2619
- import_react_ecs4.UiEntity,
2752
+ )), /* @__PURE__ */ import_react_ecs5.default.createElement(this.SectionHead, { label: "SCENE ADMINS", color: C2.purple }), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom: 6 } } }, this.sceneAdmins.length === 0 && this.modStatus !== "loading" && /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Label, { value: "No scene admins", fontSize: t.labelSmall, color: C2.textDim }), this.sceneAdmins.map((wallet, i) => /* @__PURE__ */ import_react_ecs5.default.createElement(
2753
+ import_react_ecs5.UiEntity,
2620
2754
  {
2621
2755
  key: `admin-${i}`,
2622
2756
  uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 5 }, width: "100%" }
2623
2757
  },
2624
- /* @__PURE__ */ import_react_ecs4.default.createElement(
2625
- import_react_ecs4.Label,
2758
+ /* @__PURE__ */ import_react_ecs5.default.createElement(
2759
+ import_react_ecs5.Label,
2626
2760
  {
2627
2761
  value: `${wallet.slice(0, 6)}...${wallet.slice(-4)}`,
2628
2762
  fontSize: t.label,
2629
- color: C.text,
2763
+ color: C2.text,
2630
2764
  uiTransform: { width: this.s(130) }
2631
2765
  }
2632
2766
  ),
2633
- /* @__PURE__ */ import_react_ecs4.default.createElement(
2634
- import_react_ecs4.Button,
2767
+ /* @__PURE__ */ import_react_ecs5.default.createElement(
2768
+ import_react_ecs5.Button,
2635
2769
  {
2636
2770
  uiTransform: { width: this.s(70), height: this.s(28), margin: { left: 8 } },
2637
- uiBackground: { color: C.btn },
2771
+ uiBackground: { color: C2.btn },
2638
2772
  value: "Remove",
2639
2773
  fontSize: t.buttonSmall,
2640
- color: C.text,
2774
+ color: C2.text,
2641
2775
  onMouseDown: () => this.removeSceneAdmin(wallet)
2642
2776
  }
2643
2777
  )
2644
- ))), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 14 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(
2645
- import_react_ecs4.Input,
2778
+ ))), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 14 } } }, /* @__PURE__ */ import_react_ecs5.default.createElement(
2779
+ import_react_ecs5.Input,
2646
2780
  {
2647
2781
  uiTransform: { width: this.s(230), height: this.s(UI_DIMENSIONS.admin.inputHeight) },
2648
2782
  uiBackground: { color: import_math5.Color4.create(0.15, 0.15, 0.2, 1) },
2649
2783
  placeholder: "0x... add admin",
2650
- placeholderColor: C.textDim,
2651
- color: C.text,
2784
+ placeholderColor: C2.textDim,
2785
+ color: C2.text,
2652
2786
  fontSize: t.input,
2653
2787
  value: this.newAdminWallet,
2654
2788
  onChange: (val) => {
2655
2789
  this.newAdminWallet = val;
2656
2790
  }
2657
2791
  }
2658
- ), /* @__PURE__ */ import_react_ecs4.default.createElement(
2659
- import_react_ecs4.Button,
2792
+ ), /* @__PURE__ */ import_react_ecs5.default.createElement(
2793
+ import_react_ecs5.Button,
2660
2794
  {
2661
2795
  uiTransform: { width: this.s(60), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 8 } },
2662
- uiBackground: { color: C.purple },
2796
+ uiBackground: { color: C2.purple },
2663
2797
  value: "Add",
2664
2798
  fontSize: t.button,
2665
- color: C.text,
2799
+ color: C2.text,
2666
2800
  onMouseDown: () => {
2667
2801
  if (this.newAdminWallet) this.addSceneAdmin(this.newAdminWallet);
2668
2802
  }
2669
2803
  }
2670
- )), /* @__PURE__ */ import_react_ecs4.default.createElement(this.SectionHead, { label: "BANNED WALLETS", color: C.red }), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom: 6 } } }, this.bannedWallets.length === 0 && this.modStatus !== "loading" && /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: "No banned wallets", fontSize: t.labelSmall, color: C.textDim }), this.bannedWallets.map((wallet, i) => /* @__PURE__ */ import_react_ecs4.default.createElement(
2671
- import_react_ecs4.UiEntity,
2804
+ )), /* @__PURE__ */ import_react_ecs5.default.createElement(this.SectionHead, { label: "BANNED WALLETS", color: C2.red }), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { flexDirection: "column", margin: { bottom: 6 } } }, this.bannedWallets.length === 0 && this.modStatus !== "loading" && /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Label, { value: "No banned wallets", fontSize: t.labelSmall, color: C2.textDim }), this.bannedWallets.map((wallet, i) => /* @__PURE__ */ import_react_ecs5.default.createElement(
2805
+ import_react_ecs5.UiEntity,
2672
2806
  {
2673
2807
  key: `ban-${i}`,
2674
2808
  uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 5 }, width: "100%" }
2675
2809
  },
2676
- /* @__PURE__ */ import_react_ecs4.default.createElement(
2677
- import_react_ecs4.Label,
2810
+ /* @__PURE__ */ import_react_ecs5.default.createElement(
2811
+ import_react_ecs5.Label,
2678
2812
  {
2679
2813
  value: `${wallet.slice(0, 6)}...${wallet.slice(-4)}`,
2680
2814
  fontSize: t.label,
2681
- color: C.red,
2815
+ color: C2.red,
2682
2816
  uiTransform: { width: this.s(130) }
2683
2817
  }
2684
2818
  ),
2685
- /* @__PURE__ */ import_react_ecs4.default.createElement(
2686
- import_react_ecs4.Button,
2819
+ /* @__PURE__ */ import_react_ecs5.default.createElement(
2820
+ import_react_ecs5.Button,
2687
2821
  {
2688
2822
  uiTransform: { width: this.s(70), height: this.s(28), margin: { left: 8 } },
2689
- uiBackground: { color: C.green },
2823
+ uiBackground: { color: C2.green },
2690
2824
  value: "Unban",
2691
2825
  fontSize: t.buttonSmall,
2692
- color: C.text,
2826
+ color: C2.text,
2693
2827
  onMouseDown: () => this.unbanWallet(wallet)
2694
2828
  }
2695
2829
  )
2696
- ))), /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 10 } } }, /* @__PURE__ */ import_react_ecs4.default.createElement(
2697
- import_react_ecs4.Input,
2830
+ ))), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 10 } } }, /* @__PURE__ */ import_react_ecs5.default.createElement(
2831
+ import_react_ecs5.Input,
2698
2832
  {
2699
2833
  uiTransform: { width: this.s(230), height: this.s(UI_DIMENSIONS.admin.inputHeight) },
2700
2834
  uiBackground: { color: import_math5.Color4.create(0.15, 0.15, 0.2, 1) },
2701
2835
  placeholder: "0x... ban wallet",
2702
- placeholderColor: C.textDim,
2703
- color: C.text,
2836
+ placeholderColor: C2.textDim,
2837
+ color: C2.text,
2704
2838
  fontSize: t.input,
2705
2839
  value: this.newBanWallet,
2706
2840
  onChange: (val) => {
2707
2841
  this.newBanWallet = val;
2708
2842
  }
2709
2843
  }
2710
- ), /* @__PURE__ */ import_react_ecs4.default.createElement(
2711
- import_react_ecs4.Button,
2844
+ ), /* @__PURE__ */ import_react_ecs5.default.createElement(
2845
+ import_react_ecs5.Button,
2712
2846
  {
2713
2847
  uiTransform: { width: this.s(60), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 8 } },
2714
- uiBackground: { color: C.red },
2848
+ uiBackground: { color: C2.red },
2715
2849
  value: "Ban",
2716
2850
  fontSize: t.button,
2717
- color: C.text,
2851
+ color: C2.text,
2718
2852
  onMouseDown: () => {
2719
2853
  if (this.newBanWallet) this.banWallet(this.newBanWallet);
2720
2854
  }
2721
2855
  }
2722
- )), /* @__PURE__ */ import_react_ecs4.default.createElement(
2723
- import_react_ecs4.Button,
2856
+ )), /* @__PURE__ */ import_react_ecs5.default.createElement(
2857
+ import_react_ecs5.Button,
2724
2858
  {
2725
2859
  uiTransform: { height: this.s(24) },
2726
2860
  uiBackground: { color: import_math5.Color4.create(0, 0, 0, 0) },
2727
2861
  value: "Manage at thestatic.tv",
2728
2862
  fontSize: t.labelSmall,
2729
- color: C.cyan,
2863
+ color: C2.cyan,
2730
2864
  onMouseDown: () => (0, import_RestrictedActions3.openExternalUrl)({ url: this.config.footerLink || `https://thestatic.tv/scene/${this.config.sceneId}` })
2731
2865
  }
2732
2866
  ));
2733
2867
  };
2734
2868
  /**
2735
2869
  * Get the React-ECS component for the admin panel
2870
+ * Always renders NotificationBanner (for broadcasts), admin panel only for admins
2736
2871
  */
2737
2872
  this.getComponent = () => {
2738
- if (!this.isAdmin) return null;
2873
+ if (!this.isAdmin) {
2874
+ return /* @__PURE__ */ import_react_ecs5.default.createElement(NotificationBanner, null);
2875
+ }
2739
2876
  const t = this.theme;
2740
2877
  const tabs = [];
2741
2878
  if (this.config.sceneTabs && this.config.sceneTabs.length > 0) {
@@ -2750,8 +2887,8 @@ var AdminPanelUIModule = class {
2750
2887
  if (!tabs.find((tab) => tab.id === this.activeTab) && tabs.length > 0) {
2751
2888
  this.activeTab = tabs[0].id;
2752
2889
  }
2753
- return /* @__PURE__ */ import_react_ecs4.default.createElement(
2754
- import_react_ecs4.UiEntity,
2890
+ return /* @__PURE__ */ import_react_ecs5.default.createElement(
2891
+ import_react_ecs5.UiEntity,
2755
2892
  {
2756
2893
  uiTransform: {
2757
2894
  width: "100%",
@@ -2759,28 +2896,29 @@ var AdminPanelUIModule = class {
2759
2896
  positionType: "absolute"
2760
2897
  }
2761
2898
  },
2762
- /* @__PURE__ */ import_react_ecs4.default.createElement(
2763
- import_react_ecs4.UiEntity,
2899
+ /* @__PURE__ */ import_react_ecs5.default.createElement(NotificationBanner, null),
2900
+ /* @__PURE__ */ import_react_ecs5.default.createElement(
2901
+ import_react_ecs5.UiEntity,
2764
2902
  {
2765
2903
  uiTransform: {
2766
2904
  position: { right: 20 + this.s(100) + 10 + this.s(100) + 10, bottom: 10 },
2767
2905
  positionType: "absolute"
2768
2906
  }
2769
2907
  },
2770
- /* @__PURE__ */ import_react_ecs4.default.createElement(
2771
- import_react_ecs4.Button,
2908
+ /* @__PURE__ */ import_react_ecs5.default.createElement(
2909
+ import_react_ecs5.Button,
2772
2910
  {
2773
2911
  uiTransform: { width: this.s(100), height: this.s(45) },
2774
- uiBackground: { color: this.panelOpen ? C.btn : C.header },
2912
+ uiBackground: { color: this.panelOpen ? C2.btn : C2.header },
2775
2913
  value: this.panelOpen ? "CLOSE" : "ADMIN",
2776
2914
  fontSize: this.s(14),
2777
- color: C.text,
2915
+ color: C2.text,
2778
2916
  onMouseDown: () => this.toggle()
2779
2917
  }
2780
2918
  )
2781
2919
  ),
2782
- this.panelOpen && /* @__PURE__ */ import_react_ecs4.default.createElement(
2783
- import_react_ecs4.UiEntity,
2920
+ this.panelOpen && /* @__PURE__ */ import_react_ecs5.default.createElement(
2921
+ import_react_ecs5.UiEntity,
2784
2922
  {
2785
2923
  key: `admin-panel-${this.client.uiScale}`,
2786
2924
  uiTransform: {
@@ -2791,10 +2929,10 @@ var AdminPanelUIModule = class {
2791
2929
  positionType: "absolute",
2792
2930
  flexDirection: "column"
2793
2931
  },
2794
- uiBackground: { color: C.bg }
2932
+ uiBackground: { color: C2.bg }
2795
2933
  },
2796
- /* @__PURE__ */ import_react_ecs4.default.createElement(
2797
- import_react_ecs4.UiEntity,
2934
+ /* @__PURE__ */ import_react_ecs5.default.createElement(
2935
+ import_react_ecs5.UiEntity,
2798
2936
  {
2799
2937
  uiTransform: {
2800
2938
  width: "100%",
@@ -2804,11 +2942,11 @@ var AdminPanelUIModule = class {
2804
2942
  flexDirection: "row",
2805
2943
  padding: { left: 12, right: 8 }
2806
2944
  },
2807
- uiBackground: { color: C.header }
2945
+ uiBackground: { color: C2.header }
2808
2946
  },
2809
- /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: this.config.title || "ADMIN PANEL", fontSize: t.header, color: C.text }),
2810
- /* @__PURE__ */ import_react_ecs4.default.createElement(
2811
- import_react_ecs4.Button,
2947
+ /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Label, { value: this.config.title || "ADMIN PANEL", fontSize: t.header, color: C2.text }),
2948
+ /* @__PURE__ */ import_react_ecs5.default.createElement(
2949
+ import_react_ecs5.Button,
2812
2950
  {
2813
2951
  uiTransform: { width: this.s(28), height: this.s(28) },
2814
2952
  uiBackground: { color: import_math5.Color4.create(0, 0, 0, 0.3) },
@@ -2819,9 +2957,9 @@ var AdminPanelUIModule = class {
2819
2957
  }
2820
2958
  )
2821
2959
  ),
2822
- /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.UiEntity, { uiTransform: { width: "100%", height: this.s(UI_DIMENSIONS.admin.tabHeight), flexDirection: "row" } }, tabs.map((tab) => /* @__PURE__ */ import_react_ecs4.default.createElement(this.TabBtn, { label: tab.label, tab: tab.id }))),
2823
- /* @__PURE__ */ import_react_ecs4.default.createElement(
2824
- import_react_ecs4.UiEntity,
2960
+ /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { width: "100%", height: this.s(UI_DIMENSIONS.admin.tabHeight), flexDirection: "row" } }, tabs.map((tab) => /* @__PURE__ */ import_react_ecs5.default.createElement(this.TabBtn, { label: tab.label, tab: tab.id }))),
2961
+ /* @__PURE__ */ import_react_ecs5.default.createElement(
2962
+ import_react_ecs5.UiEntity,
2825
2963
  {
2826
2964
  uiTransform: {
2827
2965
  width: "100%",
@@ -2830,12 +2968,12 @@ var AdminPanelUIModule = class {
2830
2968
  flexDirection: "column"
2831
2969
  }
2832
2970
  },
2833
- this.activeTab === "video" && /* @__PURE__ */ import_react_ecs4.default.createElement(this.VideoTab, null),
2834
- this.activeTab === "mod" && this.isOwner && /* @__PURE__ */ import_react_ecs4.default.createElement(this.ModTab, null),
2971
+ this.activeTab === "video" && /* @__PURE__ */ import_react_ecs5.default.createElement(this.VideoTab, null),
2972
+ this.activeTab === "mod" && this.isOwner && /* @__PURE__ */ import_react_ecs5.default.createElement(this.ModTab, null),
2835
2973
  this.config.sceneTabs?.map((tab) => this.activeTab === tab.id && tab.render())
2836
2974
  ),
2837
- /* @__PURE__ */ import_react_ecs4.default.createElement(
2838
- import_react_ecs4.UiEntity,
2975
+ /* @__PURE__ */ import_react_ecs5.default.createElement(
2976
+ import_react_ecs5.UiEntity,
2839
2977
  {
2840
2978
  uiTransform: {
2841
2979
  width: "100%",
@@ -2843,16 +2981,16 @@ var AdminPanelUIModule = class {
2843
2981
  justifyContent: "center",
2844
2982
  alignItems: "center"
2845
2983
  },
2846
- uiBackground: { color: C.section }
2984
+ uiBackground: { color: C2.section }
2847
2985
  },
2848
- /* @__PURE__ */ import_react_ecs4.default.createElement(
2849
- import_react_ecs4.Button,
2986
+ /* @__PURE__ */ import_react_ecs5.default.createElement(
2987
+ import_react_ecs5.Button,
2850
2988
  {
2851
2989
  uiTransform: { height: this.s(24) },
2852
2990
  uiBackground: { color: import_math5.Color4.create(0, 0, 0, 0) },
2853
2991
  value: `thestatic.tv/scene/${this.config.sceneId}`,
2854
2992
  fontSize: t.labelSmall,
2855
- color: C.cyan,
2993
+ color: C2.cyan,
2856
2994
  onMouseDown: () => (0, import_RestrictedActions3.openExternalUrl)({ url: this.config.footerLink || `https://thestatic.tv/scene/${this.config.sceneId}` })
2857
2995
  }
2858
2996
  )
@@ -2875,7 +3013,7 @@ var AdminPanelUIModule = class {
2875
3013
  };
2876
3014
  this.baseUrl = client.getBaseUrl();
2877
3015
  if (config.headerColor) {
2878
- C.header = import_math5.Color4.create(
3016
+ C2.header = import_math5.Color4.create(
2879
3017
  config.headerColor.r,
2880
3018
  config.headerColor.g,
2881
3019
  config.headerColor.b,
@@ -2903,6 +3041,7 @@ var AdminPanelUIModule = class {
2903
3041
  await this.checkAdminStatus();
2904
3042
  await this.fetchVideoState();
2905
3043
  this.autoPlayDefault();
3044
+ this.startCommandPolling();
2906
3045
  this.log("Initialized");
2907
3046
  }
2908
3047
  /**
@@ -3008,6 +3147,61 @@ var AdminPanelUIModule = class {
3008
3147
  this.log("Stream polling stopped");
3009
3148
  }
3010
3149
  }
3150
+ // --- Command Polling (receives broadcasts from website) ---
3151
+ startCommandPolling() {
3152
+ if (this.commandPollIntervalId !== null) return;
3153
+ this.commandPollIntervalId = dclSetInterval(() => {
3154
+ this.pollCommands();
3155
+ }, 5e3);
3156
+ this.pollCommands();
3157
+ this.log("Command polling started");
3158
+ }
3159
+ stopCommandPolling() {
3160
+ if (this.commandPollIntervalId !== null) {
3161
+ dclClearInterval(this.commandPollIntervalId);
3162
+ this.commandPollIntervalId = null;
3163
+ this.log("Command polling stopped");
3164
+ }
3165
+ }
3166
+ async pollCommands() {
3167
+ try {
3168
+ const res = await fetch(`${this.baseUrl}/scene/${this.config.sceneId}/commands`);
3169
+ if (!res.ok) return;
3170
+ const data = await res.json();
3171
+ const commands = data.commands || [];
3172
+ for (const cmd of commands) {
3173
+ if (cmd.timestamp <= this.lastCommandTimestamp) continue;
3174
+ this.lastCommandTimestamp = cmd.timestamp;
3175
+ if (cmd.type === "broadcast" && cmd.payload?.text) {
3176
+ const msg = cmd.payload.from ? `${cmd.payload.from}: ${cmd.payload.text}` : cmd.payload.text;
3177
+ this.log("Received broadcast:", msg);
3178
+ this.config.onBroadcast?.(msg);
3179
+ } else if (cmd.type === "clearBroadcast") {
3180
+ this.log("Received clearBroadcast");
3181
+ hideNotification();
3182
+ } else if ((cmd.type === "videoPlay" || cmd.type === "videoSetUrl") && cmd.payload?.url) {
3183
+ this.log("Received video play:", cmd.type, cmd.payload.url);
3184
+ this.config.onVideoPlay?.(cmd.payload.url);
3185
+ } else if (cmd.type === "videoStop" || cmd.type === "videoClear") {
3186
+ this.log("Received video stop/clear:", cmd.type);
3187
+ this.config.onVideoStop?.();
3188
+ } else if (cmd.type === "videoPlaySlot" && cmd.payload?.slot) {
3189
+ const slotId = cmd.payload.slot;
3190
+ if (cmd.payload.url) {
3191
+ this.log("Received videoPlaySlot with URL:", slotId, cmd.payload.url);
3192
+ this.config.onVideoPlay?.(cmd.payload.url);
3193
+ } else {
3194
+ this.log("Received videoPlaySlot:", slotId);
3195
+ this.playSlot(slotId);
3196
+ }
3197
+ } else if (this.config.onCommand) {
3198
+ this.log("Received command:", cmd.type, cmd.payload);
3199
+ this.config.onCommand(cmd.type, cmd.payload);
3200
+ }
3201
+ }
3202
+ } catch (err) {
3203
+ }
3204
+ }
3011
3205
  // --- Stream API Calls ---
3012
3206
  async fetchStreamData() {
3013
3207
  if (this.streamFetched || !this.playerWallet) return;
@@ -3439,87 +3633,6 @@ var AdminPanelUIModule = class {
3439
3633
  }
3440
3634
  };
3441
3635
 
3442
- // src/ui/ui-renderer.tsx
3443
- var import_react_ecs5 = __toESM(require("@dcl/sdk/react-ecs"));
3444
- var import_math6 = require("@dcl/sdk/math");
3445
- var import_ecs2 = require("@dcl/sdk/ecs");
3446
- var notificationText = "";
3447
- var notificationVisible = false;
3448
- var notificationEndTime = 0;
3449
- var notificationInitialized = false;
3450
- function showNotification(message, durationMs = 5e3) {
3451
- notificationText = message;
3452
- notificationVisible = true;
3453
- notificationEndTime = Date.now() + durationMs;
3454
- }
3455
- function initNotificationSystem() {
3456
- if (notificationInitialized) return;
3457
- notificationInitialized = true;
3458
- import_ecs2.engine.addSystem(() => {
3459
- if (notificationVisible && Date.now() > notificationEndTime) {
3460
- notificationVisible = false;
3461
- }
3462
- });
3463
- }
3464
- function NotificationBanner() {
3465
- if (!notificationVisible) return null;
3466
- return /* @__PURE__ */ import_react_ecs5.default.createElement(
3467
- import_react_ecs5.UiEntity,
3468
- {
3469
- uiTransform: {
3470
- positionType: "absolute",
3471
- position: { top: 80 },
3472
- width: 500,
3473
- height: 60,
3474
- padding: 16,
3475
- alignSelf: "center",
3476
- justifyContent: "center",
3477
- alignItems: "center"
3478
- },
3479
- uiBackground: { color: import_math6.Color4.create(0.1, 0.1, 0.15, 0.95) }
3480
- },
3481
- /* @__PURE__ */ import_react_ecs5.default.createElement(
3482
- import_react_ecs5.Label,
3483
- {
3484
- value: notificationText,
3485
- fontSize: 18,
3486
- color: import_math6.Color4.create(0, 1, 1, 1),
3487
- textAlign: "middle-center"
3488
- }
3489
- )
3490
- );
3491
- }
3492
- function createStaticUI(client) {
3493
- initNotificationSystem();
3494
- return function StaticUI() {
3495
- const currentScale = client.uiScale;
3496
- const guideComponent = client.guideUI?.getComponent() ?? null;
3497
- const chatComponent = client.chatUI?.getComponent() ?? null;
3498
- const adminComponent = client.adminPanel?.getComponent() ?? null;
3499
- return /* @__PURE__ */ import_react_ecs5.default.createElement(
3500
- import_react_ecs5.UiEntity,
3501
- {
3502
- key: `static-ui-root-${currentScale}`,
3503
- uiTransform: {
3504
- width: "100%",
3505
- height: "100%",
3506
- positionType: "absolute",
3507
- flexDirection: "column",
3508
- alignItems: "center"
3509
- }
3510
- },
3511
- /* @__PURE__ */ import_react_ecs5.default.createElement(NotificationBanner, null),
3512
- guideComponent,
3513
- chatComponent,
3514
- adminComponent
3515
- );
3516
- };
3517
- }
3518
- function setupStaticUI(client) {
3519
- const StaticUI = createStaticUI(client);
3520
- import_react_ecs5.ReactEcsRenderer.setUiRenderer(StaticUI);
3521
- }
3522
-
3523
3636
  // src/StaticTVClient.ts
3524
3637
  var import_ecs3 = require("@dcl/sdk/ecs");
3525
3638
  var utils = __toESM(require("@dcl-sdk/utils"));
@@ -3800,10 +3913,13 @@ var StaticTVClient = class {
3800
3913
  */
3801
3914
  stopVideo() {
3802
3915
  const screen = this.config.videoScreen;
3916
+ this._pendingVideoData = null;
3917
+ this._streamVerified = false;
3918
+ this._clearVerificationTimeout();
3919
+ if (this.guideUI) {
3920
+ this.guideUI.currentVideoId = null;
3921
+ }
3803
3922
  if (screen !== void 0) {
3804
- if (this.guideUI) {
3805
- this.guideUI.currentVideoId = null;
3806
- }
3807
3923
  const fallbackUrl = this.config.fallbackVideoUrl;
3808
3924
  const fallbackDisabled = fallbackUrl === "";
3809
3925
  if (fallbackDisabled) {
@@ -3959,20 +4075,60 @@ var StaticTVClient = class {
3959
4075
  }
3960
4076
  /**
3961
4077
  * Play video with stream verification for live content
4078
+ * Works with both videoScreen (SDK-managed) and onVideoPlay (user-managed) modes.
3962
4079
  * @internal
3963
4080
  */
3964
4081
  _playVideoWithVerification(video, isLiveStream) {
3965
4082
  const screen = this.config.videoScreen;
4083
+ const hasCallback = this.config.onVideoPlay !== void 0;
4084
+ this.log(`Playing video: ${video.src} (live: ${isLiveStream}, screen: ${screen !== void 0}, callback: ${hasCallback})`);
4085
+ this._currentVideoUrl = video.src;
4086
+ this._clearVerificationTimeout();
4087
+ this._streamVerified = false;
3966
4088
  if (screen !== void 0) {
3967
- this.log(`Playing video: ${video.src} (live: ${isLiveStream})`);
3968
- this._currentVideoUrl = video.src;
3969
4089
  this._playVideoInternal(video.src, false, isLiveStream);
3970
- if (this.guideUI) {
3971
- this.guideUI.currentVideoId = video.id;
3972
- }
3973
4090
  }
3974
- if (this.config.onVideoPlay) {
4091
+ if (hasCallback) {
3975
4092
  this.config.onVideoPlay(video.src);
4093
+ if (isLiveStream) {
4094
+ this._startCallbackVerification(video);
4095
+ }
4096
+ }
4097
+ if (this.guideUI) {
4098
+ this.guideUI.currentVideoId = video.id;
4099
+ }
4100
+ }
4101
+ /**
4102
+ * Start stream verification for callback-based video playback
4103
+ * Shows CONNECTING state and triggers fallback if verification times out
4104
+ * @internal
4105
+ */
4106
+ _startCallbackVerification(video) {
4107
+ this.showNotification(`Connecting to ${video.name}...`, 6e3);
4108
+ this._verificationTimeoutId = utils.timers.setTimeout(() => {
4109
+ if (!this._streamVerified && this._pendingVideoData) {
4110
+ this.log(`Stream verification timeout (callback mode): ${video.name}`);
4111
+ this._handleStreamOffline();
4112
+ }
4113
+ }, 5e3);
4114
+ }
4115
+ /**
4116
+ * Call this to confirm that a video stream is playing successfully.
4117
+ * Use this when you're managing video playback yourself (onVideoPlay callback).
4118
+ *
4119
+ * @example
4120
+ * ```typescript
4121
+ * // In your video player's onReady or similar event:
4122
+ * videoPlayer.events.on('playing', () => {
4123
+ * staticTV.confirmVideoPlaying()
4124
+ * })
4125
+ * ```
4126
+ */
4127
+ confirmVideoPlaying() {
4128
+ if (this._pendingVideoData && !this._streamVerified) {
4129
+ this._streamVerified = true;
4130
+ this._clearVerificationTimeout();
4131
+ this.log(`Stream confirmed playing: ${this._pendingVideoData.name}`);
3976
4132
  }
3977
4133
  }
3978
4134
  /**
@@ -4247,11 +4403,13 @@ var StaticTVClient = class {
4247
4403
  InteractionsModule,
4248
4404
  KEY_TYPE_CHANNEL,
4249
4405
  KEY_TYPE_SCENE,
4406
+ NotificationBanner,
4250
4407
  SessionModule,
4251
4408
  StaticTVClient,
4252
4409
  fetchUserData,
4253
4410
  getPlayerDisplayName,
4254
4411
  getPlayerWallet,
4412
+ hideNotification,
4255
4413
  setupStaticUI,
4256
4414
  showNotification
4257
4415
  });