@thestatic-tv/dcl-sdk 2.3.0-beta.0 → 2.3.0-beta.2

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.d.mts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Entity as Entity$1 } from '@dcl/sdk/ecs';
1
2
  import ReactEcs from '@dcl/sdk/react-ecs';
2
3
 
3
4
  /**
@@ -19,12 +20,57 @@ interface PlayerData {
19
20
  /** Player's display name */
20
21
  name?: string;
21
22
  }
23
+
24
+ type Entity = Entity$1;
22
25
  /**
23
26
  * Configuration options for StaticTVClient
24
27
  */
25
28
  interface StaticTVConfig {
26
29
  /** Your channel's API key from thestatic.tv studio */
27
30
  apiKey: string;
31
+ /**
32
+ * Video screen entity - SDK automatically manages VideoPlayer on this entity.
33
+ * When user selects a video from Guide or Admin plays a URL, it plays here.
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * const screen = engine.addEntity()
38
+ * Transform.create(screen, { position: Vector3.create(8, 3, 14), scale: Vector3.create(8, 4.5, 0.1) })
39
+ * MeshRenderer.setPlane(screen)
40
+ *
41
+ * const staticTV = new StaticTVClient({
42
+ * apiKey: 'dcls_...',
43
+ * videoScreen: screen // SDK handles all video playback!
44
+ * })
45
+ * ```
46
+ */
47
+ videoScreen?: Entity;
48
+ /**
49
+ * Scene ID for Admin Panel API calls.
50
+ * Optional - defaults to API key ID if not provided.
51
+ */
52
+ sceneId?: string;
53
+ /**
54
+ * Callback when video URL should be played (Pro tier).
55
+ * If videoScreen is set, this is called AFTER SDK plays the video.
56
+ * Use for custom logic like logging, UI updates, etc.
57
+ */
58
+ onVideoPlay?: (url: string) => void;
59
+ /**
60
+ * Callback when video playback should stop (Pro tier).
61
+ * If videoScreen is set, this is called AFTER SDK stops the video.
62
+ */
63
+ onVideoStop?: () => void;
64
+ /**
65
+ * Callback for broadcast messages (Pro tier).
66
+ * Default: shows notification via staticTV.showNotification()
67
+ */
68
+ onBroadcast?: (text: string) => void;
69
+ /**
70
+ * Command handler for scene-specific commands (Pro tier).
71
+ * Called when admin triggers a command from the panel.
72
+ */
73
+ onCommand?: (type: string, payload?: Record<string, unknown>) => void;
28
74
  /** Automatically start session tracking on init (default: true) */
29
75
  autoStartSession?: boolean;
30
76
  /** Interval for session heartbeats in ms (default: 30000) */
@@ -47,11 +93,11 @@ interface StaticTVConfig {
47
93
  */
48
94
  player?: PlayerData;
49
95
  /**
50
- * Guide UI configuration (channel keys only)
96
+ * Guide UI configuration
51
97
  */
52
98
  guideUI?: GuideUIConfig;
53
99
  /**
54
- * Chat UI configuration (channel keys only)
100
+ * Chat UI configuration
55
101
  */
56
102
  chatUI?: ChatUIConfig;
57
103
  }
@@ -840,16 +886,28 @@ declare class StaticTVClient {
840
886
  *
841
887
  * @param config Configuration options
842
888
  *
843
- * @example
889
+ * @example Free tier (session tracking only):
844
890
  * ```typescript
845
- * let staticTV: StaticTVClient
891
+ * const staticTV = new StaticTVClient({
892
+ * apiKey: 'dcls_your_key_here'
893
+ * })
894
+ * ```
895
+ *
896
+ * @example Standard/Pro with video screen (SDK handles playback):
897
+ * ```typescript
898
+ * // Create your video screen
899
+ * const screen = engine.addEntity()
900
+ * Transform.create(screen, { position: Vector3.create(8, 3, 14), scale: Vector3.create(8, 4.5, 0.1) })
901
+ * MeshRenderer.setPlane(screen)
902
+ *
903
+ * // SDK handles everything - Guide selection + Pro admin controls just work
904
+ * const staticTV = new StaticTVClient({
905
+ * apiKey: 'dcls_your_key_here',
906
+ * videoScreen: screen // That's it! SDK manages video playback
907
+ * })
846
908
  *
847
909
  * export function main() {
848
- * // All keys use dcls_ prefix - features determined by subscription
849
- * staticTV = new StaticTVClient({
850
- * apiKey: 'dcls_your_key_here'
851
- * })
852
- * // Session tracking starts automatically!
910
+ * staticTV.setupUI()
853
911
  * }
854
912
  * ```
855
913
  */
@@ -900,6 +958,33 @@ declare class StaticTVClient {
900
958
  * @internal
901
959
  */
902
960
  getConfig(): StaticTVConfig;
961
+ private _currentVideoUrl;
962
+ /**
963
+ * Play a video on the configured videoScreen entity.
964
+ * Called by Guide UI and Admin Panel.
965
+ *
966
+ * @param url Video URL to play
967
+ */
968
+ playVideo(url: string): void;
969
+ /**
970
+ * Stop video playback on the configured videoScreen entity.
971
+ * Called by Admin Panel.
972
+ */
973
+ stopVideo(): void;
974
+ /**
975
+ * Get the currently playing video URL
976
+ */
977
+ get currentVideoUrl(): string;
978
+ /**
979
+ * Internal handler for Guide video selection
980
+ * @internal
981
+ */
982
+ _handleGuideVideoSelect(video: GuideVideo): void;
983
+ /**
984
+ * Internal handler for broadcast messages
985
+ * @internal
986
+ */
987
+ _handleBroadcast(text: string): void;
903
988
  /**
904
989
  * Initialize standard feature modules (guide, heartbeat, interactions, UI)
905
990
  * @internal
@@ -939,28 +1024,30 @@ declare class StaticTVClient {
939
1024
  get sdkType(): 'lite' | 'full';
940
1025
  /**
941
1026
  * Configure Pro features (Admin Panel with Video + Mod tabs)
942
- * Call this after creating the client to configure admin panel.
943
- * The panel will auto-enable when server confirms Pro tier.
944
1027
  *
945
- * @param config Admin panel configuration (optional - defaults work for basic usage)
1028
+ * **OPTIONAL**: If you set `videoScreen` in the constructor, you don't need
1029
+ * to call this method - the Admin Panel will auto-enable with sensible defaults.
946
1030
  *
947
- * @example
948
- * ```typescript
949
- * const staticTV = new StaticTVClient({ apiKey: 'dcls_...' })
1031
+ * Use this method only if you need:
1032
+ * - Custom scene tab UI
1033
+ * - Custom panel title/styling
1034
+ * - Advanced command handlers
950
1035
  *
951
- * // Simplest usage - just enable with callbacks:
952
- * staticTV.enableProFeatures({
953
- * onVideoPlay: (url) => videoPlayer.play(url),
954
- * onVideoStop: () => videoPlayer.stop()
1036
+ * @param config Admin panel configuration
1037
+ *
1038
+ * @example Using videoScreen (recommended - no enableProFeatures needed):
1039
+ * ```typescript
1040
+ * const staticTV = new StaticTVClient({
1041
+ * apiKey: 'dcls_...',
1042
+ * videoScreen: myScreen // Admin Panel auto-enabled for Pro tier!
955
1043
  * })
1044
+ * ```
956
1045
  *
957
- * // Advanced usage - custom sceneId and title:
1046
+ * @example Advanced: Custom scene tabs
1047
+ * ```typescript
958
1048
  * staticTV.enableProFeatures({
959
- * sceneId: 'my-scene', // optional - defaults to API key ID
960
1049
  * title: 'MY SCENE ADMIN',
961
- * onVideoPlay: (url) => videoPlayer.play(url),
962
- * onVideoStop: () => videoPlayer.stop(),
963
- * onBroadcast: (text) => showNotification(text)
1050
+ * sceneTabs: [{ label: 'LIGHTS', id: 'lights', render: () => <LightsTab /> }]
964
1051
  * })
965
1052
  * ```
966
1053
  */
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Entity as Entity$1 } from '@dcl/sdk/ecs';
1
2
  import ReactEcs from '@dcl/sdk/react-ecs';
2
3
 
3
4
  /**
@@ -19,12 +20,57 @@ interface PlayerData {
19
20
  /** Player's display name */
20
21
  name?: string;
21
22
  }
23
+
24
+ type Entity = Entity$1;
22
25
  /**
23
26
  * Configuration options for StaticTVClient
24
27
  */
25
28
  interface StaticTVConfig {
26
29
  /** Your channel's API key from thestatic.tv studio */
27
30
  apiKey: string;
31
+ /**
32
+ * Video screen entity - SDK automatically manages VideoPlayer on this entity.
33
+ * When user selects a video from Guide or Admin plays a URL, it plays here.
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * const screen = engine.addEntity()
38
+ * Transform.create(screen, { position: Vector3.create(8, 3, 14), scale: Vector3.create(8, 4.5, 0.1) })
39
+ * MeshRenderer.setPlane(screen)
40
+ *
41
+ * const staticTV = new StaticTVClient({
42
+ * apiKey: 'dcls_...',
43
+ * videoScreen: screen // SDK handles all video playback!
44
+ * })
45
+ * ```
46
+ */
47
+ videoScreen?: Entity;
48
+ /**
49
+ * Scene ID for Admin Panel API calls.
50
+ * Optional - defaults to API key ID if not provided.
51
+ */
52
+ sceneId?: string;
53
+ /**
54
+ * Callback when video URL should be played (Pro tier).
55
+ * If videoScreen is set, this is called AFTER SDK plays the video.
56
+ * Use for custom logic like logging, UI updates, etc.
57
+ */
58
+ onVideoPlay?: (url: string) => void;
59
+ /**
60
+ * Callback when video playback should stop (Pro tier).
61
+ * If videoScreen is set, this is called AFTER SDK stops the video.
62
+ */
63
+ onVideoStop?: () => void;
64
+ /**
65
+ * Callback for broadcast messages (Pro tier).
66
+ * Default: shows notification via staticTV.showNotification()
67
+ */
68
+ onBroadcast?: (text: string) => void;
69
+ /**
70
+ * Command handler for scene-specific commands (Pro tier).
71
+ * Called when admin triggers a command from the panel.
72
+ */
73
+ onCommand?: (type: string, payload?: Record<string, unknown>) => void;
28
74
  /** Automatically start session tracking on init (default: true) */
29
75
  autoStartSession?: boolean;
30
76
  /** Interval for session heartbeats in ms (default: 30000) */
@@ -47,11 +93,11 @@ interface StaticTVConfig {
47
93
  */
48
94
  player?: PlayerData;
49
95
  /**
50
- * Guide UI configuration (channel keys only)
96
+ * Guide UI configuration
51
97
  */
52
98
  guideUI?: GuideUIConfig;
53
99
  /**
54
- * Chat UI configuration (channel keys only)
100
+ * Chat UI configuration
55
101
  */
56
102
  chatUI?: ChatUIConfig;
57
103
  }
@@ -840,16 +886,28 @@ declare class StaticTVClient {
840
886
  *
841
887
  * @param config Configuration options
842
888
  *
843
- * @example
889
+ * @example Free tier (session tracking only):
844
890
  * ```typescript
845
- * let staticTV: StaticTVClient
891
+ * const staticTV = new StaticTVClient({
892
+ * apiKey: 'dcls_your_key_here'
893
+ * })
894
+ * ```
895
+ *
896
+ * @example Standard/Pro with video screen (SDK handles playback):
897
+ * ```typescript
898
+ * // Create your video screen
899
+ * const screen = engine.addEntity()
900
+ * Transform.create(screen, { position: Vector3.create(8, 3, 14), scale: Vector3.create(8, 4.5, 0.1) })
901
+ * MeshRenderer.setPlane(screen)
902
+ *
903
+ * // SDK handles everything - Guide selection + Pro admin controls just work
904
+ * const staticTV = new StaticTVClient({
905
+ * apiKey: 'dcls_your_key_here',
906
+ * videoScreen: screen // That's it! SDK manages video playback
907
+ * })
846
908
  *
847
909
  * export function main() {
848
- * // All keys use dcls_ prefix - features determined by subscription
849
- * staticTV = new StaticTVClient({
850
- * apiKey: 'dcls_your_key_here'
851
- * })
852
- * // Session tracking starts automatically!
910
+ * staticTV.setupUI()
853
911
  * }
854
912
  * ```
855
913
  */
@@ -900,6 +958,33 @@ declare class StaticTVClient {
900
958
  * @internal
901
959
  */
902
960
  getConfig(): StaticTVConfig;
961
+ private _currentVideoUrl;
962
+ /**
963
+ * Play a video on the configured videoScreen entity.
964
+ * Called by Guide UI and Admin Panel.
965
+ *
966
+ * @param url Video URL to play
967
+ */
968
+ playVideo(url: string): void;
969
+ /**
970
+ * Stop video playback on the configured videoScreen entity.
971
+ * Called by Admin Panel.
972
+ */
973
+ stopVideo(): void;
974
+ /**
975
+ * Get the currently playing video URL
976
+ */
977
+ get currentVideoUrl(): string;
978
+ /**
979
+ * Internal handler for Guide video selection
980
+ * @internal
981
+ */
982
+ _handleGuideVideoSelect(video: GuideVideo): void;
983
+ /**
984
+ * Internal handler for broadcast messages
985
+ * @internal
986
+ */
987
+ _handleBroadcast(text: string): void;
903
988
  /**
904
989
  * Initialize standard feature modules (guide, heartbeat, interactions, UI)
905
990
  * @internal
@@ -939,28 +1024,30 @@ declare class StaticTVClient {
939
1024
  get sdkType(): 'lite' | 'full';
940
1025
  /**
941
1026
  * Configure Pro features (Admin Panel with Video + Mod tabs)
942
- * Call this after creating the client to configure admin panel.
943
- * The panel will auto-enable when server confirms Pro tier.
944
1027
  *
945
- * @param config Admin panel configuration (optional - defaults work for basic usage)
1028
+ * **OPTIONAL**: If you set `videoScreen` in the constructor, you don't need
1029
+ * to call this method - the Admin Panel will auto-enable with sensible defaults.
946
1030
  *
947
- * @example
948
- * ```typescript
949
- * const staticTV = new StaticTVClient({ apiKey: 'dcls_...' })
1031
+ * Use this method only if you need:
1032
+ * - Custom scene tab UI
1033
+ * - Custom panel title/styling
1034
+ * - Advanced command handlers
950
1035
  *
951
- * // Simplest usage - just enable with callbacks:
952
- * staticTV.enableProFeatures({
953
- * onVideoPlay: (url) => videoPlayer.play(url),
954
- * onVideoStop: () => videoPlayer.stop()
1036
+ * @param config Admin panel configuration
1037
+ *
1038
+ * @example Using videoScreen (recommended - no enableProFeatures needed):
1039
+ * ```typescript
1040
+ * const staticTV = new StaticTVClient({
1041
+ * apiKey: 'dcls_...',
1042
+ * videoScreen: myScreen // Admin Panel auto-enabled for Pro tier!
955
1043
  * })
1044
+ * ```
956
1045
  *
957
- * // Advanced usage - custom sceneId and title:
1046
+ * @example Advanced: Custom scene tabs
1047
+ * ```typescript
958
1048
  * staticTV.enableProFeatures({
959
- * sceneId: 'my-scene', // optional - defaults to API key ID
960
1049
  * title: 'MY SCENE ADMIN',
961
- * onVideoPlay: (url) => videoPlayer.play(url),
962
- * onVideoStop: () => videoPlayer.stop(),
963
- * onBroadcast: (text) => showNotification(text)
1050
+ * sceneTabs: [{ label: 'LIGHTS', id: 'lights', render: () => <LightsTab /> }]
964
1051
  * })
965
1052
  * ```
966
1053
  */
package/dist/index.js CHANGED
@@ -1441,7 +1441,7 @@ var GuideUIModule = class {
1441
1441
  renderToggleButton() {
1442
1442
  const buttonText = this._isVisible ? "CLOSE" : "GUIDE";
1443
1443
  const buttonColor = this._isVisible ? import_math3.Color4.create(0.2, 0.2, 0.28, 0.9) : import_math3.Color4.create(0, 0.5, 0.5, 0.9);
1444
- const buttonPos = 20 + this.s(100) + 10 + this.s(100) + 10;
1444
+ const buttonPos = 20 + this.s(100) + 10;
1445
1445
  return import_react_ecs2.default.createElement(import_react_ecs2.UiEntity, {
1446
1446
  uiTransform: {
1447
1447
  positionType: "absolute",
@@ -2734,7 +2734,7 @@ var AdminPanelUIModule = class {
2734
2734
  import_react_ecs4.UiEntity,
2735
2735
  {
2736
2736
  uiTransform: {
2737
- position: { right: 20 + this.s(100) + 10, bottom: 10 },
2737
+ position: { right: 20 + this.s(100) + 10 + this.s(100) + 10, bottom: 10 },
2738
2738
  positionType: "absolute"
2739
2739
  }
2740
2740
  },
@@ -3492,6 +3492,7 @@ function setupStaticUI(client) {
3492
3492
  }
3493
3493
 
3494
3494
  // src/StaticTVClient.ts
3495
+ var import_ecs3 = require("@dcl/sdk/ecs");
3495
3496
  var DEFAULT_BASE_URL = "https://thestatic.tv/api/v1/dcl";
3496
3497
  var KEY_TYPE_CHANNEL = "channel";
3497
3498
  var KEY_TYPE_SCENE = "scene";
@@ -3501,16 +3502,28 @@ var StaticTVClient = class {
3501
3502
  *
3502
3503
  * @param config Configuration options
3503
3504
  *
3504
- * @example
3505
+ * @example Free tier (session tracking only):
3506
+ * ```typescript
3507
+ * const staticTV = new StaticTVClient({
3508
+ * apiKey: 'dcls_your_key_here'
3509
+ * })
3510
+ * ```
3511
+ *
3512
+ * @example Standard/Pro with video screen (SDK handles playback):
3505
3513
  * ```typescript
3506
- * let staticTV: StaticTVClient
3514
+ * // Create your video screen
3515
+ * const screen = engine.addEntity()
3516
+ * Transform.create(screen, { position: Vector3.create(8, 3, 14), scale: Vector3.create(8, 4.5, 0.1) })
3517
+ * MeshRenderer.setPlane(screen)
3518
+ *
3519
+ * // SDK handles everything - Guide selection + Pro admin controls just work
3520
+ * const staticTV = new StaticTVClient({
3521
+ * apiKey: 'dcls_your_key_here',
3522
+ * videoScreen: screen // That's it! SDK manages video playback
3523
+ * })
3507
3524
  *
3508
3525
  * export function main() {
3509
- * // All keys use dcls_ prefix - features determined by subscription
3510
- * staticTV = new StaticTVClient({
3511
- * apiKey: 'dcls_your_key_here'
3512
- * })
3513
- * // Session tracking starts automatically!
3526
+ * staticTV.setupUI()
3514
3527
  * }
3515
3528
  * ```
3516
3529
  */
@@ -3538,6 +3551,10 @@ var StaticTVClient = class {
3538
3551
  this.adminPanel = null;
3539
3552
  /** UI scale - fixed at 1.0. DCL's UI system auto-scales based on viewport. */
3540
3553
  this.uiScale = 1;
3554
+ // =============================================================================
3555
+ // --- VIDEO PLAYBACK (Internal handler for videoScreen) ---
3556
+ // =============================================================================
3557
+ this._currentVideoUrl = "";
3541
3558
  this.config = {
3542
3559
  autoStartSession: true,
3543
3560
  sessionHeartbeatInterval: 3e4,
@@ -3667,6 +3684,84 @@ var StaticTVClient = class {
3667
3684
  getConfig() {
3668
3685
  return this.config;
3669
3686
  }
3687
+ /**
3688
+ * Play a video on the configured videoScreen entity.
3689
+ * Called by Guide UI and Admin Panel.
3690
+ *
3691
+ * @param url Video URL to play
3692
+ */
3693
+ playVideo(url) {
3694
+ const screen = this.config.videoScreen;
3695
+ if (screen !== void 0) {
3696
+ this.log(`Playing video: ${url}`);
3697
+ this._currentVideoUrl = url;
3698
+ if (import_ecs3.VideoPlayer.has(screen)) {
3699
+ import_ecs3.VideoPlayer.deleteFrom(screen);
3700
+ }
3701
+ import_ecs3.VideoPlayer.create(screen, {
3702
+ src: url,
3703
+ playing: true,
3704
+ volume: 1
3705
+ });
3706
+ import_ecs3.Material.setBasicMaterial(screen, {
3707
+ texture: import_ecs3.Material.Texture.Video({ videoPlayerEntity: screen })
3708
+ });
3709
+ if (this.guideUI) {
3710
+ const videos = this.guideUI.getVideos();
3711
+ const video = videos.find((v) => v.src === url);
3712
+ if (video) {
3713
+ this.guideUI.currentVideoId = video.id;
3714
+ }
3715
+ }
3716
+ }
3717
+ if (this.config.onVideoPlay) {
3718
+ this.config.onVideoPlay(url);
3719
+ }
3720
+ }
3721
+ /**
3722
+ * Stop video playback on the configured videoScreen entity.
3723
+ * Called by Admin Panel.
3724
+ */
3725
+ stopVideo() {
3726
+ const screen = this.config.videoScreen;
3727
+ if (screen !== void 0 && import_ecs3.VideoPlayer.has(screen)) {
3728
+ this.log("Stopping video");
3729
+ import_ecs3.VideoPlayer.getMutable(screen).playing = false;
3730
+ this._currentVideoUrl = "";
3731
+ if (this.guideUI) {
3732
+ this.guideUI.currentVideoId = null;
3733
+ }
3734
+ }
3735
+ if (this.config.onVideoStop) {
3736
+ this.config.onVideoStop();
3737
+ }
3738
+ }
3739
+ /**
3740
+ * Get the currently playing video URL
3741
+ */
3742
+ get currentVideoUrl() {
3743
+ return this._currentVideoUrl;
3744
+ }
3745
+ /**
3746
+ * Internal handler for Guide video selection
3747
+ * @internal
3748
+ */
3749
+ _handleGuideVideoSelect(video) {
3750
+ if (video.src) {
3751
+ this.playVideo(video.src);
3752
+ }
3753
+ }
3754
+ /**
3755
+ * Internal handler for broadcast messages
3756
+ * @internal
3757
+ */
3758
+ _handleBroadcast(text) {
3759
+ if (this.config.onBroadcast) {
3760
+ this.config.onBroadcast(text);
3761
+ } else {
3762
+ this.showNotification(text);
3763
+ }
3764
+ }
3670
3765
  /**
3671
3766
  * Initialize standard feature modules (guide, heartbeat, interactions, UI)
3672
3767
  * @internal
@@ -3676,7 +3771,18 @@ var StaticTVClient = class {
3676
3771
  this.guide = new GuideModule(this);
3677
3772
  this.heartbeat = new HeartbeatModule(this);
3678
3773
  this.interactions = new InteractionsModule(this);
3679
- this.guideUI = new GuideUIModule(this, this.config.guideUI);
3774
+ const guideConfig = {
3775
+ ...this.config.guideUI,
3776
+ onVideoSelect: (video) => {
3777
+ if (this.config.videoScreen !== void 0 || this.config.onVideoPlay) {
3778
+ this._handleGuideVideoSelect(video);
3779
+ }
3780
+ if (this.config.guideUI?.onVideoSelect) {
3781
+ this.config.guideUI.onVideoSelect(video);
3782
+ }
3783
+ }
3784
+ };
3785
+ this.guideUI = new GuideUIModule(this, guideConfig);
3680
3786
  this.chatUI = new ChatUIModule(this, this.config.chatUI);
3681
3787
  this._standardFeaturesEnabled = true;
3682
3788
  this.chatUI.init().catch((err) => {
@@ -3689,21 +3795,28 @@ var StaticTVClient = class {
3689
3795
  * @internal
3690
3796
  */
3691
3797
  _initProModules() {
3692
- if (this._proFeaturesEnabled || !this._pendingProConfig) return;
3693
- const configWithDefaults = {
3694
- ...this._pendingProConfig,
3695
- sceneId: this._pendingProConfig.sceneId || this._keyId || void 0
3696
- };
3697
- if (!configWithDefaults.sceneId) {
3798
+ if (this._proFeaturesEnabled) return;
3799
+ const sceneId = this._pendingProConfig?.sceneId || this.config.sceneId || this._keyId || void 0;
3800
+ if (!sceneId) {
3698
3801
  this.log("Pro features: No sceneId and no keyId available - admin panel disabled");
3699
3802
  return;
3700
3803
  }
3701
- this.adminPanel = new AdminPanelUIModule(this, configWithDefaults);
3804
+ const adminConfig = {
3805
+ sceneId,
3806
+ // Use internal handlers that manage videoScreen + call user callbacks
3807
+ onVideoPlay: (url) => this.playVideo(url),
3808
+ onVideoStop: () => this.stopVideo(),
3809
+ onBroadcast: (text) => this._handleBroadcast(text),
3810
+ onCommand: this.config.onCommand || this._pendingProConfig?.onCommand,
3811
+ // Merge other settings from enableProFeatures if provided
3812
+ ...this._pendingProConfig
3813
+ };
3814
+ this.adminPanel = new AdminPanelUIModule(this, adminConfig);
3702
3815
  this._proFeaturesEnabled = true;
3703
3816
  this.adminPanel.init().catch((err) => {
3704
3817
  this.log(`Admin panel init failed: ${err}`);
3705
3818
  });
3706
- this.log(`Pro features enabled (admin panel) - sceneId: ${configWithDefaults.sceneId}`);
3819
+ this.log(`Pro features enabled (admin panel) - sceneId: ${sceneId}`);
3707
3820
  }
3708
3821
  /**
3709
3822
  * Called by SessionModule when server returns the tier
@@ -3718,8 +3831,13 @@ var StaticTVClient = class {
3718
3831
  if (tier === "standard" || tier === "pro") {
3719
3832
  this._initStandardModules();
3720
3833
  }
3721
- if (tier === "pro" && this._pendingProConfig) {
3722
- this._initProModules();
3834
+ if (tier === "pro") {
3835
+ const hasVideoConfig = this.config.videoScreen !== void 0 || this.config.onVideoPlay !== void 0 || this.config.sceneId !== void 0 || this._pendingProConfig !== null;
3836
+ if (hasVideoConfig) {
3837
+ this._initProModules();
3838
+ } else {
3839
+ this.log("Pro tier detected but no video config - call enableProFeatures() or set videoScreen to enable admin panel");
3840
+ }
3723
3841
  }
3724
3842
  }
3725
3843
  /**
@@ -3755,28 +3873,30 @@ var StaticTVClient = class {
3755
3873
  }
3756
3874
  /**
3757
3875
  * Configure Pro features (Admin Panel with Video + Mod tabs)
3758
- * Call this after creating the client to configure admin panel.
3759
- * The panel will auto-enable when server confirms Pro tier.
3760
3876
  *
3761
- * @param config Admin panel configuration (optional - defaults work for basic usage)
3877
+ * **OPTIONAL**: If you set `videoScreen` in the constructor, you don't need
3878
+ * to call this method - the Admin Panel will auto-enable with sensible defaults.
3762
3879
  *
3763
- * @example
3764
- * ```typescript
3765
- * const staticTV = new StaticTVClient({ apiKey: 'dcls_...' })
3880
+ * Use this method only if you need:
3881
+ * - Custom scene tab UI
3882
+ * - Custom panel title/styling
3883
+ * - Advanced command handlers
3766
3884
  *
3767
- * // Simplest usage - just enable with callbacks:
3768
- * staticTV.enableProFeatures({
3769
- * onVideoPlay: (url) => videoPlayer.play(url),
3770
- * onVideoStop: () => videoPlayer.stop()
3885
+ * @param config Admin panel configuration
3886
+ *
3887
+ * @example Using videoScreen (recommended - no enableProFeatures needed):
3888
+ * ```typescript
3889
+ * const staticTV = new StaticTVClient({
3890
+ * apiKey: 'dcls_...',
3891
+ * videoScreen: myScreen // Admin Panel auto-enabled for Pro tier!
3771
3892
  * })
3893
+ * ```
3772
3894
  *
3773
- * // Advanced usage - custom sceneId and title:
3895
+ * @example Advanced: Custom scene tabs
3896
+ * ```typescript
3774
3897
  * staticTV.enableProFeatures({
3775
- * sceneId: 'my-scene', // optional - defaults to API key ID
3776
3898
  * title: 'MY SCENE ADMIN',
3777
- * onVideoPlay: (url) => videoPlayer.play(url),
3778
- * onVideoStop: () => videoPlayer.stop(),
3779
- * onBroadcast: (text) => showNotification(text)
3899
+ * sceneTabs: [{ label: 'LIGHTS', id: 'lights', render: () => <LightsTab /> }]
3780
3900
  * })
3781
3901
  * ```
3782
3902
  */
package/dist/index.mjs CHANGED
@@ -1398,7 +1398,7 @@ var GuideUIModule = class {
1398
1398
  renderToggleButton() {
1399
1399
  const buttonText = this._isVisible ? "CLOSE" : "GUIDE";
1400
1400
  const buttonColor = this._isVisible ? Color43.create(0.2, 0.2, 0.28, 0.9) : Color43.create(0, 0.5, 0.5, 0.9);
1401
- const buttonPos = 20 + this.s(100) + 10 + this.s(100) + 10;
1401
+ const buttonPos = 20 + this.s(100) + 10;
1402
1402
  return ReactEcs2.createElement(UiEntity2, {
1403
1403
  uiTransform: {
1404
1404
  positionType: "absolute",
@@ -2691,7 +2691,7 @@ var AdminPanelUIModule = class {
2691
2691
  UiEntity4,
2692
2692
  {
2693
2693
  uiTransform: {
2694
- position: { right: 20 + this.s(100) + 10, bottom: 10 },
2694
+ position: { right: 20 + this.s(100) + 10 + this.s(100) + 10, bottom: 10 },
2695
2695
  positionType: "absolute"
2696
2696
  }
2697
2697
  },
@@ -3449,6 +3449,7 @@ function setupStaticUI(client) {
3449
3449
  }
3450
3450
 
3451
3451
  // src/StaticTVClient.ts
3452
+ import { VideoPlayer, Material } from "@dcl/sdk/ecs";
3452
3453
  var DEFAULT_BASE_URL = "https://thestatic.tv/api/v1/dcl";
3453
3454
  var KEY_TYPE_CHANNEL = "channel";
3454
3455
  var KEY_TYPE_SCENE = "scene";
@@ -3458,16 +3459,28 @@ var StaticTVClient = class {
3458
3459
  *
3459
3460
  * @param config Configuration options
3460
3461
  *
3461
- * @example
3462
+ * @example Free tier (session tracking only):
3463
+ * ```typescript
3464
+ * const staticTV = new StaticTVClient({
3465
+ * apiKey: 'dcls_your_key_here'
3466
+ * })
3467
+ * ```
3468
+ *
3469
+ * @example Standard/Pro with video screen (SDK handles playback):
3462
3470
  * ```typescript
3463
- * let staticTV: StaticTVClient
3471
+ * // Create your video screen
3472
+ * const screen = engine.addEntity()
3473
+ * Transform.create(screen, { position: Vector3.create(8, 3, 14), scale: Vector3.create(8, 4.5, 0.1) })
3474
+ * MeshRenderer.setPlane(screen)
3475
+ *
3476
+ * // SDK handles everything - Guide selection + Pro admin controls just work
3477
+ * const staticTV = new StaticTVClient({
3478
+ * apiKey: 'dcls_your_key_here',
3479
+ * videoScreen: screen // That's it! SDK manages video playback
3480
+ * })
3464
3481
  *
3465
3482
  * export function main() {
3466
- * // All keys use dcls_ prefix - features determined by subscription
3467
- * staticTV = new StaticTVClient({
3468
- * apiKey: 'dcls_your_key_here'
3469
- * })
3470
- * // Session tracking starts automatically!
3483
+ * staticTV.setupUI()
3471
3484
  * }
3472
3485
  * ```
3473
3486
  */
@@ -3495,6 +3508,10 @@ var StaticTVClient = class {
3495
3508
  this.adminPanel = null;
3496
3509
  /** UI scale - fixed at 1.0. DCL's UI system auto-scales based on viewport. */
3497
3510
  this.uiScale = 1;
3511
+ // =============================================================================
3512
+ // --- VIDEO PLAYBACK (Internal handler for videoScreen) ---
3513
+ // =============================================================================
3514
+ this._currentVideoUrl = "";
3498
3515
  this.config = {
3499
3516
  autoStartSession: true,
3500
3517
  sessionHeartbeatInterval: 3e4,
@@ -3624,6 +3641,84 @@ var StaticTVClient = class {
3624
3641
  getConfig() {
3625
3642
  return this.config;
3626
3643
  }
3644
+ /**
3645
+ * Play a video on the configured videoScreen entity.
3646
+ * Called by Guide UI and Admin Panel.
3647
+ *
3648
+ * @param url Video URL to play
3649
+ */
3650
+ playVideo(url) {
3651
+ const screen = this.config.videoScreen;
3652
+ if (screen !== void 0) {
3653
+ this.log(`Playing video: ${url}`);
3654
+ this._currentVideoUrl = url;
3655
+ if (VideoPlayer.has(screen)) {
3656
+ VideoPlayer.deleteFrom(screen);
3657
+ }
3658
+ VideoPlayer.create(screen, {
3659
+ src: url,
3660
+ playing: true,
3661
+ volume: 1
3662
+ });
3663
+ Material.setBasicMaterial(screen, {
3664
+ texture: Material.Texture.Video({ videoPlayerEntity: screen })
3665
+ });
3666
+ if (this.guideUI) {
3667
+ const videos = this.guideUI.getVideos();
3668
+ const video = videos.find((v) => v.src === url);
3669
+ if (video) {
3670
+ this.guideUI.currentVideoId = video.id;
3671
+ }
3672
+ }
3673
+ }
3674
+ if (this.config.onVideoPlay) {
3675
+ this.config.onVideoPlay(url);
3676
+ }
3677
+ }
3678
+ /**
3679
+ * Stop video playback on the configured videoScreen entity.
3680
+ * Called by Admin Panel.
3681
+ */
3682
+ stopVideo() {
3683
+ const screen = this.config.videoScreen;
3684
+ if (screen !== void 0 && VideoPlayer.has(screen)) {
3685
+ this.log("Stopping video");
3686
+ VideoPlayer.getMutable(screen).playing = false;
3687
+ this._currentVideoUrl = "";
3688
+ if (this.guideUI) {
3689
+ this.guideUI.currentVideoId = null;
3690
+ }
3691
+ }
3692
+ if (this.config.onVideoStop) {
3693
+ this.config.onVideoStop();
3694
+ }
3695
+ }
3696
+ /**
3697
+ * Get the currently playing video URL
3698
+ */
3699
+ get currentVideoUrl() {
3700
+ return this._currentVideoUrl;
3701
+ }
3702
+ /**
3703
+ * Internal handler for Guide video selection
3704
+ * @internal
3705
+ */
3706
+ _handleGuideVideoSelect(video) {
3707
+ if (video.src) {
3708
+ this.playVideo(video.src);
3709
+ }
3710
+ }
3711
+ /**
3712
+ * Internal handler for broadcast messages
3713
+ * @internal
3714
+ */
3715
+ _handleBroadcast(text) {
3716
+ if (this.config.onBroadcast) {
3717
+ this.config.onBroadcast(text);
3718
+ } else {
3719
+ this.showNotification(text);
3720
+ }
3721
+ }
3627
3722
  /**
3628
3723
  * Initialize standard feature modules (guide, heartbeat, interactions, UI)
3629
3724
  * @internal
@@ -3633,7 +3728,18 @@ var StaticTVClient = class {
3633
3728
  this.guide = new GuideModule(this);
3634
3729
  this.heartbeat = new HeartbeatModule(this);
3635
3730
  this.interactions = new InteractionsModule(this);
3636
- this.guideUI = new GuideUIModule(this, this.config.guideUI);
3731
+ const guideConfig = {
3732
+ ...this.config.guideUI,
3733
+ onVideoSelect: (video) => {
3734
+ if (this.config.videoScreen !== void 0 || this.config.onVideoPlay) {
3735
+ this._handleGuideVideoSelect(video);
3736
+ }
3737
+ if (this.config.guideUI?.onVideoSelect) {
3738
+ this.config.guideUI.onVideoSelect(video);
3739
+ }
3740
+ }
3741
+ };
3742
+ this.guideUI = new GuideUIModule(this, guideConfig);
3637
3743
  this.chatUI = new ChatUIModule(this, this.config.chatUI);
3638
3744
  this._standardFeaturesEnabled = true;
3639
3745
  this.chatUI.init().catch((err) => {
@@ -3646,21 +3752,28 @@ var StaticTVClient = class {
3646
3752
  * @internal
3647
3753
  */
3648
3754
  _initProModules() {
3649
- if (this._proFeaturesEnabled || !this._pendingProConfig) return;
3650
- const configWithDefaults = {
3651
- ...this._pendingProConfig,
3652
- sceneId: this._pendingProConfig.sceneId || this._keyId || void 0
3653
- };
3654
- if (!configWithDefaults.sceneId) {
3755
+ if (this._proFeaturesEnabled) return;
3756
+ const sceneId = this._pendingProConfig?.sceneId || this.config.sceneId || this._keyId || void 0;
3757
+ if (!sceneId) {
3655
3758
  this.log("Pro features: No sceneId and no keyId available - admin panel disabled");
3656
3759
  return;
3657
3760
  }
3658
- this.adminPanel = new AdminPanelUIModule(this, configWithDefaults);
3761
+ const adminConfig = {
3762
+ sceneId,
3763
+ // Use internal handlers that manage videoScreen + call user callbacks
3764
+ onVideoPlay: (url) => this.playVideo(url),
3765
+ onVideoStop: () => this.stopVideo(),
3766
+ onBroadcast: (text) => this._handleBroadcast(text),
3767
+ onCommand: this.config.onCommand || this._pendingProConfig?.onCommand,
3768
+ // Merge other settings from enableProFeatures if provided
3769
+ ...this._pendingProConfig
3770
+ };
3771
+ this.adminPanel = new AdminPanelUIModule(this, adminConfig);
3659
3772
  this._proFeaturesEnabled = true;
3660
3773
  this.adminPanel.init().catch((err) => {
3661
3774
  this.log(`Admin panel init failed: ${err}`);
3662
3775
  });
3663
- this.log(`Pro features enabled (admin panel) - sceneId: ${configWithDefaults.sceneId}`);
3776
+ this.log(`Pro features enabled (admin panel) - sceneId: ${sceneId}`);
3664
3777
  }
3665
3778
  /**
3666
3779
  * Called by SessionModule when server returns the tier
@@ -3675,8 +3788,13 @@ var StaticTVClient = class {
3675
3788
  if (tier === "standard" || tier === "pro") {
3676
3789
  this._initStandardModules();
3677
3790
  }
3678
- if (tier === "pro" && this._pendingProConfig) {
3679
- this._initProModules();
3791
+ if (tier === "pro") {
3792
+ const hasVideoConfig = this.config.videoScreen !== void 0 || this.config.onVideoPlay !== void 0 || this.config.sceneId !== void 0 || this._pendingProConfig !== null;
3793
+ if (hasVideoConfig) {
3794
+ this._initProModules();
3795
+ } else {
3796
+ this.log("Pro tier detected but no video config - call enableProFeatures() or set videoScreen to enable admin panel");
3797
+ }
3680
3798
  }
3681
3799
  }
3682
3800
  /**
@@ -3712,28 +3830,30 @@ var StaticTVClient = class {
3712
3830
  }
3713
3831
  /**
3714
3832
  * Configure Pro features (Admin Panel with Video + Mod tabs)
3715
- * Call this after creating the client to configure admin panel.
3716
- * The panel will auto-enable when server confirms Pro tier.
3717
3833
  *
3718
- * @param config Admin panel configuration (optional - defaults work for basic usage)
3834
+ * **OPTIONAL**: If you set `videoScreen` in the constructor, you don't need
3835
+ * to call this method - the Admin Panel will auto-enable with sensible defaults.
3719
3836
  *
3720
- * @example
3721
- * ```typescript
3722
- * const staticTV = new StaticTVClient({ apiKey: 'dcls_...' })
3837
+ * Use this method only if you need:
3838
+ * - Custom scene tab UI
3839
+ * - Custom panel title/styling
3840
+ * - Advanced command handlers
3723
3841
  *
3724
- * // Simplest usage - just enable with callbacks:
3725
- * staticTV.enableProFeatures({
3726
- * onVideoPlay: (url) => videoPlayer.play(url),
3727
- * onVideoStop: () => videoPlayer.stop()
3842
+ * @param config Admin panel configuration
3843
+ *
3844
+ * @example Using videoScreen (recommended - no enableProFeatures needed):
3845
+ * ```typescript
3846
+ * const staticTV = new StaticTVClient({
3847
+ * apiKey: 'dcls_...',
3848
+ * videoScreen: myScreen // Admin Panel auto-enabled for Pro tier!
3728
3849
  * })
3850
+ * ```
3729
3851
  *
3730
- * // Advanced usage - custom sceneId and title:
3852
+ * @example Advanced: Custom scene tabs
3853
+ * ```typescript
3731
3854
  * staticTV.enableProFeatures({
3732
- * sceneId: 'my-scene', // optional - defaults to API key ID
3733
3855
  * title: 'MY SCENE ADMIN',
3734
- * onVideoPlay: (url) => videoPlayer.play(url),
3735
- * onVideoStop: () => videoPlayer.stop(),
3736
- * onBroadcast: (text) => showNotification(text)
3856
+ * sceneTabs: [{ label: 'LIGHTS', id: 'lights', render: () => <LightsTab /> }]
3737
3857
  * })
3738
3858
  * ```
3739
3859
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thestatic-tv/dcl-sdk",
3
- "version": "2.3.0-beta.0",
3
+ "version": "2.3.0-beta.2",
4
4
  "description": "Connect your Decentraland scene to thestatic.tv - full channel lineup, metrics tracking, and interactions",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",