@thestatic-tv/dcl-sdk 2.5.10 → 2.5.13

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 - centered, aligned with toggle buttons
674
+ notification: {
675
+ width: 340,
676
+ height: 36,
677
+ // Same as toggle buttons
678
+ bottom: 10,
679
+ // Same as toggle buttons
680
+ borderWidth: 2,
681
+ fontSize: 14
682
+ // Same as toggle buttons (14)
683
+ },
671
684
  // Shared
672
685
  closeButton: {
673
686
  size: 40,
@@ -2295,12 +2308,144 @@ 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 (
2346
+ // Full-width container to center the notification
2347
+ /* @__PURE__ */ import_react_ecs4.default.createElement(
2348
+ import_react_ecs4.UiEntity,
2349
+ {
2350
+ uiTransform: {
2351
+ positionType: "absolute",
2352
+ position: { bottom: N.bottom, left: 0, right: 0 },
2353
+ height: N.height,
2354
+ justifyContent: "center",
2355
+ alignItems: "center"
2356
+ }
2357
+ },
2358
+ /* @__PURE__ */ import_react_ecs4.default.createElement(
2359
+ import_react_ecs4.UiEntity,
2360
+ {
2361
+ uiTransform: {
2362
+ width: N.width,
2363
+ height: N.height,
2364
+ flexDirection: "row",
2365
+ alignItems: "center"
2366
+ },
2367
+ uiBackground: { color: C.panel }
2368
+ },
2369
+ /* @__PURE__ */ import_react_ecs4.default.createElement(
2370
+ import_react_ecs4.UiEntity,
2371
+ {
2372
+ uiTransform: {
2373
+ width: N.borderWidth,
2374
+ height: "100%"
2375
+ },
2376
+ uiBackground: { color: C.cyan }
2377
+ }
2378
+ ),
2379
+ /* @__PURE__ */ import_react_ecs4.default.createElement(
2380
+ import_react_ecs4.UiEntity,
2381
+ {
2382
+ uiTransform: {
2383
+ flexGrow: 1,
2384
+ height: "100%",
2385
+ padding: { left: 10, right: 10 },
2386
+ justifyContent: "center",
2387
+ alignItems: "flex-start"
2388
+ }
2389
+ },
2390
+ /* @__PURE__ */ import_react_ecs4.default.createElement(
2391
+ import_react_ecs4.Label,
2392
+ {
2393
+ value: notificationText,
2394
+ fontSize: N.fontSize,
2395
+ color: C.white,
2396
+ textAlign: "middle-left"
2397
+ }
2398
+ )
2399
+ ),
2400
+ /* @__PURE__ */ import_react_ecs4.default.createElement(
2401
+ import_react_ecs4.UiEntity,
2402
+ {
2403
+ uiTransform: {
2404
+ width: 5,
2405
+ height: 5,
2406
+ margin: { right: 10 }
2407
+ },
2408
+ uiBackground: { color: C.cyan }
2409
+ }
2410
+ )
2411
+ )
2412
+ )
2413
+ );
2414
+ }
2415
+ function createStaticUI(client) {
2416
+ initNotificationSystem();
2417
+ return function StaticUI() {
2418
+ const currentScale = client.uiScale;
2419
+ const guideComponent = client.guideUI?.getComponent() ?? null;
2420
+ const chatComponent = client.chatUI?.getComponent() ?? null;
2421
+ const adminComponent = client.adminPanel?.getComponent() ?? null;
2422
+ return /* @__PURE__ */ import_react_ecs4.default.createElement(
2423
+ import_react_ecs4.UiEntity,
2424
+ {
2425
+ key: `static-ui-root-${currentScale}`,
2426
+ uiTransform: {
2427
+ width: "100%",
2428
+ height: "100%",
2429
+ positionType: "absolute",
2430
+ flexDirection: "column",
2431
+ alignItems: "center"
2432
+ }
2433
+ },
2434
+ /* @__PURE__ */ import_react_ecs4.default.createElement(NotificationBanner, null),
2435
+ guideComponent,
2436
+ chatComponent,
2437
+ adminComponent
2438
+ );
2439
+ };
2440
+ }
2441
+ function setupStaticUI(client) {
2442
+ const StaticUI = createStaticUI(client);
2443
+ import_react_ecs4.ReactEcsRenderer.setUiRenderer(StaticUI);
2444
+ }
2445
+
2446
+ // src/ui/admin-panel-ui.tsx
2302
2447
  var BAN_KICK_POSITION = import_math5.Vector3.create(16, -50, 16);
2303
- var C = {
2448
+ var C2 = {
2304
2449
  bg: import_math5.Color4.create(0.08, 0.08, 0.12, 0.98),
2305
2450
  header: import_math5.Color4.create(0.9, 0.15, 0.15, 1),
2306
2451
  tabActive: import_math5.Color4.create(0, 0.7, 0.7, 1),
@@ -2341,6 +2486,8 @@ var AdminPanelUIModule = class {
2341
2486
  this.streamControlling = false;
2342
2487
  this.streamControlStatus = "";
2343
2488
  this.pollIntervalId = null;
2489
+ this.commandPollIntervalId = null;
2490
+ this.lastCommandTimestamp = 0;
2344
2491
  this.trialClaiming = false;
2345
2492
  this.trialClaimError = "";
2346
2493
  // Mod tab state
@@ -2352,22 +2499,22 @@ var AdminPanelUIModule = class {
2352
2499
  this.modStatus = "";
2353
2500
  this.modsFetched = false;
2354
2501
  // --- UI Components ---
2355
- this.SectionHead = ({ label, color }) => /* @__PURE__ */ import_react_ecs4.default.createElement(
2356
- import_react_ecs4.UiEntity,
2502
+ this.SectionHead = ({ label, color }) => /* @__PURE__ */ import_react_ecs5.default.createElement(
2503
+ import_react_ecs5.UiEntity,
2357
2504
  {
2358
2505
  uiTransform: { width: "100%", height: this.s(UI_DIMENSIONS.admin.sectionHeadHeight), margin: { bottom: 8 }, padding: { left: 10 }, alignItems: "center" },
2359
2506
  uiBackground: { color: import_math5.Color4.create(color.r * 0.3, color.g * 0.3, color.b * 0.3, 0.5) }
2360
2507
  },
2361
- /* @__PURE__ */ import_react_ecs4.default.createElement(import_react_ecs4.Label, { value: label, fontSize: this.theme.sectionHead, color })
2508
+ /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Label, { value: label, fontSize: this.theme.sectionHead, color })
2362
2509
  );
2363
- this.TabBtn = ({ label, tab }) => /* @__PURE__ */ import_react_ecs4.default.createElement(
2364
- import_react_ecs4.Button,
2510
+ this.TabBtn = ({ label, tab }) => /* @__PURE__ */ import_react_ecs5.default.createElement(
2511
+ import_react_ecs5.Button,
2365
2512
  {
2366
2513
  uiTransform: { flexGrow: 1, height: this.s(UI_DIMENSIONS.admin.tabHeight), justifyContent: "center", alignItems: "center" },
2367
- uiBackground: { color: this.activeTab === tab ? C.tabActive : C.tabInactive },
2514
+ uiBackground: { color: this.activeTab === tab ? C2.tabActive : C2.tabInactive },
2368
2515
  value: label,
2369
2516
  fontSize: this.theme.tabButton,
2370
- color: this.activeTab === tab ? import_math5.Color4.Black() : C.text,
2517
+ color: this.activeTab === tab ? import_math5.Color4.Black() : C2.text,
2371
2518
  textAlign: "middle-center",
2372
2519
  onMouseDown: () => this.setActiveTab(tab)
2373
2520
  }
@@ -2377,365 +2524,368 @@ var AdminPanelUIModule = class {
2377
2524
  this.fetchStreamData();
2378
2525
  }
2379
2526
  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,
2527
+ 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(
2528
+ import_react_ecs5.Button,
2382
2529
  {
2383
2530
  uiTransform: { width: this.s(200), height: this.s(UI_DIMENSIONS.admin.buttonHeight), margin: { bottom: 6 } },
2384
- uiBackground: { color: this.trialClaiming ? C.btn : C.green },
2531
+ uiBackground: { color: this.trialClaiming ? C2.btn : C2.green },
2385
2532
  value: this.trialClaiming ? "Claiming..." : "Start Free 4-Hour Trial",
2386
2533
  fontSize: t.button,
2387
- color: C.text,
2534
+ color: C2.text,
2388
2535
  onMouseDown: () => this.claimTrial()
2389
2536
  }
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,
2537
+ ), 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(
2538
+ import_react_ecs5.Button,
2392
2539
  {
2393
2540
  uiTransform: { width: this.s(170), height: this.s(UI_DIMENSIONS.admin.buttonHeight), margin: { bottom: 6 } },
2394
- uiBackground: { color: this.channelCreating ? C.btn : C.cyan },
2541
+ uiBackground: { color: this.channelCreating ? C2.btn : C2.cyan },
2395
2542
  value: this.channelCreating ? "Creating..." : "+ Create Channel",
2396
2543
  fontSize: t.button,
2397
- color: C.text,
2544
+ color: C2.text,
2398
2545
  onMouseDown: () => this.createChannel()
2399
2546
  }
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,
2547
+ ), 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(
2548
+ import_react_ecs5.Label,
2402
2549
  {
2403
2550
  value: this.streamData.isLive ? `LIVE \u2022 ${this.streamData.currentViewers || 0} viewers` : "OFFLINE",
2404
2551
  fontSize: t.label,
2405
- color: this.streamData.isLive ? C.red : C.textDim
2552
+ color: this.streamData.isLive ? C2.red : C2.textDim
2406
2553
  }
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,
2554
+ ), /* @__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(
2555
+ import_react_ecs5.Label,
2409
2556
  {
2410
2557
  value: `Channel: ${this.streamData.channelName || this.streamData.channelId}`,
2411
2558
  fontSize: t.labelSmall,
2412
- color: C.textDim,
2559
+ color: C2.textDim,
2413
2560
  uiTransform: { margin: { bottom: 10 } }
2414
2561
  }
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,
2562
+ ), /* @__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(
2563
+ import_react_ecs5.Label,
2417
2564
  {
2418
2565
  value: this.streamData.rtmpUrl || "Loading...",
2419
2566
  fontSize: t.label,
2420
- color: this.streamData.rtmpUrl ? C.text : C.textDim,
2567
+ color: this.streamData.rtmpUrl ? C2.text : C2.textDim,
2421
2568
  uiTransform: { margin: { left: 6 } }
2422
2569
  }
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,
2570
+ )), /* @__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(
2571
+ import_react_ecs5.Label,
2425
2572
  {
2426
2573
  value: this.streamData.streamKey || "Loading...",
2427
2574
  fontSize: t.label,
2428
- color: this.streamData.streamKey ? C.cyan : C.textDim,
2575
+ color: this.streamData.streamKey ? C2.cyan : C2.textDim,
2429
2576
  uiTransform: { margin: { left: 6 } }
2430
2577
  }
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,
2578
+ )), /* @__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(
2579
+ import_react_ecs5.Label,
2433
2580
  {
2434
2581
  value: this.streamData.hlsUrl || "Not available",
2435
2582
  fontSize: t.label,
2436
- color: this.streamData.hlsUrl ? C.green : C.textDim,
2583
+ color: this.streamData.hlsUrl ? C2.green : C2.textDim,
2437
2584
  uiTransform: { margin: { left: 6 } }
2438
2585
  }
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,
2586
+ )), /* @__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(
2587
+ import_react_ecs5.Button,
2441
2588
  {
2442
2589
  uiTransform: { width: this.s(110), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2443
- uiBackground: { color: this.streamControlling ? C.btn : C.green },
2590
+ uiBackground: { color: this.streamControlling ? C2.btn : C2.green },
2444
2591
  value: this.streamControlling ? "Starting..." : "Start Stream",
2445
2592
  fontSize: t.buttonSmall,
2446
- color: C.text,
2593
+ color: C2.text,
2447
2594
  onMouseDown: () => this.startStream()
2448
2595
  }
2449
- ), this.streamData.isLive && /* @__PURE__ */ import_react_ecs4.default.createElement(
2450
- import_react_ecs4.Button,
2596
+ ), this.streamData.isLive && /* @__PURE__ */ import_react_ecs5.default.createElement(
2597
+ import_react_ecs5.Button,
2451
2598
  {
2452
2599
  uiTransform: { width: this.s(110), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2453
- uiBackground: { color: this.streamControlling ? C.btn : C.red },
2600
+ uiBackground: { color: this.streamControlling ? C2.btn : C2.red },
2454
2601
  value: this.streamControlling ? "Stopping..." : "Stop Stream",
2455
2602
  fontSize: t.buttonSmall,
2456
- color: C.text,
2603
+ color: C2.text,
2457
2604
  onMouseDown: () => this.stopStream()
2458
2605
  }
2459
- ), this.streamData.isLive && this.streamData.hlsUrl && /* @__PURE__ */ import_react_ecs4.default.createElement(
2460
- import_react_ecs4.Button,
2606
+ ), this.streamData.isLive && this.streamData.hlsUrl && /* @__PURE__ */ import_react_ecs5.default.createElement(
2607
+ import_react_ecs5.Button,
2461
2608
  {
2462
2609
  uiTransform: { width: this.s(100), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2463
- uiBackground: { color: C.cyan },
2610
+ uiBackground: { color: C2.cyan },
2464
2611
  value: "Play on Screen",
2465
2612
  fontSize: t.buttonSmall,
2466
- color: C.text,
2613
+ color: C2.text,
2467
2614
  onMouseDown: () => this.config.onVideoPlay?.(this.streamData.hlsUrl)
2468
2615
  }
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,
2616
+ )), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 6 } } }, /* @__PURE__ */ import_react_ecs5.default.createElement(
2617
+ import_react_ecs5.Button,
2471
2618
  {
2472
2619
  uiTransform: { width: this.s(80), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2473
- uiBackground: { color: C.btn },
2620
+ uiBackground: { color: C2.btn },
2474
2621
  value: "Refresh",
2475
2622
  fontSize: t.buttonSmall,
2476
- color: C.text,
2623
+ color: C2.text,
2477
2624
  onMouseDown: () => this.refreshStreamStatus()
2478
2625
  }
2479
- ), /* @__PURE__ */ import_react_ecs4.default.createElement(
2480
- import_react_ecs4.Button,
2626
+ ), /* @__PURE__ */ import_react_ecs5.default.createElement(
2627
+ import_react_ecs5.Button,
2481
2628
  {
2482
2629
  uiTransform: { width: this.s(95), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2483
- uiBackground: { color: this.keyRotating ? C.btn : C.yellow },
2630
+ uiBackground: { color: this.keyRotating ? C2.btn : C2.yellow },
2484
2631
  value: this.keyRotating ? "..." : "Rotate Key",
2485
2632
  fontSize: t.buttonSmall,
2486
- color: C.text,
2633
+ color: C2.text,
2487
2634
  onMouseDown: () => this.rotateStreamKey()
2488
2635
  }
2489
- ), !this.streamData.isLive && /* @__PURE__ */ import_react_ecs4.default.createElement(
2490
- import_react_ecs4.Button,
2636
+ ), !this.streamData.isLive && /* @__PURE__ */ import_react_ecs5.default.createElement(
2637
+ import_react_ecs5.Button,
2491
2638
  {
2492
2639
  uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2493
- uiBackground: { color: this.channelDeleting ? C.btn : C.red },
2640
+ uiBackground: { color: this.channelDeleting ? C2.btn : C2.red },
2494
2641
  value: this.channelDeleting ? "..." : "Delete",
2495
2642
  fontSize: t.buttonSmall,
2496
- color: C.text,
2643
+ color: C2.text,
2497
2644
  onMouseDown: () => this.deleteChannel()
2498
2645
  }
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,
2646
+ )), 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(
2647
+ import_react_ecs5.Input,
2501
2648
  {
2502
2649
  uiTransform: { width: this.s(230), height: this.s(UI_DIMENSIONS.admin.inputHeight) },
2503
2650
  uiBackground: { color: import_math5.Color4.create(0.15, 0.15, 0.2, 1) },
2504
2651
  placeholder: "Video URL...",
2505
- placeholderColor: C.textDim,
2506
- color: C.text,
2652
+ placeholderColor: C2.textDim,
2653
+ color: C2.text,
2507
2654
  fontSize: t.input,
2508
2655
  value: this.customVideoUrl,
2509
2656
  onChange: (val) => {
2510
2657
  this.customVideoUrl = val;
2511
2658
  }
2512
2659
  }
2513
- ), /* @__PURE__ */ import_react_ecs4.default.createElement(
2514
- import_react_ecs4.Button,
2660
+ ), /* @__PURE__ */ import_react_ecs5.default.createElement(
2661
+ import_react_ecs5.Button,
2515
2662
  {
2516
2663
  uiTransform: { width: this.s(75), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 8 } },
2517
- uiBackground: { color: C.green },
2664
+ uiBackground: { color: C2.green },
2518
2665
  value: "Play",
2519
2666
  fontSize: t.button,
2520
- color: C.text,
2667
+ color: C2.text,
2521
2668
  onMouseDown: () => {
2522
2669
  if (this.customVideoUrl) this.config.onVideoPlay?.(this.customVideoUrl);
2523
2670
  }
2524
2671
  }
2525
- ), /* @__PURE__ */ import_react_ecs4.default.createElement(
2526
- import_react_ecs4.Button,
2672
+ ), /* @__PURE__ */ import_react_ecs5.default.createElement(
2673
+ import_react_ecs5.Button,
2527
2674
  {
2528
2675
  uiTransform: { width: this.s(65), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 6 } },
2529
- uiBackground: { color: C.btn },
2676
+ uiBackground: { color: C2.btn },
2530
2677
  value: "Clear",
2531
2678
  fontSize: t.button,
2532
- color: C.text,
2679
+ color: C2.text,
2533
2680
  onMouseDown: () => {
2534
2681
  this.customVideoUrl = "";
2535
2682
  }
2536
2683
  }
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,
2684
+ )), /* @__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(
2685
+ import_react_ecs5.Button,
2539
2686
  {
2540
2687
  uiTransform: { width: this.s(75), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2541
- uiBackground: { color: C.green },
2688
+ uiBackground: { color: C2.green },
2542
2689
  value: "Play",
2543
2690
  fontSize: t.button,
2544
- color: C.text,
2691
+ color: C2.text,
2545
2692
  onMouseDown: () => this.config.onCommand?.("videoPlay", { playing: true })
2546
2693
  }
2547
- ), /* @__PURE__ */ import_react_ecs4.default.createElement(
2548
- import_react_ecs4.Button,
2694
+ ), /* @__PURE__ */ import_react_ecs5.default.createElement(
2695
+ import_react_ecs5.Button,
2549
2696
  {
2550
2697
  uiTransform: { width: this.s(75), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2551
- uiBackground: { color: C.red },
2698
+ uiBackground: { color: C2.red },
2552
2699
  value: "Stop",
2553
2700
  fontSize: t.button,
2554
- color: C.text,
2701
+ color: C2.text,
2555
2702
  onMouseDown: () => this.config.onVideoStop?.()
2556
2703
  }
2557
- ), /* @__PURE__ */ import_react_ecs4.default.createElement(
2558
- import_react_ecs4.Button,
2704
+ ), /* @__PURE__ */ import_react_ecs5.default.createElement(
2705
+ import_react_ecs5.Button,
2559
2706
  {
2560
2707
  uiTransform: { width: this.s(100), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2561
- uiBackground: { color: C.btn },
2708
+ uiBackground: { color: C2.btn },
2562
2709
  value: "Reset Default",
2563
2710
  fontSize: t.buttonSmall,
2564
- color: C.text,
2711
+ color: C2.text,
2565
2712
  onMouseDown: () => this.config.onCommand?.("videoClear", {})
2566
2713
  }
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,
2714
+ )), /* @__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(
2715
+ import_react_ecs5.Button,
2569
2716
  {
2570
2717
  uiTransform: { height: this.s(24) },
2571
2718
  uiBackground: { color: import_math5.Color4.create(0, 0, 0, 0) },
2572
2719
  value: "Edit slots at thestatic.tv",
2573
2720
  fontSize: t.labelSmall,
2574
- color: C.cyan,
2721
+ color: C2.cyan,
2575
2722
  onMouseDown: () => (0, import_RestrictedActions3.openExternalUrl)({ url: this.config.footerLink || `https://thestatic.tv/scene/${this.config.sceneId}` })
2576
2723
  }
2577
2724
  ));
2578
2725
  };
2579
2726
  this.ModTab = () => {
2580
2727
  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,
2728
+ 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(
2729
+ import_react_ecs5.Input,
2583
2730
  {
2584
2731
  uiTransform: { width: this.s(230), height: this.s(UI_DIMENSIONS.admin.inputHeight) },
2585
2732
  uiBackground: { color: import_math5.Color4.create(0.15, 0.15, 0.2, 1) },
2586
2733
  placeholder: "Message to all players...",
2587
- placeholderColor: C.textDim,
2588
- color: C.text,
2734
+ placeholderColor: C2.textDim,
2735
+ color: C2.text,
2589
2736
  fontSize: t.input,
2590
2737
  value: this.broadcastText,
2591
2738
  onChange: (val) => {
2592
2739
  this.broadcastText = val;
2593
2740
  }
2594
2741
  }
2595
- ), /* @__PURE__ */ import_react_ecs4.default.createElement(
2596
- import_react_ecs4.Button,
2742
+ ), /* @__PURE__ */ import_react_ecs5.default.createElement(
2743
+ import_react_ecs5.Button,
2597
2744
  {
2598
2745
  uiTransform: { width: this.s(65), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 8 } },
2599
- uiBackground: { color: C.orange },
2746
+ uiBackground: { color: C2.orange },
2600
2747
  value: "Send",
2601
2748
  fontSize: t.button,
2602
- color: C.text,
2749
+ color: C2.text,
2603
2750
  onMouseDown: () => this.sendBroadcast()
2604
2751
  }
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,
2752
+ )), /* @__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(
2753
+ import_react_ecs5.Button,
2607
2754
  {
2608
2755
  uiTransform: { width: this.s(130), height: this.s(UI_DIMENSIONS.admin.buttonHeight), margin: 4 },
2609
- uiBackground: { color: C.red },
2756
+ uiBackground: { color: C2.red },
2610
2757
  value: "KICK ALL",
2611
2758
  fontSize: t.button,
2612
- color: C.text,
2759
+ color: C2.text,
2613
2760
  onMouseDown: () => {
2614
2761
  this.log("KICK ALL clicked");
2615
2762
  this.config.onCommand?.("kickAll", {});
2616
2763
  }
2617
2764
  }
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,
2765
+ )), /* @__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(
2766
+ import_react_ecs5.UiEntity,
2620
2767
  {
2621
2768
  key: `admin-${i}`,
2622
2769
  uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 5 }, width: "100%" }
2623
2770
  },
2624
- /* @__PURE__ */ import_react_ecs4.default.createElement(
2625
- import_react_ecs4.Label,
2771
+ /* @__PURE__ */ import_react_ecs5.default.createElement(
2772
+ import_react_ecs5.Label,
2626
2773
  {
2627
2774
  value: `${wallet.slice(0, 6)}...${wallet.slice(-4)}`,
2628
2775
  fontSize: t.label,
2629
- color: C.text,
2776
+ color: C2.text,
2630
2777
  uiTransform: { width: this.s(130) }
2631
2778
  }
2632
2779
  ),
2633
- /* @__PURE__ */ import_react_ecs4.default.createElement(
2634
- import_react_ecs4.Button,
2780
+ /* @__PURE__ */ import_react_ecs5.default.createElement(
2781
+ import_react_ecs5.Button,
2635
2782
  {
2636
2783
  uiTransform: { width: this.s(70), height: this.s(28), margin: { left: 8 } },
2637
- uiBackground: { color: C.btn },
2784
+ uiBackground: { color: C2.btn },
2638
2785
  value: "Remove",
2639
2786
  fontSize: t.buttonSmall,
2640
- color: C.text,
2787
+ color: C2.text,
2641
2788
  onMouseDown: () => this.removeSceneAdmin(wallet)
2642
2789
  }
2643
2790
  )
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,
2791
+ ))), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 14 } } }, /* @__PURE__ */ import_react_ecs5.default.createElement(
2792
+ import_react_ecs5.Input,
2646
2793
  {
2647
2794
  uiTransform: { width: this.s(230), height: this.s(UI_DIMENSIONS.admin.inputHeight) },
2648
2795
  uiBackground: { color: import_math5.Color4.create(0.15, 0.15, 0.2, 1) },
2649
2796
  placeholder: "0x... add admin",
2650
- placeholderColor: C.textDim,
2651
- color: C.text,
2797
+ placeholderColor: C2.textDim,
2798
+ color: C2.text,
2652
2799
  fontSize: t.input,
2653
2800
  value: this.newAdminWallet,
2654
2801
  onChange: (val) => {
2655
2802
  this.newAdminWallet = val;
2656
2803
  }
2657
2804
  }
2658
- ), /* @__PURE__ */ import_react_ecs4.default.createElement(
2659
- import_react_ecs4.Button,
2805
+ ), /* @__PURE__ */ import_react_ecs5.default.createElement(
2806
+ import_react_ecs5.Button,
2660
2807
  {
2661
2808
  uiTransform: { width: this.s(60), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 8 } },
2662
- uiBackground: { color: C.purple },
2809
+ uiBackground: { color: C2.purple },
2663
2810
  value: "Add",
2664
2811
  fontSize: t.button,
2665
- color: C.text,
2812
+ color: C2.text,
2666
2813
  onMouseDown: () => {
2667
2814
  if (this.newAdminWallet) this.addSceneAdmin(this.newAdminWallet);
2668
2815
  }
2669
2816
  }
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,
2817
+ )), /* @__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(
2818
+ import_react_ecs5.UiEntity,
2672
2819
  {
2673
2820
  key: `ban-${i}`,
2674
2821
  uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 5 }, width: "100%" }
2675
2822
  },
2676
- /* @__PURE__ */ import_react_ecs4.default.createElement(
2677
- import_react_ecs4.Label,
2823
+ /* @__PURE__ */ import_react_ecs5.default.createElement(
2824
+ import_react_ecs5.Label,
2678
2825
  {
2679
2826
  value: `${wallet.slice(0, 6)}...${wallet.slice(-4)}`,
2680
2827
  fontSize: t.label,
2681
- color: C.red,
2828
+ color: C2.red,
2682
2829
  uiTransform: { width: this.s(130) }
2683
2830
  }
2684
2831
  ),
2685
- /* @__PURE__ */ import_react_ecs4.default.createElement(
2686
- import_react_ecs4.Button,
2832
+ /* @__PURE__ */ import_react_ecs5.default.createElement(
2833
+ import_react_ecs5.Button,
2687
2834
  {
2688
2835
  uiTransform: { width: this.s(70), height: this.s(28), margin: { left: 8 } },
2689
- uiBackground: { color: C.green },
2836
+ uiBackground: { color: C2.green },
2690
2837
  value: "Unban",
2691
2838
  fontSize: t.buttonSmall,
2692
- color: C.text,
2839
+ color: C2.text,
2693
2840
  onMouseDown: () => this.unbanWallet(wallet)
2694
2841
  }
2695
2842
  )
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,
2843
+ ))), /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.UiEntity, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 10 } } }, /* @__PURE__ */ import_react_ecs5.default.createElement(
2844
+ import_react_ecs5.Input,
2698
2845
  {
2699
2846
  uiTransform: { width: this.s(230), height: this.s(UI_DIMENSIONS.admin.inputHeight) },
2700
2847
  uiBackground: { color: import_math5.Color4.create(0.15, 0.15, 0.2, 1) },
2701
2848
  placeholder: "0x... ban wallet",
2702
- placeholderColor: C.textDim,
2703
- color: C.text,
2849
+ placeholderColor: C2.textDim,
2850
+ color: C2.text,
2704
2851
  fontSize: t.input,
2705
2852
  value: this.newBanWallet,
2706
2853
  onChange: (val) => {
2707
2854
  this.newBanWallet = val;
2708
2855
  }
2709
2856
  }
2710
- ), /* @__PURE__ */ import_react_ecs4.default.createElement(
2711
- import_react_ecs4.Button,
2857
+ ), /* @__PURE__ */ import_react_ecs5.default.createElement(
2858
+ import_react_ecs5.Button,
2712
2859
  {
2713
2860
  uiTransform: { width: this.s(60), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 8 } },
2714
- uiBackground: { color: C.red },
2861
+ uiBackground: { color: C2.red },
2715
2862
  value: "Ban",
2716
2863
  fontSize: t.button,
2717
- color: C.text,
2864
+ color: C2.text,
2718
2865
  onMouseDown: () => {
2719
2866
  if (this.newBanWallet) this.banWallet(this.newBanWallet);
2720
2867
  }
2721
2868
  }
2722
- )), /* @__PURE__ */ import_react_ecs4.default.createElement(
2723
- import_react_ecs4.Button,
2869
+ )), /* @__PURE__ */ import_react_ecs5.default.createElement(
2870
+ import_react_ecs5.Button,
2724
2871
  {
2725
2872
  uiTransform: { height: this.s(24) },
2726
2873
  uiBackground: { color: import_math5.Color4.create(0, 0, 0, 0) },
2727
2874
  value: "Manage at thestatic.tv",
2728
2875
  fontSize: t.labelSmall,
2729
- color: C.cyan,
2876
+ color: C2.cyan,
2730
2877
  onMouseDown: () => (0, import_RestrictedActions3.openExternalUrl)({ url: this.config.footerLink || `https://thestatic.tv/scene/${this.config.sceneId}` })
2731
2878
  }
2732
2879
  ));
2733
2880
  };
2734
2881
  /**
2735
2882
  * Get the React-ECS component for the admin panel
2883
+ * Always renders NotificationBanner (for broadcasts), admin panel only for admins
2736
2884
  */
2737
2885
  this.getComponent = () => {
2738
- if (!this.isAdmin) return null;
2886
+ if (!this.isAdmin) {
2887
+ return /* @__PURE__ */ import_react_ecs5.default.createElement(NotificationBanner, null);
2888
+ }
2739
2889
  const t = this.theme;
2740
2890
  const tabs = [];
2741
2891
  if (this.config.sceneTabs && this.config.sceneTabs.length > 0) {
@@ -2750,8 +2900,8 @@ var AdminPanelUIModule = class {
2750
2900
  if (!tabs.find((tab) => tab.id === this.activeTab) && tabs.length > 0) {
2751
2901
  this.activeTab = tabs[0].id;
2752
2902
  }
2753
- return /* @__PURE__ */ import_react_ecs4.default.createElement(
2754
- import_react_ecs4.UiEntity,
2903
+ return /* @__PURE__ */ import_react_ecs5.default.createElement(
2904
+ import_react_ecs5.UiEntity,
2755
2905
  {
2756
2906
  uiTransform: {
2757
2907
  width: "100%",
@@ -2759,28 +2909,29 @@ var AdminPanelUIModule = class {
2759
2909
  positionType: "absolute"
2760
2910
  }
2761
2911
  },
2762
- /* @__PURE__ */ import_react_ecs4.default.createElement(
2763
- import_react_ecs4.UiEntity,
2912
+ /* @__PURE__ */ import_react_ecs5.default.createElement(NotificationBanner, null),
2913
+ /* @__PURE__ */ import_react_ecs5.default.createElement(
2914
+ import_react_ecs5.UiEntity,
2764
2915
  {
2765
2916
  uiTransform: {
2766
2917
  position: { right: 20 + this.s(100) + 10 + this.s(100) + 10, bottom: 10 },
2767
2918
  positionType: "absolute"
2768
2919
  }
2769
2920
  },
2770
- /* @__PURE__ */ import_react_ecs4.default.createElement(
2771
- import_react_ecs4.Button,
2921
+ /* @__PURE__ */ import_react_ecs5.default.createElement(
2922
+ import_react_ecs5.Button,
2772
2923
  {
2773
2924
  uiTransform: { width: this.s(100), height: this.s(45) },
2774
- uiBackground: { color: this.panelOpen ? C.btn : C.header },
2925
+ uiBackground: { color: this.panelOpen ? C2.btn : C2.header },
2775
2926
  value: this.panelOpen ? "CLOSE" : "ADMIN",
2776
2927
  fontSize: this.s(14),
2777
- color: C.text,
2928
+ color: C2.text,
2778
2929
  onMouseDown: () => this.toggle()
2779
2930
  }
2780
2931
  )
2781
2932
  ),
2782
- this.panelOpen && /* @__PURE__ */ import_react_ecs4.default.createElement(
2783
- import_react_ecs4.UiEntity,
2933
+ this.panelOpen && /* @__PURE__ */ import_react_ecs5.default.createElement(
2934
+ import_react_ecs5.UiEntity,
2784
2935
  {
2785
2936
  key: `admin-panel-${this.client.uiScale}`,
2786
2937
  uiTransform: {
@@ -2791,10 +2942,10 @@ var AdminPanelUIModule = class {
2791
2942
  positionType: "absolute",
2792
2943
  flexDirection: "column"
2793
2944
  },
2794
- uiBackground: { color: C.bg }
2945
+ uiBackground: { color: C2.bg }
2795
2946
  },
2796
- /* @__PURE__ */ import_react_ecs4.default.createElement(
2797
- import_react_ecs4.UiEntity,
2947
+ /* @__PURE__ */ import_react_ecs5.default.createElement(
2948
+ import_react_ecs5.UiEntity,
2798
2949
  {
2799
2950
  uiTransform: {
2800
2951
  width: "100%",
@@ -2804,11 +2955,11 @@ var AdminPanelUIModule = class {
2804
2955
  flexDirection: "row",
2805
2956
  padding: { left: 12, right: 8 }
2806
2957
  },
2807
- uiBackground: { color: C.header }
2958
+ uiBackground: { color: C2.header }
2808
2959
  },
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,
2960
+ /* @__PURE__ */ import_react_ecs5.default.createElement(import_react_ecs5.Label, { value: this.config.title || "ADMIN PANEL", fontSize: t.header, color: C2.text }),
2961
+ /* @__PURE__ */ import_react_ecs5.default.createElement(
2962
+ import_react_ecs5.Button,
2812
2963
  {
2813
2964
  uiTransform: { width: this.s(28), height: this.s(28) },
2814
2965
  uiBackground: { color: import_math5.Color4.create(0, 0, 0, 0.3) },
@@ -2819,9 +2970,9 @@ var AdminPanelUIModule = class {
2819
2970
  }
2820
2971
  )
2821
2972
  ),
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,
2973
+ /* @__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 }))),
2974
+ /* @__PURE__ */ import_react_ecs5.default.createElement(
2975
+ import_react_ecs5.UiEntity,
2825
2976
  {
2826
2977
  uiTransform: {
2827
2978
  width: "100%",
@@ -2830,12 +2981,12 @@ var AdminPanelUIModule = class {
2830
2981
  flexDirection: "column"
2831
2982
  }
2832
2983
  },
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),
2984
+ this.activeTab === "video" && /* @__PURE__ */ import_react_ecs5.default.createElement(this.VideoTab, null),
2985
+ this.activeTab === "mod" && this.isOwner && /* @__PURE__ */ import_react_ecs5.default.createElement(this.ModTab, null),
2835
2986
  this.config.sceneTabs?.map((tab) => this.activeTab === tab.id && tab.render())
2836
2987
  ),
2837
- /* @__PURE__ */ import_react_ecs4.default.createElement(
2838
- import_react_ecs4.UiEntity,
2988
+ /* @__PURE__ */ import_react_ecs5.default.createElement(
2989
+ import_react_ecs5.UiEntity,
2839
2990
  {
2840
2991
  uiTransform: {
2841
2992
  width: "100%",
@@ -2843,16 +2994,16 @@ var AdminPanelUIModule = class {
2843
2994
  justifyContent: "center",
2844
2995
  alignItems: "center"
2845
2996
  },
2846
- uiBackground: { color: C.section }
2997
+ uiBackground: { color: C2.section }
2847
2998
  },
2848
- /* @__PURE__ */ import_react_ecs4.default.createElement(
2849
- import_react_ecs4.Button,
2999
+ /* @__PURE__ */ import_react_ecs5.default.createElement(
3000
+ import_react_ecs5.Button,
2850
3001
  {
2851
3002
  uiTransform: { height: this.s(24) },
2852
3003
  uiBackground: { color: import_math5.Color4.create(0, 0, 0, 0) },
2853
3004
  value: `thestatic.tv/scene/${this.config.sceneId}`,
2854
3005
  fontSize: t.labelSmall,
2855
- color: C.cyan,
3006
+ color: C2.cyan,
2856
3007
  onMouseDown: () => (0, import_RestrictedActions3.openExternalUrl)({ url: this.config.footerLink || `https://thestatic.tv/scene/${this.config.sceneId}` })
2857
3008
  }
2858
3009
  )
@@ -2875,7 +3026,7 @@ var AdminPanelUIModule = class {
2875
3026
  };
2876
3027
  this.baseUrl = client.getBaseUrl();
2877
3028
  if (config.headerColor) {
2878
- C.header = import_math5.Color4.create(
3029
+ C2.header = import_math5.Color4.create(
2879
3030
  config.headerColor.r,
2880
3031
  config.headerColor.g,
2881
3032
  config.headerColor.b,
@@ -2903,6 +3054,7 @@ var AdminPanelUIModule = class {
2903
3054
  await this.checkAdminStatus();
2904
3055
  await this.fetchVideoState();
2905
3056
  this.autoPlayDefault();
3057
+ this.startCommandPolling();
2906
3058
  this.log("Initialized");
2907
3059
  }
2908
3060
  /**
@@ -3008,6 +3160,61 @@ var AdminPanelUIModule = class {
3008
3160
  this.log("Stream polling stopped");
3009
3161
  }
3010
3162
  }
3163
+ // --- Command Polling (receives broadcasts from website) ---
3164
+ startCommandPolling() {
3165
+ if (this.commandPollIntervalId !== null) return;
3166
+ this.commandPollIntervalId = dclSetInterval(() => {
3167
+ this.pollCommands();
3168
+ }, 5e3);
3169
+ this.pollCommands();
3170
+ this.log("Command polling started");
3171
+ }
3172
+ stopCommandPolling() {
3173
+ if (this.commandPollIntervalId !== null) {
3174
+ dclClearInterval(this.commandPollIntervalId);
3175
+ this.commandPollIntervalId = null;
3176
+ this.log("Command polling stopped");
3177
+ }
3178
+ }
3179
+ async pollCommands() {
3180
+ try {
3181
+ const res = await fetch(`${this.baseUrl}/scene/${this.config.sceneId}/commands`);
3182
+ if (!res.ok) return;
3183
+ const data = await res.json();
3184
+ const commands = data.commands || [];
3185
+ for (const cmd of commands) {
3186
+ if (cmd.timestamp <= this.lastCommandTimestamp) continue;
3187
+ this.lastCommandTimestamp = cmd.timestamp;
3188
+ if (cmd.type === "broadcast" && cmd.payload?.text) {
3189
+ const msg = cmd.payload.from ? `${cmd.payload.from}: ${cmd.payload.text}` : cmd.payload.text;
3190
+ this.log("Received broadcast:", msg);
3191
+ this.config.onBroadcast?.(msg);
3192
+ } else if (cmd.type === "clearBroadcast") {
3193
+ this.log("Received clearBroadcast");
3194
+ hideNotification();
3195
+ } else if ((cmd.type === "videoPlay" || cmd.type === "videoSetUrl") && cmd.payload?.url) {
3196
+ this.log("Received video play:", cmd.type, cmd.payload.url);
3197
+ this.config.onVideoPlay?.(cmd.payload.url);
3198
+ } else if (cmd.type === "videoStop" || cmd.type === "videoClear") {
3199
+ this.log("Received video stop/clear:", cmd.type);
3200
+ this.config.onVideoStop?.();
3201
+ } else if (cmd.type === "videoPlaySlot" && cmd.payload?.slot) {
3202
+ const slotId = cmd.payload.slot;
3203
+ if (cmd.payload.url) {
3204
+ this.log("Received videoPlaySlot with URL:", slotId, cmd.payload.url);
3205
+ this.config.onVideoPlay?.(cmd.payload.url);
3206
+ } else {
3207
+ this.log("Received videoPlaySlot:", slotId);
3208
+ this.playSlot(slotId);
3209
+ }
3210
+ } else if (this.config.onCommand) {
3211
+ this.log("Received command:", cmd.type, cmd.payload);
3212
+ this.config.onCommand(cmd.type, cmd.payload);
3213
+ }
3214
+ }
3215
+ } catch (err) {
3216
+ }
3217
+ }
3011
3218
  // --- Stream API Calls ---
3012
3219
  async fetchStreamData() {
3013
3220
  if (this.streamFetched || !this.playerWallet) return;
@@ -3439,87 +3646,6 @@ var AdminPanelUIModule = class {
3439
3646
  }
3440
3647
  };
3441
3648
 
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
3649
  // src/StaticTVClient.ts
3524
3650
  var import_ecs3 = require("@dcl/sdk/ecs");
3525
3651
  var utils = __toESM(require("@dcl-sdk/utils"));
@@ -3800,10 +3926,13 @@ var StaticTVClient = class {
3800
3926
  */
3801
3927
  stopVideo() {
3802
3928
  const screen = this.config.videoScreen;
3929
+ this._pendingVideoData = null;
3930
+ this._streamVerified = false;
3931
+ this._clearVerificationTimeout();
3932
+ if (this.guideUI) {
3933
+ this.guideUI.currentVideoId = null;
3934
+ }
3803
3935
  if (screen !== void 0) {
3804
- if (this.guideUI) {
3805
- this.guideUI.currentVideoId = null;
3806
- }
3807
3936
  const fallbackUrl = this.config.fallbackVideoUrl;
3808
3937
  const fallbackDisabled = fallbackUrl === "";
3809
3938
  if (fallbackDisabled) {
@@ -3959,20 +4088,60 @@ var StaticTVClient = class {
3959
4088
  }
3960
4089
  /**
3961
4090
  * Play video with stream verification for live content
4091
+ * Works with both videoScreen (SDK-managed) and onVideoPlay (user-managed) modes.
3962
4092
  * @internal
3963
4093
  */
3964
4094
  _playVideoWithVerification(video, isLiveStream) {
3965
4095
  const screen = this.config.videoScreen;
4096
+ const hasCallback = this.config.onVideoPlay !== void 0;
4097
+ this.log(`Playing video: ${video.src} (live: ${isLiveStream}, screen: ${screen !== void 0}, callback: ${hasCallback})`);
4098
+ this._currentVideoUrl = video.src;
4099
+ this._clearVerificationTimeout();
4100
+ this._streamVerified = false;
3966
4101
  if (screen !== void 0) {
3967
- this.log(`Playing video: ${video.src} (live: ${isLiveStream})`);
3968
- this._currentVideoUrl = video.src;
3969
4102
  this._playVideoInternal(video.src, false, isLiveStream);
3970
- if (this.guideUI) {
3971
- this.guideUI.currentVideoId = video.id;
3972
- }
3973
4103
  }
3974
- if (this.config.onVideoPlay) {
4104
+ if (hasCallback) {
3975
4105
  this.config.onVideoPlay(video.src);
4106
+ if (isLiveStream) {
4107
+ this._startCallbackVerification(video);
4108
+ }
4109
+ }
4110
+ if (this.guideUI) {
4111
+ this.guideUI.currentVideoId = video.id;
4112
+ }
4113
+ }
4114
+ /**
4115
+ * Start stream verification for callback-based video playback
4116
+ * Shows CONNECTING state and triggers fallback if verification times out
4117
+ * @internal
4118
+ */
4119
+ _startCallbackVerification(video) {
4120
+ this.showNotification(`Connecting to ${video.name}...`, 6e3);
4121
+ this._verificationTimeoutId = utils.timers.setTimeout(() => {
4122
+ if (!this._streamVerified && this._pendingVideoData) {
4123
+ this.log(`Stream verification timeout (callback mode): ${video.name}`);
4124
+ this._handleStreamOffline();
4125
+ }
4126
+ }, 5e3);
4127
+ }
4128
+ /**
4129
+ * Call this to confirm that a video stream is playing successfully.
4130
+ * Use this when you're managing video playback yourself (onVideoPlay callback).
4131
+ *
4132
+ * @example
4133
+ * ```typescript
4134
+ * // In your video player's onReady or similar event:
4135
+ * videoPlayer.events.on('playing', () => {
4136
+ * staticTV.confirmVideoPlaying()
4137
+ * })
4138
+ * ```
4139
+ */
4140
+ confirmVideoPlaying() {
4141
+ if (this._pendingVideoData && !this._streamVerified) {
4142
+ this._streamVerified = true;
4143
+ this._clearVerificationTimeout();
4144
+ this.log(`Stream confirmed playing: ${this._pendingVideoData.name}`);
3976
4145
  }
3977
4146
  }
3978
4147
  /**
@@ -4247,11 +4416,13 @@ var StaticTVClient = class {
4247
4416
  InteractionsModule,
4248
4417
  KEY_TYPE_CHANNEL,
4249
4418
  KEY_TYPE_SCENE,
4419
+ NotificationBanner,
4250
4420
  SessionModule,
4251
4421
  StaticTVClient,
4252
4422
  fetchUserData,
4253
4423
  getPlayerDisplayName,
4254
4424
  getPlayerWallet,
4425
+ hideNotification,
4255
4426
  setupStaticUI,
4256
4427
  showNotification
4257
4428
  });