saltfish 0.3.30 → 0.3.33

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.
@@ -2732,7 +2732,7 @@ class PlaylistOrchestrator {
2732
2732
  { component: "PlaylistOrchestrator", method: "startPlaylist", playlistId }
2733
2733
  );
2734
2734
  }
2735
- const isPlaylistRunning = store.manifest && (store.currentState === "playing" || store.currentState === "paused" || store.currentState === "loading" || store.currentState === "waitingForInteraction" || store.currentState === "autoplayBlocked" || store.currentState === "minimized");
2735
+ const isPlaylistRunning = store.manifest && (store.currentState === "playing" || store.currentState === "paused" || store.currentState === "loading" || store.currentState === "waitingForInteraction" || store.currentState === "autoplayBlocked" || store.currentState === "minimized" || store.currentState === "idleMode");
2736
2736
  if (isPlaylistRunning) {
2737
2737
  log("PlaylistOrchestrator: Starting new playlist while another is running, resetting state");
2738
2738
  if (this.managerOrchestrator) {
@@ -2912,6 +2912,9 @@ class StateMachineActionHandler {
2912
2912
  constructor(managers) {
2913
2913
  __publicField(this, "managers");
2914
2914
  __publicField(this, "destroyCallback", null);
2915
+ __publicField(this, "cursorAnimationListener", null);
2916
+ __publicField(this, "cursorAnimationVideoElement", null);
2917
+ __publicField(this, "cursorAnimationStepId", null);
2915
2918
  this.managers = managers;
2916
2919
  }
2917
2920
  /**
@@ -3044,6 +3047,87 @@ class StateMachineActionHandler {
3044
3047
  return null;
3045
3048
  }
3046
3049
  destroy() {
3050
+ this.cleanupCursorAnimationListener();
3051
+ }
3052
+ /**
3053
+ * Cleans up the active cursor animation time listener
3054
+ * @param stepId - Optional step ID to verify we're cleaning up the right listener
3055
+ */
3056
+ cleanupCursorAnimationListener(stepId) {
3057
+ if (stepId && this.cursorAnimationStepId && stepId !== this.cursorAnimationStepId) {
3058
+ log(`StateMachineActionHandler: Skipping cleanup - stepId mismatch (requested: ${stepId}, current: ${this.cursorAnimationStepId})`);
3059
+ return;
3060
+ }
3061
+ if (this.cursorAnimationListener && this.cursorAnimationVideoElement) {
3062
+ this.cursorAnimationVideoElement.removeEventListener("timeupdate", this.cursorAnimationListener);
3063
+ this.cursorAnimationListener = null;
3064
+ this.cursorAnimationVideoElement = null;
3065
+ this.cursorAnimationStepId = null;
3066
+ }
3067
+ }
3068
+ /**
3069
+ * Schedules a cursor animation to run either immediately or at a specific video time
3070
+ * Note: Works for both video and audio-only steps (audio files use the same video element)
3071
+ */
3072
+ scheduleCursorAnimation(animation, stepId) {
3073
+ this.cleanupCursorAnimationListener();
3074
+ let showAtSeconds = animation.showAtSeconds ?? 0;
3075
+ if (typeof showAtSeconds !== "number" || !isFinite(showAtSeconds)) {
3076
+ showAtSeconds = 0;
3077
+ }
3078
+ if (showAtSeconds < 0) {
3079
+ showAtSeconds = 0;
3080
+ }
3081
+ if (showAtSeconds <= 0) {
3082
+ this.managers.cursorManager.animate(animation);
3083
+ } else {
3084
+ const videoElement = this.managers.videoManager.getVideoElement();
3085
+ if (!videoElement) {
3086
+ return;
3087
+ }
3088
+ let animationTriggered = false;
3089
+ let warningLogged = false;
3090
+ const timeUpdateHandler = () => {
3091
+ if (animationTriggered) {
3092
+ return;
3093
+ }
3094
+ const currentTime = videoElement.currentTime;
3095
+ const duration = videoElement.duration;
3096
+ if (duration && !isNaN(duration) && showAtSeconds > duration && !warningLogged) {
3097
+ warningLogged = true;
3098
+ animationTriggered = true;
3099
+ this.cleanupCursorAnimationListener();
3100
+ this.managers.cursorManager.animate(animation);
3101
+ return;
3102
+ }
3103
+ if (currentTime >= showAtSeconds) {
3104
+ animationTriggered = true;
3105
+ this.cleanupCursorAnimationListener();
3106
+ this.managers.cursorManager.animate(animation);
3107
+ }
3108
+ };
3109
+ const endedHandler = () => {
3110
+ if (!animationTriggered) {
3111
+ const store = getSaltfishStore();
3112
+ if (store.currentStepId !== stepId) {
3113
+ log(`StateMachineActionHandler: Video ended but step changed (was ${stepId}, now ${store.currentStepId}). Not triggering cursor animation.`);
3114
+ this.cleanupCursorAnimationListener(stepId);
3115
+ return;
3116
+ }
3117
+ videoElement.duration;
3118
+ animationTriggered = true;
3119
+ this.managers.cursorManager.animate(animation);
3120
+ this.cleanupCursorAnimationListener();
3121
+ }
3122
+ };
3123
+ this.cursorAnimationListener = () => {
3124
+ timeUpdateHandler();
3125
+ };
3126
+ this.cursorAnimationVideoElement = videoElement;
3127
+ this.cursorAnimationStepId = stepId;
3128
+ videoElement.addEventListener("timeupdate", this.cursorAnimationListener);
3129
+ videoElement.addEventListener("ended", endedHandler, { once: true });
3130
+ }
3047
3131
  }
3048
3132
  /**
3049
3133
  * Validates URL requirement for a specific step with retry logic
@@ -3134,17 +3218,7 @@ class StateMachineActionHandler {
3134
3218
  if (currentStep.buttons) {
3135
3219
  this.managers.interactionManager.createButtons(currentStep.buttons);
3136
3220
  }
3137
- log(`StateMachineActionHandler: Processing cursor animations for step ${currentStep.id}`);
3138
3221
  log(`StateMachineActionHandler: Step has cursor animations: ${!!(currentStep.cursorAnimations && currentStep.cursorAnimations.length > 0)}`);
3139
- if (currentStep.cursorAnimations && currentStep.cursorAnimations.length > 0) {
3140
- log(`StateMachineActionHandler: Setting cursor visibility to true for step ${currentStep.id}`);
3141
- this.managers.cursorManager.setShouldShowCursor(true);
3142
- log(`StateMachineActionHandler: Starting cursor animation for step ${currentStep.id} with target: ${currentStep.cursorAnimations[0].targetSelector || "no target"}`);
3143
- this.managers.cursorManager.animate(currentStep.cursorAnimations[0]);
3144
- } else {
3145
- log(`StateMachineActionHandler: Setting cursor visibility to false for step ${currentStep.id} - step has no cursor animations`);
3146
- this.managers.cursorManager.setShouldShowCursor(false);
3147
- }
3148
3222
  const hasSpecialTransitions = currentStep.buttons && currentStep.buttons.length > 0 || currentStep.transitions.some(
3149
3223
  (t) => t.type === "dom-click" || t.type === "url-path" || t.type === "dom-element-visible"
3150
3224
  );
@@ -3233,6 +3307,14 @@ class StateMachineActionHandler {
3233
3307
  log("StateMachineActionHandler: Video loaded successfully, playing");
3234
3308
  this.managers.uiManager.hideError();
3235
3309
  loadTranscriptForStep();
3310
+ if (currentStep.cursorAnimations && currentStep.cursorAnimations.length > 0) {
3311
+ log(`StateMachineActionHandler: Setting cursor visibility and scheduling animation for step ${currentStep.id}`);
3312
+ this.managers.cursorManager.setShouldShowCursor(true);
3313
+ this.scheduleCursorAnimation(currentStep.cursorAnimations[0], currentStep.id);
3314
+ } else {
3315
+ log(`StateMachineActionHandler: Setting cursor visibility to false for step ${currentStep.id} - step has no cursor animations`);
3316
+ this.managers.cursorManager.setShouldShowCursor(false);
3317
+ }
3236
3318
  if (isAudioFallback) {
3237
3319
  this.managers.videoManager.startAudioVisualization();
3238
3320
  } else {
@@ -3655,6 +3737,9 @@ const _ManagerOrchestrator = class _ManagerOrchestrator {
3655
3737
  // Store updater unsubscribe functions
3656
3738
  __publicField(this, "uiUpdaterUnsubscribe", null);
3657
3739
  __publicField(this, "eventUpdaterUnsubscribe", null);
3740
+ __publicField(this, "cursorAnimationListener", null);
3741
+ __publicField(this, "cursorAnimationVideoElement", null);
3742
+ __publicField(this, "cursorAnimationStepId", null);
3658
3743
  // Initialization state
3659
3744
  __publicField(this, "isInitialized", false);
3660
3745
  this.managers = managers;
@@ -3699,6 +3784,86 @@ const _ManagerOrchestrator = class _ManagerOrchestrator {
3699
3784
  stepTimeoutUnsubscribe();
3700
3785
  };
3701
3786
  }
3787
+ /**
3788
+ * Cleans up the active cursor animation time listener
3789
+ * @param stepId - Optional step ID to verify we're cleaning up the right listener
3790
+ */
3791
+ cleanupCursorAnimationListener(stepId) {
3792
+ if (stepId && this.cursorAnimationStepId && stepId !== this.cursorAnimationStepId) {
3793
+ log(`ManagerOrchestrator: Skipping cleanup - stepId mismatch (requested: ${stepId}, current: ${this.cursorAnimationStepId})`);
3794
+ return;
3795
+ }
3796
+ if (this.cursorAnimationListener && this.cursorAnimationVideoElement) {
3797
+ this.cursorAnimationVideoElement.removeEventListener("timeupdate", this.cursorAnimationListener);
3798
+ this.cursorAnimationListener = null;
3799
+ this.cursorAnimationVideoElement = null;
3800
+ this.cursorAnimationStepId = null;
3801
+ }
3802
+ }
3803
+ /**
3804
+ * Schedules a cursor animation to run either immediately or at a specific video time
3805
+ * Note: Works for both video and audio-only steps (audio files use the same video element)
3806
+ */
3807
+ scheduleCursorAnimation(animation, stepId) {
3808
+ this.cleanupCursorAnimationListener();
3809
+ let showAtSeconds = animation.showAtSeconds ?? 0;
3810
+ if (typeof showAtSeconds !== "number" || !isFinite(showAtSeconds)) {
3811
+ showAtSeconds = 0;
3812
+ }
3813
+ if (showAtSeconds < 0) {
3814
+ showAtSeconds = 0;
3815
+ }
3816
+ if (showAtSeconds <= 0) {
3817
+ this.managers.cursorManager.animate(animation);
3818
+ } else {
3819
+ const videoElement = this.managers.videoManager.getVideoElement();
3820
+ if (!videoElement) {
3821
+ return;
3822
+ }
3823
+ let animationTriggered = false;
3824
+ let warningLogged = false;
3825
+ const timeUpdateHandler = () => {
3826
+ if (animationTriggered) {
3827
+ return;
3828
+ }
3829
+ const currentTime = videoElement.currentTime;
3830
+ const duration = videoElement.duration;
3831
+ if (duration && !isNaN(duration) && showAtSeconds > duration && !warningLogged) {
3832
+ warningLogged = true;
3833
+ animationTriggered = true;
3834
+ this.cleanupCursorAnimationListener();
3835
+ this.managers.cursorManager.animate(animation);
3836
+ return;
3837
+ }
3838
+ if (currentTime >= showAtSeconds) {
3839
+ animationTriggered = true;
3840
+ this.cleanupCursorAnimationListener();
3841
+ this.managers.cursorManager.animate(animation);
3842
+ }
3843
+ };
3844
+ const endedHandler = () => {
3845
+ if (!animationTriggered) {
3846
+ const store = getSaltfishStore();
3847
+ if (store.currentStepId !== stepId) {
3848
+ log(`ManagerOrchestrator: Video ended but step changed (was ${stepId}, now ${store.currentStepId}). Not triggering cursor animation.`);
3849
+ this.cleanupCursorAnimationListener(stepId);
3850
+ return;
3851
+ }
3852
+ videoElement.duration;
3853
+ animationTriggered = true;
3854
+ this.managers.cursorManager.animate(animation);
3855
+ this.cleanupCursorAnimationListener();
3856
+ }
3857
+ };
3858
+ this.cursorAnimationListener = () => {
3859
+ timeUpdateHandler();
3860
+ };
3861
+ this.cursorAnimationVideoElement = videoElement;
3862
+ this.cursorAnimationStepId = stepId;
3863
+ videoElement.addEventListener("timeupdate", this.cursorAnimationListener);
3864
+ videoElement.addEventListener("ended", endedHandler, { once: true });
3865
+ }
3866
+ }
3702
3867
  /**
3703
3868
  * Handle store state changes
3704
3869
  */
@@ -3730,7 +3895,7 @@ const _ManagerOrchestrator = class _ManagerOrchestrator {
3730
3895
  const currentStep = manifest == null ? void 0 : manifest.steps.find((step) => step.id === store.currentStepId);
3731
3896
  if ((currentStep == null ? void 0 : currentStep.cursorAnimations) && currentStep.cursorAnimations.length > 0) {
3732
3897
  this.managers.cursorManager.setShouldShowCursor(true);
3733
- this.managers.cursorManager.animate(currentStep.cursorAnimations[0]);
3898
+ this.scheduleCursorAnimation(currentStep.cursorAnimations[0], store.currentStepId || "unknown");
3734
3899
  }
3735
3900
  } else if (store.currentState === "completed" || store.currentState === "closing") {
3736
3901
  this.cleanupPlaylist();
@@ -3749,6 +3914,7 @@ const _ManagerOrchestrator = class _ManagerOrchestrator {
3749
3914
  */
3750
3915
  cleanupCurrentPlaylist() {
3751
3916
  try {
3917
+ this.cleanupCursorAnimationListener();
3752
3918
  if (this.eventUpdaterUnsubscribe) {
3753
3919
  this.eventUpdaterUnsubscribe();
3754
3920
  this.eventUpdaterUnsubscribe = null;
@@ -3840,6 +4006,7 @@ const _ManagerOrchestrator = class _ManagerOrchestrator {
3840
4006
  isMinimized: store.isMinimized,
3841
4007
  manifestId: (_a = store.manifest) == null ? void 0 : _a.id
3842
4008
  });
4009
+ this.cleanupCursorAnimationListener();
3843
4010
  if (this.uiUpdaterUnsubscribe) {
3844
4011
  this.uiUpdaterUnsubscribe();
3845
4012
  this.uiUpdaterUnsubscribe = null;
@@ -8206,21 +8373,13 @@ class InteractionManager {
8206
8373
  this.container = null;
8207
8374
  }
8208
8375
  }
8209
- class AnalyticsManager {
8210
- // Default to enabled
8376
+ class EventSubscriberManager {
8211
8377
  /**
8212
- * Creates a new AnalyticsManager
8378
+ * Creates a new event-subscribing manager instance
8213
8379
  * @param eventManager - Optional event manager to subscribe to events
8214
8380
  */
8215
8381
  constructor(eventManager) {
8216
- __publicField(this, "config", null);
8217
- __publicField(this, "user", null);
8218
- __publicField(this, "eventQueue", []);
8219
- __publicField(this, "isSending", false);
8220
- __publicField(this, "flushInterval", null);
8221
8382
  __publicField(this, "eventManager", null);
8222
- __publicField(this, "sessionId", null);
8223
- __publicField(this, "analyticsEnabled", true);
8224
8383
  if (eventManager) {
8225
8384
  this.setEventManager(eventManager);
8226
8385
  }
@@ -8233,6 +8392,23 @@ class AnalyticsManager {
8233
8392
  this.eventManager = eventManager;
8234
8393
  this.subscribeToEvents();
8235
8394
  }
8395
+ }
8396
+ class AnalyticsManager extends EventSubscriberManager {
8397
+ // Default to enabled
8398
+ /**
8399
+ * Creates a new AnalyticsManager
8400
+ * @param eventManager - Optional event manager to subscribe to events
8401
+ */
8402
+ constructor(eventManager) {
8403
+ super(eventManager);
8404
+ __publicField(this, "config", null);
8405
+ __publicField(this, "user", null);
8406
+ __publicField(this, "eventQueue", []);
8407
+ __publicField(this, "isSending", false);
8408
+ __publicField(this, "flushInterval", null);
8409
+ __publicField(this, "sessionId", null);
8410
+ __publicField(this, "analyticsEnabled", true);
8411
+ }
8236
8412
  /**
8237
8413
  * Subscribe to relevant player events for analytics tracking
8238
8414
  */
@@ -9792,24 +9968,13 @@ class TriggerManager {
9792
9968
  this.visibleElements.clear();
9793
9969
  }
9794
9970
  }
9795
- class ABTestManager {
9971
+ class ABTestManager extends EventSubscriberManager {
9796
9972
  /**
9797
9973
  * Creates a new ABTestManager
9798
9974
  * @param eventManager - Optional event manager to subscribe to events
9799
9975
  */
9800
9976
  constructor(eventManager) {
9801
- __publicField(this, "eventManager", null);
9802
- if (eventManager) {
9803
- this.setEventManager(eventManager);
9804
- }
9805
- }
9806
- /**
9807
- * Sets the event manager and subscribes to relevant events
9808
- * @param eventManager - Event manager instance
9809
- */
9810
- setEventManager(eventManager) {
9811
- this.eventManager = eventManager;
9812
- this.subscribeToEvents();
9977
+ super(eventManager);
9813
9978
  }
9814
9979
  /**
9815
9980
  * Subscribe to relevant events for A/B testing
@@ -10189,30 +10354,19 @@ class PlaylistLoader {
10189
10354
  return startStepId;
10190
10355
  }
10191
10356
  }
10192
- class PlaylistManager {
10357
+ class PlaylistManager extends EventSubscriberManager {
10193
10358
  /**
10194
10359
  * Creates a new PlaylistManager
10195
10360
  * @param eventManager - Optional event manager to subscribe to events
10196
10361
  * @param storageManager - Optional storage manager for localStorage operations
10197
10362
  */
10198
10363
  constructor(eventManager, storageManager2) {
10199
- __publicField(this, "eventManager", null);
10364
+ super(eventManager);
10200
10365
  __publicField(this, "isUpdatingWatchedPlaylists", false);
10201
10366
  __publicField(this, "playlistLoader");
10202
10367
  __publicField(this, "storageManager");
10203
10368
  this.playlistLoader = new PlaylistLoader();
10204
10369
  this.storageManager = storageManager2 || StorageManager.getInstance();
10205
- if (eventManager) {
10206
- this.setEventManager(eventManager);
10207
- }
10208
- }
10209
- /**
10210
- * Sets the event manager and subscribes to relevant events
10211
- * @param eventManager - Event manager instance
10212
- */
10213
- setEventManager(eventManager) {
10214
- this.eventManager = eventManager;
10215
- this.subscribeToEvents();
10216
10370
  }
10217
10371
  /**
10218
10372
  * Subscribe to relevant player events for playlist tracking
@@ -11597,7 +11751,7 @@ const SaltfishPlayer$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.de
11597
11751
  __proto__: null,
11598
11752
  SaltfishPlayer
11599
11753
  }, Symbol.toStringTag, { value: "Module" }));
11600
- const version = "0.3.30";
11754
+ const version = "0.3.33";
11601
11755
  const packageJson = {
11602
11756
  version
11603
11757
  };