saltfish 0.3.71 → 0.3.73

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.
@@ -2983,7 +2983,6 @@ class PlaylistOrchestrator {
2983
2983
  });
2984
2984
  }
2985
2985
  }
2986
- this.managers.analyticsManager.trackPlaylistStart(playlistId);
2987
2986
  this.managers.cursorManager.resetFirstAnimation();
2988
2987
  log(`[PlaylistOrchestrator.startPlaylist] Using validated manifest path: ${manifestPathToLoad}`);
2989
2988
  await this.managers.playlistManager.load(manifestPathToLoad, { ...finalOptions, persistence: playlistPersistence });
@@ -3750,6 +3749,39 @@ function setupEventUpdater(eventManager) {
3750
3749
  let prevPreviousState = null;
3751
3750
  let prevIsMinimized = null;
3752
3751
  let prevStepId = null;
3752
+ let pendingStartEvents = { playlistData: null, stepData: null };
3753
+ let autoplayConfirmationTimer = null;
3754
+ const firePendingEvents = () => {
3755
+ if (autoplayConfirmationTimer) {
3756
+ clearTimeout(autoplayConfirmationTimer);
3757
+ autoplayConfirmationTimer = null;
3758
+ }
3759
+ if (pendingStartEvents.playlistData) {
3760
+ eventManager.trigger("playlistStarted", {
3761
+ timestamp: Date.now(),
3762
+ playlist: pendingStartEvents.playlistData
3763
+ });
3764
+ }
3765
+ if (pendingStartEvents.stepData) {
3766
+ eventManager.trigger("stepStarted", {
3767
+ timestamp: Date.now(),
3768
+ step: { id: pendingStartEvents.stepData.id, title: pendingStartEvents.stepData.title },
3769
+ playlist: { id: pendingStartEvents.stepData.playlistId, title: pendingStartEvents.stepData.playlistTitle }
3770
+ });
3771
+ }
3772
+ pendingStartEvents = { playlistData: null, stepData: null };
3773
+ };
3774
+ const startAutoplayConfirmationTimer = () => {
3775
+ if (autoplayConfirmationTimer) {
3776
+ clearTimeout(autoplayConfirmationTimer);
3777
+ }
3778
+ autoplayConfirmationTimer = setTimeout(() => {
3779
+ if (pendingStartEvents.playlistData || pendingStartEvents.stepData) {
3780
+ firePendingEvents();
3781
+ }
3782
+ autoplayConfirmationTimer = null;
3783
+ }, 500);
3784
+ };
3753
3785
  const unsubscribe = useSaltfishStore.subscribe(
3754
3786
  (state) => {
3755
3787
  var _a;
@@ -3774,30 +3806,55 @@ function setupEventUpdater(eventManager) {
3774
3806
  log(`EventUpdater: Processing state change from '${eventData.previousState}' to '${eventData.currentState}'`, {
3775
3807
  manifestId: (_a = state.manifest) == null ? void 0 : _a.id
3776
3808
  });
3777
- handleStateTransitionEvents(eventData, state, eventManager);
3809
+ if (eventData.previousState === "playing") {
3810
+ if (eventData.currentState === "autoplayBlocked" || eventData.currentState === "idleMode") {
3811
+ if (autoplayConfirmationTimer) {
3812
+ clearTimeout(autoplayConfirmationTimer);
3813
+ autoplayConfirmationTimer = null;
3814
+ }
3815
+ if (pendingStartEvents.playlistData || pendingStartEvents.stepData) {
3816
+ pendingStartEvents = { playlistData: null, stepData: null };
3817
+ }
3818
+ } else if (pendingStartEvents.playlistData || pendingStartEvents.stepData) {
3819
+ firePendingEvents();
3820
+ }
3821
+ }
3822
+ handleStateTransitionEvents(eventData, state, eventManager, pendingStartEvents);
3778
3823
  handleMinimizeEvents(eventData, state, eventManager, actualPrevMinimized);
3779
- handleStepEvents(eventData, state, eventManager, actualPrevStepId);
3824
+ handleStepEvents(eventData, state, eventManager, actualPrevStepId, pendingStartEvents);
3780
3825
  handleErrorEvents(eventData, state, eventManager);
3826
+ if ((pendingStartEvents.playlistData || pendingStartEvents.stepData) && !autoplayConfirmationTimer) {
3827
+ startAutoplayConfirmationTimer();
3828
+ }
3781
3829
  }
3782
3830
  );
3783
3831
  return unsubscribe;
3784
3832
  }
3785
- function handleStateTransitionEvents(eventData, store, eventManager) {
3833
+ function handleStateTransitionEvents(eventData, store, eventManager, pendingStartEvents) {
3786
3834
  const { prevPreviousState, previousState, currentState, currentStepId } = eventData;
3787
3835
  const isNormalPlaylistStart = currentState === "playing" && previousState === "paused" && prevPreviousState === "loading";
3788
- const isDelayedPlaylistStart = currentState === "playing" && (previousState === "autoplayBlocked" || previousState === "idleMode");
3789
- if ((isNormalPlaylistStart || isDelayedPlaylistStart) && store.manifest && currentStepId) {
3836
+ const isUserInitiatedPlaylistStart = currentState === "playing" && (previousState === "autoplayBlocked" || previousState === "idleMode");
3837
+ if (store.manifest && currentStepId) {
3790
3838
  const isStartingNode = currentStepId === store.manifest.startStep;
3791
3839
  if (isStartingNode) {
3792
- log(`EventUpdater: Triggering playlistStarted event for ${store.manifest.id} (starting node: ${currentStepId})`);
3793
- eventManager.trigger("playlistStarted", {
3794
- timestamp: Date.now(),
3795
- playlist: {
3840
+ if (isUserInitiatedPlaylistStart) {
3841
+ log(`EventUpdater: Triggering playlistStarted event for ${store.manifest.id} (user-initiated from ${previousState})`);
3842
+ eventManager.trigger("playlistStarted", {
3843
+ timestamp: Date.now(),
3844
+ playlist: {
3845
+ id: store.manifest.id,
3846
+ title: store.manifest.name
3847
+ }
3848
+ });
3849
+ return;
3850
+ } else if (isNormalPlaylistStart) {
3851
+ log(`EventUpdater: Deferring playlistStarted event for ${store.manifest.id} (pending autoplay confirmation)`);
3852
+ pendingStartEvents.playlistData = {
3796
3853
  id: store.manifest.id,
3797
3854
  title: store.manifest.name
3798
- }
3799
- });
3800
- return;
3855
+ };
3856
+ return;
3857
+ }
3801
3858
  }
3802
3859
  }
3803
3860
  if (previousState === "paused" && currentState === "playing") {
@@ -3831,7 +3888,7 @@ function handleMinimizeEvents(eventData, _store, eventManager, prevIsMinimized)
3831
3888
  });
3832
3889
  }
3833
3890
  }
3834
- function handleStepEvents(eventData, store, eventManager, prevStepId) {
3891
+ function handleStepEvents(eventData, store, eventManager, prevStepId, pendingStartEvents) {
3835
3892
  var _a, _b, _c;
3836
3893
  const { prevPreviousState, previousState, currentStepId, currentState } = eventData;
3837
3894
  const currentStep = store.currentStepId ? (((_a = store.manifest) == null ? void 0 : _a.steps) || []).find((s) => s.id === store.currentStepId) : null;
@@ -3865,26 +3922,38 @@ function handleStepEvents(eventData, store, eventManager, prevStepId) {
3865
3922
  }
3866
3923
  }
3867
3924
  const isStepChange = currentStepId !== prevStepId && currentState === "playing";
3868
- const isAutoplayOrIdleModeTransition = (previousState === "autoplayBlocked" || previousState === "idleMode") && currentState === "playing" && prevStepId === currentStepId;
3869
- const isFirstStepOfPlaylist = prevPreviousState === "loading" && previousState === "paused" && currentState === "playing" && currentStepId;
3870
- const shouldTriggerStepStarted = currentStep && store.manifest && (isStepChange || isAutoplayOrIdleModeTransition || isFirstStepOfPlaylist);
3925
+ const isUserInitiatedStepStart = (previousState === "autoplayBlocked" || previousState === "idleMode") && currentState === "playing" && prevStepId === currentStepId;
3926
+ const isFirstPlayOfPlaylist = prevPreviousState === "loading" && previousState === "paused" && currentState === "playing" && currentStepId;
3927
+ const isStartingNode = store.manifest && currentStepId === store.manifest.startStep;
3928
+ const isFirstStepAutoplayAttempt = isStartingNode && isFirstPlayOfPlaylist;
3929
+ const isNonStartingNodeFirstPlay = !isStartingNode && isFirstPlayOfPlaylist;
3871
3930
  log("EventUpdater.handleStepEvents: Step started check", {
3872
3931
  currentStep: currentStep == null ? void 0 : currentStep.id,
3873
3932
  hasManifest: !!store.manifest
3874
3933
  });
3875
- if (shouldTriggerStepStarted) {
3876
- log(`EventUpdater: Triggering stepStarted for ${currentStep.id}`);
3877
- eventManager.trigger("stepStarted", {
3878
- timestamp: Date.now(),
3879
- step: {
3934
+ if (currentStep && store.manifest) {
3935
+ if (isStepChange || isUserInitiatedStepStart || isNonStartingNodeFirstPlay) {
3936
+ log(`EventUpdater: Triggering stepStarted for ${currentStep.id}`);
3937
+ eventManager.trigger("stepStarted", {
3938
+ timestamp: Date.now(),
3939
+ step: {
3940
+ id: currentStep.id,
3941
+ title: currentStep.title || currentStep.id
3942
+ },
3943
+ playlist: {
3944
+ id: store.manifest.id,
3945
+ title: store.manifest.name
3946
+ }
3947
+ });
3948
+ } else if (isFirstStepAutoplayAttempt) {
3949
+ log(`EventUpdater: Deferring stepStarted for ${currentStep.id} (pending autoplay confirmation)`);
3950
+ pendingStartEvents.stepData = {
3880
3951
  id: currentStep.id,
3881
- title: currentStep.title || currentStep.id
3882
- },
3883
- playlist: {
3884
- id: store.manifest.id,
3885
- title: store.manifest.name
3886
- }
3887
- });
3952
+ title: currentStep.title || currentStep.id,
3953
+ playlistId: store.manifest.id,
3954
+ playlistTitle: store.manifest.name
3955
+ };
3956
+ }
3888
3957
  }
3889
3958
  }
3890
3959
  function handleErrorEvents(eventData, store, eventManager) {
@@ -8960,7 +9029,6 @@ class EventSubscriberManager {
8960
9029
  }
8961
9030
  }
8962
9031
  class AnalyticsManager extends EventSubscriberManager {
8963
- // Default to enabled
8964
9032
  /**
8965
9033
  * Creates a new AnalyticsManager
8966
9034
  * @param eventManager - Optional event manager to subscribe to events
@@ -8974,6 +9042,8 @@ class AnalyticsManager extends EventSubscriberManager {
8974
9042
  __publicField(this, "flushInterval", null);
8975
9043
  __publicField(this, "sessionId", null);
8976
9044
  __publicField(this, "analyticsEnabled", true);
9045
+ // Default to enabled
9046
+ __publicField(this, "boundVisibilityHandler", null);
8977
9047
  }
8978
9048
  /**
8979
9049
  * Subscribe to relevant player events for analytics tracking
@@ -9060,6 +9130,10 @@ class AnalyticsManager extends EventSubscriberManager {
9060
9130
  });
9061
9131
  }
9062
9132
  });
9133
+ this.eventManager.on("playlistStarted", (event) => {
9134
+ log(`AnalyticsManager: Playlist started event received - ${event.playlist.id}`);
9135
+ this.trackPlaylistStart(event.playlist.id);
9136
+ });
9063
9137
  }
9064
9138
  /**
9065
9139
  * Initializes the analytics manager
@@ -9076,6 +9150,8 @@ class AnalyticsManager extends EventSubscriberManager {
9076
9150
  this.flushInterval = window.setInterval(() => {
9077
9151
  this.flushEvents();
9078
9152
  }, ANALYTICS.FLUSH_INTERVAL_MS);
9153
+ this.boundVisibilityHandler = this.handleVisibilityChange.bind(this);
9154
+ document.addEventListener("visibilitychange", this.boundVisibilityHandler);
9079
9155
  }
9080
9156
  }
9081
9157
  /**
@@ -9272,6 +9348,45 @@ class AnalyticsManager extends EventSubscriberManager {
9272
9348
  this.isSending = false;
9273
9349
  }
9274
9350
  }
9351
+ /**
9352
+ * Handles visibility change events to flush analytics when page is hidden
9353
+ * Uses sendBeacon for reliable delivery during page unload
9354
+ */
9355
+ handleVisibilityChange() {
9356
+ if (document.visibilityState === "hidden") {
9357
+ this.flushWithBeacon();
9358
+ }
9359
+ }
9360
+ /**
9361
+ * Flushes events using sendBeacon API for reliable delivery during page unload
9362
+ * sendBeacon is fire-and-forget and survives page navigation/close
9363
+ */
9364
+ flushWithBeacon() {
9365
+ if (this.eventQueue.length === 0 || !this.config || !this.analyticsEnabled) {
9366
+ return;
9367
+ }
9368
+ const events = [...this.eventQueue];
9369
+ this.eventQueue = [];
9370
+ const payload = {
9371
+ token: this.config.token,
9372
+ sessionId: this.sessionId,
9373
+ user: this.user,
9374
+ events
9375
+ };
9376
+ try {
9377
+ fetch("https://player.saltfish.ai/analytics", {
9378
+ method: "POST",
9379
+ headers: {
9380
+ "Content-Type": "application/json"
9381
+ },
9382
+ body: JSON.stringify(payload),
9383
+ keepalive: true
9384
+ });
9385
+ } catch (error2) {
9386
+ console.warn("AnalyticsManager: fetch keepalive failed, events returned to queue");
9387
+ this.eventQueue = [...events, ...this.eventQueue];
9388
+ }
9389
+ }
9275
9390
  /**
9276
9391
  * Cleans up resources used by the analytics manager
9277
9392
  */
@@ -9283,6 +9398,10 @@ class AnalyticsManager extends EventSubscriberManager {
9283
9398
  clearInterval(this.flushInterval);
9284
9399
  this.flushInterval = null;
9285
9400
  }
9401
+ if (this.boundVisibilityHandler) {
9402
+ document.removeEventListener("visibilitychange", this.boundVisibilityHandler);
9403
+ this.boundVisibilityHandler = null;
9404
+ }
9286
9405
  if (this.eventManager) {
9287
9406
  this.eventManager = null;
9288
9407
  }
@@ -12590,7 +12709,7 @@ const SaltfishPlayer$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.de
12590
12709
  __proto__: null,
12591
12710
  SaltfishPlayer
12592
12711
  }, Symbol.toStringTag, { value: "Module" }));
12593
- const version = "0.3.71";
12712
+ const version = "0.3.73";
12594
12713
  const packageJson = {
12595
12714
  version
12596
12715
  };