saltfish 0.3.86 → 0.3.91
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/README.md +3 -3
- package/dist/core/services/CursorAnimationScheduler.d.ts +27 -0
- package/dist/core/services/CursorAnimationScheduler.d.ts.map +1 -0
- package/dist/core/services/ManagerOrchestrator.d.ts +1 -13
- package/dist/core/services/ManagerOrchestrator.d.ts.map +1 -1
- package/dist/core/services/StateMachineActionHandler.d.ts +26 -13
- package/dist/core/services/StateMachineActionHandler.d.ts.map +1 -1
- package/dist/core/stateMachine.d.ts +0 -4
- package/dist/core/stateMachine.d.ts.map +1 -1
- package/dist/core/stateMachineConfig.d.ts +6 -2
- package/dist/core/stateMachineConfig.d.ts.map +1 -1
- package/dist/core/store.d.ts.map +1 -1
- package/dist/managers/PlaylistManager.d.ts +14 -8
- package/dist/managers/PlaylistManager.d.ts.map +1 -1
- package/dist/managers/TriggerManager.d.ts.map +1 -1
- package/dist/managers/VideoControlsUI.d.ts +8 -0
- package/dist/managers/VideoControlsUI.d.ts.map +1 -1
- package/dist/managers/VideoManager.d.ts +12 -0
- package/dist/managers/VideoManager.d.ts.map +1 -1
- package/dist/player.js +2 -2
- package/dist/player.min.js +2 -2
- package/dist/saltfish-playlist-player.es.js +442 -588
- package/dist/saltfish-playlist-player.umd.js +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/progressUtils.d.ts +0 -7
- package/dist/utils/progressUtils.d.ts.map +1 -1
- package/package.json +5 -5
|
@@ -699,32 +699,8 @@ class PlayerStateMachine {
|
|
|
699
699
|
this.config = config;
|
|
700
700
|
this.currentState = config.initial;
|
|
701
701
|
this.context = initialContext;
|
|
702
|
-
this.setupDefaultActions();
|
|
703
702
|
this.runEntryActions(this.currentState);
|
|
704
703
|
}
|
|
705
|
-
/**
|
|
706
|
-
* Set up default action handlers for common operations
|
|
707
|
-
*/
|
|
708
|
-
setupDefaultActions() {
|
|
709
|
-
this.actionHandlers = {
|
|
710
|
-
...this.actionHandlers,
|
|
711
|
-
logStateEntry: (_context) => {
|
|
712
|
-
log(`State Machine: Entered ${this.currentState} state`);
|
|
713
|
-
},
|
|
714
|
-
logErrorEvent: (_context, event) => {
|
|
715
|
-
if ((event == null ? void 0 : event.type) === "ERROR") {
|
|
716
|
-
log(`State Machine: ERROR event received with message: ${event.error.message}`);
|
|
717
|
-
}
|
|
718
|
-
},
|
|
719
|
-
logStepTransition: (_context, event) => {
|
|
720
|
-
if ((event == null ? void 0 : event.type) === "TRANSITION_TO_STEP") {
|
|
721
|
-
log(`State Machine: Transitioning to new step: ${event.step.id}`);
|
|
722
|
-
}
|
|
723
|
-
},
|
|
724
|
-
logErrorRecovery: () => {
|
|
725
|
-
}
|
|
726
|
-
};
|
|
727
|
-
}
|
|
728
704
|
/**
|
|
729
705
|
* Register custom action handlers
|
|
730
706
|
* @param actions - Object mapping action names to handler functions
|
|
@@ -833,18 +809,14 @@ const playerStateMachineConfig = {
|
|
|
833
809
|
on: {
|
|
834
810
|
"INITIALIZE": { target: "idle" },
|
|
835
811
|
"LOAD_MANIFEST": { target: "loading" }
|
|
836
|
-
}
|
|
837
|
-
entry: ["logStateEntry"]
|
|
812
|
+
}
|
|
838
813
|
},
|
|
839
814
|
"loading": {
|
|
840
815
|
on: {
|
|
841
816
|
"MANIFEST_LOADED": { target: "paused" },
|
|
842
|
-
"ERROR": {
|
|
843
|
-
target: "error",
|
|
844
|
-
actions: ["logErrorEvent"]
|
|
845
|
-
}
|
|
817
|
+
"ERROR": { target: "error" }
|
|
846
818
|
},
|
|
847
|
-
entry: ["
|
|
819
|
+
entry: ["showLoadingState"],
|
|
848
820
|
exit: ["hideLoadingState"]
|
|
849
821
|
},
|
|
850
822
|
"playing": {
|
|
@@ -853,15 +825,12 @@ const playerStateMachineConfig = {
|
|
|
853
825
|
"MINIMIZE": { target: "minimized" },
|
|
854
826
|
"VIDEO_FINISHED_WAIT": { target: "waitingForInteraction" },
|
|
855
827
|
"AUTOPLAY_FALLBACK": { target: "autoplayBlocked" },
|
|
856
|
-
"TRANSITION_TO_STEP": {
|
|
857
|
-
target: "playing",
|
|
858
|
-
actions: ["logStepTransition"]
|
|
859
|
-
},
|
|
828
|
+
"TRANSITION_TO_STEP": { target: "playing" },
|
|
860
829
|
"ERROR": { target: "error" },
|
|
861
830
|
"COMPLETE_PLAYLIST": { target: "completed" },
|
|
862
831
|
"COMPLETE_PLAYLIST_WAITING_FOR_INTERACTION": { target: "completedWaitingForInteraction" }
|
|
863
832
|
},
|
|
864
|
-
entry: ["
|
|
833
|
+
entry: ["startVideoPlayback", "showVideoControls", "hidePlayButton"],
|
|
865
834
|
exit: ["pauseVideoPlayback"]
|
|
866
835
|
},
|
|
867
836
|
"paused": {
|
|
@@ -870,19 +839,16 @@ const playerStateMachineConfig = {
|
|
|
870
839
|
"START_IDLE_MODE": { target: "idleMode" },
|
|
871
840
|
"AUTOPLAY_FALLBACK": { target: "autoplayBlocked" },
|
|
872
841
|
"MINIMIZE": { target: "minimized" },
|
|
873
|
-
"TRANSITION_TO_STEP": {
|
|
874
|
-
target: "playing",
|
|
875
|
-
actions: ["logStepTransition"]
|
|
876
|
-
}
|
|
842
|
+
"TRANSITION_TO_STEP": { target: "playing" }
|
|
877
843
|
},
|
|
878
|
-
entry: ["
|
|
844
|
+
entry: ["pauseVideoPlayback", "showPlayButton"]
|
|
879
845
|
},
|
|
880
846
|
"minimized": {
|
|
881
847
|
on: {
|
|
882
848
|
"MAXIMIZE": { target: "playing" },
|
|
883
849
|
"EXIT": { target: "closing" }
|
|
884
850
|
},
|
|
885
|
-
entry: ["
|
|
851
|
+
entry: ["pauseVideoPlayback"]
|
|
886
852
|
},
|
|
887
853
|
"waitingForInteraction": {
|
|
888
854
|
on: {
|
|
@@ -891,12 +857,9 @@ const playerStateMachineConfig = {
|
|
|
891
857
|
"MINIMIZE": { target: "minimized" },
|
|
892
858
|
"COMPLETE_PLAYLIST": { target: "completed" },
|
|
893
859
|
"COMPLETE_PLAYLIST_WAITING_FOR_INTERACTION": { target: "completedWaitingForInteraction" },
|
|
894
|
-
"TRANSITION_TO_STEP": {
|
|
895
|
-
target: "playing",
|
|
896
|
-
actions: ["logStepTransition"]
|
|
897
|
-
}
|
|
860
|
+
"TRANSITION_TO_STEP": { target: "playing" }
|
|
898
861
|
},
|
|
899
|
-
entry: ["
|
|
862
|
+
entry: ["showPlayButton"]
|
|
900
863
|
},
|
|
901
864
|
"autoplayBlocked": {
|
|
902
865
|
on: {
|
|
@@ -904,7 +867,7 @@ const playerStateMachineConfig = {
|
|
|
904
867
|
"TRANSITION_TO_STEP": { target: "playing" },
|
|
905
868
|
"MINIMIZE": { target: "minimized" }
|
|
906
869
|
},
|
|
907
|
-
entry: ["
|
|
870
|
+
entry: ["enterCompactMode", "startMutedLoopedVideo", "hideVideoControls", "showPlayButton", "enablePlayButtonProminent"],
|
|
908
871
|
exit: ["disablePlayButtonProminent", "showVideoControls", "exitCompactMode"]
|
|
909
872
|
},
|
|
910
873
|
"idleMode": {
|
|
@@ -913,43 +876,37 @@ const playerStateMachineConfig = {
|
|
|
913
876
|
"TRANSITION_TO_STEP": { target: "playing" },
|
|
914
877
|
"MINIMIZE": { target: "minimized" }
|
|
915
878
|
},
|
|
916
|
-
entry: ["
|
|
879
|
+
entry: ["startIdleModeVideo", "hideVideoControls", "showPlayButton", "enablePlayButtonProminent"],
|
|
917
880
|
exit: ["disablePlayButtonProminent", "showVideoControls", "exitCompactMode"]
|
|
918
881
|
},
|
|
919
882
|
"error": {
|
|
920
883
|
on: {
|
|
921
884
|
"INITIALIZE": { target: "idle" },
|
|
922
|
-
"PLAY": {
|
|
923
|
-
target: "playing",
|
|
924
|
-
actions: ["logErrorRecovery"]
|
|
925
|
-
},
|
|
885
|
+
"PLAY": { target: "playing" },
|
|
926
886
|
"AUTOPLAY_FALLBACK": { target: "autoplayBlocked" }
|
|
927
887
|
},
|
|
928
|
-
entry: ["
|
|
888
|
+
entry: ["handleError", "showPlayButton"],
|
|
929
889
|
exit: ["hideError"]
|
|
930
890
|
},
|
|
931
891
|
"completedWaitingForInteraction": {
|
|
932
892
|
on: {
|
|
933
893
|
"COMPLETE_PLAYLIST": { target: "completed" },
|
|
934
894
|
"INITIALIZE": { target: "idle" },
|
|
935
|
-
"TRANSITION_TO_STEP": {
|
|
936
|
-
target: "playing",
|
|
937
|
-
actions: ["logStepTransition"]
|
|
938
|
-
},
|
|
895
|
+
"TRANSITION_TO_STEP": { target: "playing" },
|
|
939
896
|
"PLAY": { target: "playing" },
|
|
940
897
|
"MINIMIZE": { target: "minimized" }
|
|
941
898
|
},
|
|
942
|
-
entry: ["
|
|
899
|
+
entry: ["showPlayButton"]
|
|
943
900
|
},
|
|
944
901
|
"completed": {
|
|
945
902
|
on: {
|
|
946
903
|
"INITIALIZE": { target: "idle" }
|
|
947
904
|
},
|
|
948
|
-
entry: ["
|
|
905
|
+
entry: ["trackPlaylistComplete"]
|
|
949
906
|
},
|
|
950
907
|
"closing": {
|
|
951
908
|
on: {},
|
|
952
|
-
entry: ["
|
|
909
|
+
entry: ["triggerPlaylistDismissed"]
|
|
953
910
|
}
|
|
954
911
|
}
|
|
955
912
|
};
|
|
@@ -1212,6 +1169,13 @@ const saltfishStore = createStore()(
|
|
|
1212
1169
|
const newState = _stateMachine.send(event);
|
|
1213
1170
|
return newState;
|
|
1214
1171
|
};
|
|
1172
|
+
const dispatch = (event) => {
|
|
1173
|
+
const newState = _stateMachine.send(event);
|
|
1174
|
+
set2((state) => {
|
|
1175
|
+
state.currentState = newState;
|
|
1176
|
+
});
|
|
1177
|
+
return newState;
|
|
1178
|
+
};
|
|
1215
1179
|
return {
|
|
1216
1180
|
// State
|
|
1217
1181
|
config: null,
|
|
@@ -1273,7 +1237,51 @@ const saltfishStore = createStore()(
|
|
|
1273
1237
|
},
|
|
1274
1238
|
setUserData: (userData) => {
|
|
1275
1239
|
set2((state) => {
|
|
1276
|
-
|
|
1240
|
+
var _a;
|
|
1241
|
+
const incoming = (userData == null ? void 0 : userData.watchedPlaylists) || {};
|
|
1242
|
+
const existing = ((_a = state.userData) == null ? void 0 : _a.watchedPlaylists) || {};
|
|
1243
|
+
const merged = { ...incoming };
|
|
1244
|
+
for (const [playlistId, local] of Object.entries(existing)) {
|
|
1245
|
+
if (!local) {
|
|
1246
|
+
continue;
|
|
1247
|
+
}
|
|
1248
|
+
const next = merged[playlistId];
|
|
1249
|
+
const localTerminal = local.status === "completed" || local.status === "dismissed";
|
|
1250
|
+
const nextTerminal = !!next && (next.status === "completed" || next.status === "dismissed");
|
|
1251
|
+
if (localTerminal && !nextTerminal) {
|
|
1252
|
+
merged[playlistId] = local;
|
|
1253
|
+
log(`Store: setUserData preserved local '${local.status}' status for playlist ${playlistId} (incoming was '${(next == null ? void 0 : next.status) ?? "absent"}')`);
|
|
1254
|
+
} else if (next) {
|
|
1255
|
+
const visitCount = Math.max(local.visitCount ?? 0, next.visitCount ?? 0);
|
|
1256
|
+
if ((next.visitCount ?? 0) !== visitCount) {
|
|
1257
|
+
merged[playlistId] = { ...next, visitCount };
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
state.userData = { ...userData, watchedPlaylists: merged };
|
|
1262
|
+
});
|
|
1263
|
+
},
|
|
1264
|
+
updateWatchedPlaylist: (playlistId, status, currentStepId) => {
|
|
1265
|
+
set2((state) => {
|
|
1266
|
+
if (!state.userData) {
|
|
1267
|
+
state.userData = { watchedPlaylists: {} };
|
|
1268
|
+
}
|
|
1269
|
+
if (!state.userData.watchedPlaylists) {
|
|
1270
|
+
state.userData.watchedPlaylists = {};
|
|
1271
|
+
}
|
|
1272
|
+
const existing = state.userData.watchedPlaylists[playlistId];
|
|
1273
|
+
const previousStatus = existing == null ? void 0 : existing.status;
|
|
1274
|
+
const previousVisitCount = (existing == null ? void 0 : existing.visitCount) ?? 0;
|
|
1275
|
+
const completesVisit = (status === "completed" || status === "dismissed") && previousStatus !== status;
|
|
1276
|
+
const now = Date.now();
|
|
1277
|
+
state.userData.watchedPlaylists[playlistId] = {
|
|
1278
|
+
status,
|
|
1279
|
+
// Completed playlists restart from the beginning, so they keep no step.
|
|
1280
|
+
currentStepId: status === "completed" ? null : currentStepId ?? state.currentStepId ?? null,
|
|
1281
|
+
timestamp: now,
|
|
1282
|
+
lastProgressAt: now,
|
|
1283
|
+
visitCount: completesVisit ? previousVisitCount + 1 : previousVisitCount
|
|
1284
|
+
};
|
|
1277
1285
|
});
|
|
1278
1286
|
},
|
|
1279
1287
|
setManifest: (manifest, startStepId) => {
|
|
@@ -1319,9 +1327,7 @@ const saltfishStore = createStore()(
|
|
|
1319
1327
|
goToStep: (stepId) => {
|
|
1320
1328
|
const { manifest } = get();
|
|
1321
1329
|
if (stepId === "completed") {
|
|
1322
|
-
|
|
1323
|
-
state.currentState = transitionState({ type: "COMPLETE_PLAYLIST" });
|
|
1324
|
-
});
|
|
1330
|
+
dispatch({ type: "COMPLETE_PLAYLIST" });
|
|
1325
1331
|
return;
|
|
1326
1332
|
}
|
|
1327
1333
|
if (manifest && manifest.steps.some((step) => step.id === stepId)) {
|
|
@@ -1406,9 +1412,7 @@ const saltfishStore = createStore()(
|
|
|
1406
1412
|
});
|
|
1407
1413
|
},
|
|
1408
1414
|
completePlaylist: () => {
|
|
1409
|
-
|
|
1410
|
-
state.currentState = transitionState({ type: "COMPLETE_PLAYLIST" });
|
|
1411
|
-
});
|
|
1415
|
+
dispatch({ type: "COMPLETE_PLAYLIST" });
|
|
1412
1416
|
},
|
|
1413
1417
|
// Add new method to reset playlist state while preserving config and user data
|
|
1414
1418
|
resetForNewPlaylist: () => {
|
|
@@ -1445,9 +1449,7 @@ const saltfishStore = createStore()(
|
|
|
1445
1449
|
},
|
|
1446
1450
|
// Method to send events to the state machine without exposing it
|
|
1447
1451
|
sendStateMachineEvent: (event) => {
|
|
1448
|
-
|
|
1449
|
-
state.currentState = transitionState(event);
|
|
1450
|
-
});
|
|
1452
|
+
dispatch(event);
|
|
1451
1453
|
},
|
|
1452
1454
|
// New action to update progress when transitioning to completion waiting state
|
|
1453
1455
|
updateProgressWithCompletion: (playlistId, currentStepId) => {
|
|
@@ -3222,14 +3224,110 @@ class PlaylistOrchestrator {
|
|
|
3222
3224
|
destroy() {
|
|
3223
3225
|
}
|
|
3224
3226
|
}
|
|
3227
|
+
class CursorAnimationScheduler {
|
|
3228
|
+
constructor(managers, logPrefix) {
|
|
3229
|
+
__publicField(this, "listener", null);
|
|
3230
|
+
__publicField(this, "videoElement", null);
|
|
3231
|
+
__publicField(this, "stepId", null);
|
|
3232
|
+
this.managers = managers;
|
|
3233
|
+
this.logPrefix = logPrefix;
|
|
3234
|
+
}
|
|
3235
|
+
/**
|
|
3236
|
+
* Cleans up the active cursor animation time listener.
|
|
3237
|
+
* @param stepId - Optional step ID to verify we're cleaning up the right listener
|
|
3238
|
+
*/
|
|
3239
|
+
cleanup(stepId) {
|
|
3240
|
+
if (stepId && this.stepId && stepId !== this.stepId) {
|
|
3241
|
+
log(`${this.logPrefix}: Skipping cleanup - stepId mismatch (requested: ${stepId}, current: ${this.stepId})`);
|
|
3242
|
+
return;
|
|
3243
|
+
}
|
|
3244
|
+
if (this.listener && this.videoElement) {
|
|
3245
|
+
this.videoElement.removeEventListener("timeupdate", this.listener);
|
|
3246
|
+
this.listener = null;
|
|
3247
|
+
this.videoElement = null;
|
|
3248
|
+
this.stepId = null;
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
/**
|
|
3252
|
+
* Schedules a cursor animation to run either immediately or at a specific video time.
|
|
3253
|
+
*/
|
|
3254
|
+
schedule(animation, stepId) {
|
|
3255
|
+
this.cleanup();
|
|
3256
|
+
let showAtSeconds = animation.showAtSeconds ?? 0;
|
|
3257
|
+
if (typeof showAtSeconds !== "number" || !isFinite(showAtSeconds)) {
|
|
3258
|
+
log(`${this.logPrefix}: Invalid showAtSeconds value (${showAtSeconds}), defaulting to 0`);
|
|
3259
|
+
showAtSeconds = 0;
|
|
3260
|
+
}
|
|
3261
|
+
if (showAtSeconds < 0) {
|
|
3262
|
+
log(`${this.logPrefix}: Negative showAtSeconds value (${showAtSeconds}), treating as immediate (0)`);
|
|
3263
|
+
showAtSeconds = 0;
|
|
3264
|
+
}
|
|
3265
|
+
if (showAtSeconds <= 0) {
|
|
3266
|
+
log(`${this.logPrefix}: Starting immediate cursor animation for step ${stepId}`);
|
|
3267
|
+
this.managers.cursorManager.animate(animation);
|
|
3268
|
+
return;
|
|
3269
|
+
}
|
|
3270
|
+
log(`${this.logPrefix}: Scheduling cursor animation for step ${stepId} at ${showAtSeconds} seconds`);
|
|
3271
|
+
const videoElement = this.managers.videoManager.getVideoElement();
|
|
3272
|
+
if (!videoElement) {
|
|
3273
|
+
log(`${this.logPrefix}: Warning - No video element found, cannot schedule delayed cursor animation`);
|
|
3274
|
+
return;
|
|
3275
|
+
}
|
|
3276
|
+
let animationTriggered = false;
|
|
3277
|
+
let warningLogged = false;
|
|
3278
|
+
const timeUpdateHandler = () => {
|
|
3279
|
+
if (animationTriggered) {
|
|
3280
|
+
return;
|
|
3281
|
+
}
|
|
3282
|
+
const currentTime = videoElement.currentTime;
|
|
3283
|
+
const duration = videoElement.duration;
|
|
3284
|
+
if (duration && !isNaN(duration) && showAtSeconds > duration && !warningLogged) {
|
|
3285
|
+
log(`${this.logPrefix}: showAtSeconds (${showAtSeconds}s) exceeds video duration (${duration}s) for step ${stepId}. Triggering animation immediately.`);
|
|
3286
|
+
warningLogged = true;
|
|
3287
|
+
animationTriggered = true;
|
|
3288
|
+
this.cleanup();
|
|
3289
|
+
this.managers.cursorManager.animate(animation);
|
|
3290
|
+
return;
|
|
3291
|
+
}
|
|
3292
|
+
if (currentTime >= showAtSeconds) {
|
|
3293
|
+
animationTriggered = true;
|
|
3294
|
+
log(`${this.logPrefix}: Triggering cursor animation at ${currentTime} seconds`);
|
|
3295
|
+
this.cleanup();
|
|
3296
|
+
this.managers.cursorManager.animate(animation);
|
|
3297
|
+
}
|
|
3298
|
+
};
|
|
3299
|
+
const endedHandler = () => {
|
|
3300
|
+
if (animationTriggered) {
|
|
3301
|
+
return;
|
|
3302
|
+
}
|
|
3303
|
+
const store = getSaltfishStore();
|
|
3304
|
+
if (store.currentStepId !== stepId) {
|
|
3305
|
+
log(`${this.logPrefix}: Video ended but step changed (was ${stepId}, now ${store.currentStepId}). Not triggering cursor animation.`);
|
|
3306
|
+
this.cleanup(stepId);
|
|
3307
|
+
return;
|
|
3308
|
+
}
|
|
3309
|
+
const duration = videoElement.duration;
|
|
3310
|
+
log(`${this.logPrefix}: Video ended before cursor animation could trigger (showAtSeconds: ${showAtSeconds}s, duration: ${duration}s). Triggering at end.`);
|
|
3311
|
+
animationTriggered = true;
|
|
3312
|
+
this.managers.cursorManager.animate(animation);
|
|
3313
|
+
this.cleanup();
|
|
3314
|
+
};
|
|
3315
|
+
this.listener = () => {
|
|
3316
|
+
timeUpdateHandler();
|
|
3317
|
+
};
|
|
3318
|
+
this.videoElement = videoElement;
|
|
3319
|
+
this.stepId = stepId;
|
|
3320
|
+
videoElement.addEventListener("timeupdate", this.listener);
|
|
3321
|
+
videoElement.addEventListener("ended", endedHandler, { once: true });
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3225
3324
|
class StateMachineActionHandler {
|
|
3226
3325
|
constructor(managers) {
|
|
3227
3326
|
__publicField(this, "managers");
|
|
3228
3327
|
__publicField(this, "destroyCallback", null);
|
|
3229
|
-
__publicField(this, "
|
|
3230
|
-
__publicField(this, "cursorAnimationVideoElement", null);
|
|
3231
|
-
__publicField(this, "cursorAnimationStepId", null);
|
|
3328
|
+
__publicField(this, "cursorScheduler");
|
|
3232
3329
|
this.managers = managers;
|
|
3330
|
+
this.cursorScheduler = new CursorAnimationScheduler(managers, "StateMachineActionHandler");
|
|
3233
3331
|
}
|
|
3234
3332
|
/**
|
|
3235
3333
|
* Set the destroy callback for player destruction
|
|
@@ -3243,63 +3341,25 @@ class StateMachineActionHandler {
|
|
|
3243
3341
|
registerStateMachineActions() {
|
|
3244
3342
|
const store = getSaltfishStore();
|
|
3245
3343
|
store.registerStateMachineActions({
|
|
3246
|
-
startVideoPlayback: (context) =>
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
pauseVideoPlayback: () =>
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
this.handleHideError();
|
|
3266
|
-
},
|
|
3267
|
-
showLoadingState: () => {
|
|
3268
|
-
this.handleShowLoadingState();
|
|
3269
|
-
},
|
|
3270
|
-
hideLoadingState: () => {
|
|
3271
|
-
this.handleHideLoadingState();
|
|
3272
|
-
},
|
|
3273
|
-
hideVideoControls: () => {
|
|
3274
|
-
this.handleHideVideoControls();
|
|
3275
|
-
},
|
|
3276
|
-
showVideoControls: () => {
|
|
3277
|
-
this.handleShowVideoControls();
|
|
3278
|
-
},
|
|
3279
|
-
showPlayButton: () => {
|
|
3280
|
-
this.handleShowPlayButton();
|
|
3281
|
-
},
|
|
3282
|
-
hidePlayButton: () => {
|
|
3283
|
-
this.handleHidePlayButton();
|
|
3284
|
-
},
|
|
3285
|
-
enablePlayButtonProminent: () => {
|
|
3286
|
-
this.handleEnablePlayButtonProminent();
|
|
3287
|
-
},
|
|
3288
|
-
disablePlayButtonProminent: () => {
|
|
3289
|
-
this.handleDisablePlayButtonProminent();
|
|
3290
|
-
},
|
|
3291
|
-
enterCompactMode: () => {
|
|
3292
|
-
this.handleEnterCompactMode();
|
|
3293
|
-
},
|
|
3294
|
-
exitCompactMode: () => {
|
|
3295
|
-
this.handleExitCompactMode();
|
|
3296
|
-
},
|
|
3297
|
-
triggerPlaylistDismissed: () => {
|
|
3298
|
-
this.handleTriggerPlaylistDismissed();
|
|
3299
|
-
},
|
|
3300
|
-
scheduleDestroy: () => {
|
|
3301
|
-
this.handleScheduleDestroy();
|
|
3302
|
-
}
|
|
3344
|
+
startVideoPlayback: (context) => this.handleStartVideoPlayback(context),
|
|
3345
|
+
startIdleModeVideo: (context) => this.handleStartIdleModeVideo(context),
|
|
3346
|
+
handleError: (context) => this.handleError(context),
|
|
3347
|
+
pauseVideoPlayback: () => this.handlePauseVideoPlayback(),
|
|
3348
|
+
startMutedLoopedVideo: () => this.handleStartMutedLoopedVideo(),
|
|
3349
|
+
trackPlaylistComplete: () => this.handleTrackPlaylistComplete(),
|
|
3350
|
+
hideError: () => this.handleHideError(),
|
|
3351
|
+
showLoadingState: () => this.handleShowLoadingState(),
|
|
3352
|
+
hideLoadingState: () => this.handleHideLoadingState(),
|
|
3353
|
+
hideVideoControls: () => this.handleHideVideoControls(),
|
|
3354
|
+
showVideoControls: () => this.handleShowVideoControls(),
|
|
3355
|
+
showPlayButton: () => this.handleShowPlayButton(),
|
|
3356
|
+
hidePlayButton: () => this.handleHidePlayButton(),
|
|
3357
|
+
enablePlayButtonProminent: () => this.handleEnablePlayButtonProminent(),
|
|
3358
|
+
disablePlayButtonProminent: () => this.handleDisablePlayButtonProminent(),
|
|
3359
|
+
enterCompactMode: () => this.handleEnterCompactMode(),
|
|
3360
|
+
exitCompactMode: () => this.handleExitCompactMode(),
|
|
3361
|
+
triggerPlaylistDismissed: () => this.handleTriggerPlaylistDismissed(),
|
|
3362
|
+
scheduleDestroy: () => this.handleScheduleDestroy()
|
|
3303
3363
|
});
|
|
3304
3364
|
}
|
|
3305
3365
|
/**
|
|
@@ -3367,87 +3427,7 @@ class StateMachineActionHandler {
|
|
|
3367
3427
|
return null;
|
|
3368
3428
|
}
|
|
3369
3429
|
destroy() {
|
|
3370
|
-
this.
|
|
3371
|
-
}
|
|
3372
|
-
/**
|
|
3373
|
-
* Cleans up the active cursor animation time listener
|
|
3374
|
-
* @param stepId - Optional step ID to verify we're cleaning up the right listener
|
|
3375
|
-
*/
|
|
3376
|
-
cleanupCursorAnimationListener(stepId) {
|
|
3377
|
-
if (stepId && this.cursorAnimationStepId && stepId !== this.cursorAnimationStepId) {
|
|
3378
|
-
log(`StateMachineActionHandler: Skipping cleanup - stepId mismatch (requested: ${stepId}, current: ${this.cursorAnimationStepId})`);
|
|
3379
|
-
return;
|
|
3380
|
-
}
|
|
3381
|
-
if (this.cursorAnimationListener && this.cursorAnimationVideoElement) {
|
|
3382
|
-
this.cursorAnimationVideoElement.removeEventListener("timeupdate", this.cursorAnimationListener);
|
|
3383
|
-
this.cursorAnimationListener = null;
|
|
3384
|
-
this.cursorAnimationVideoElement = null;
|
|
3385
|
-
this.cursorAnimationStepId = null;
|
|
3386
|
-
}
|
|
3387
|
-
}
|
|
3388
|
-
/**
|
|
3389
|
-
* Schedules a cursor animation to run either immediately or at a specific video time
|
|
3390
|
-
* Note: Works for both video and audio-only steps (audio files use the same video element)
|
|
3391
|
-
*/
|
|
3392
|
-
scheduleCursorAnimation(animation, stepId) {
|
|
3393
|
-
this.cleanupCursorAnimationListener();
|
|
3394
|
-
let showAtSeconds = animation.showAtSeconds ?? 0;
|
|
3395
|
-
if (typeof showAtSeconds !== "number" || !isFinite(showAtSeconds)) {
|
|
3396
|
-
showAtSeconds = 0;
|
|
3397
|
-
}
|
|
3398
|
-
if (showAtSeconds < 0) {
|
|
3399
|
-
showAtSeconds = 0;
|
|
3400
|
-
}
|
|
3401
|
-
if (showAtSeconds <= 0) {
|
|
3402
|
-
this.managers.cursorManager.animate(animation);
|
|
3403
|
-
} else {
|
|
3404
|
-
const videoElement = this.managers.videoManager.getVideoElement();
|
|
3405
|
-
if (!videoElement) {
|
|
3406
|
-
return;
|
|
3407
|
-
}
|
|
3408
|
-
let animationTriggered = false;
|
|
3409
|
-
let warningLogged = false;
|
|
3410
|
-
const timeUpdateHandler = () => {
|
|
3411
|
-
if (animationTriggered) {
|
|
3412
|
-
return;
|
|
3413
|
-
}
|
|
3414
|
-
const currentTime = videoElement.currentTime;
|
|
3415
|
-
const duration = videoElement.duration;
|
|
3416
|
-
if (duration && !isNaN(duration) && showAtSeconds > duration && !warningLogged) {
|
|
3417
|
-
warningLogged = true;
|
|
3418
|
-
animationTriggered = true;
|
|
3419
|
-
this.cleanupCursorAnimationListener();
|
|
3420
|
-
this.managers.cursorManager.animate(animation);
|
|
3421
|
-
return;
|
|
3422
|
-
}
|
|
3423
|
-
if (currentTime >= showAtSeconds) {
|
|
3424
|
-
animationTriggered = true;
|
|
3425
|
-
this.cleanupCursorAnimationListener();
|
|
3426
|
-
this.managers.cursorManager.animate(animation);
|
|
3427
|
-
}
|
|
3428
|
-
};
|
|
3429
|
-
const endedHandler = () => {
|
|
3430
|
-
if (!animationTriggered) {
|
|
3431
|
-
const store = getSaltfishStore();
|
|
3432
|
-
if (store.currentStepId !== stepId) {
|
|
3433
|
-
log(`StateMachineActionHandler: Video ended but step changed (was ${stepId}, now ${store.currentStepId}). Not triggering cursor animation.`);
|
|
3434
|
-
this.cleanupCursorAnimationListener(stepId);
|
|
3435
|
-
return;
|
|
3436
|
-
}
|
|
3437
|
-
videoElement.duration;
|
|
3438
|
-
animationTriggered = true;
|
|
3439
|
-
this.managers.cursorManager.animate(animation);
|
|
3440
|
-
this.cleanupCursorAnimationListener();
|
|
3441
|
-
}
|
|
3442
|
-
};
|
|
3443
|
-
this.cursorAnimationListener = () => {
|
|
3444
|
-
timeUpdateHandler();
|
|
3445
|
-
};
|
|
3446
|
-
this.cursorAnimationVideoElement = videoElement;
|
|
3447
|
-
this.cursorAnimationStepId = stepId;
|
|
3448
|
-
videoElement.addEventListener("timeupdate", this.cursorAnimationListener);
|
|
3449
|
-
videoElement.addEventListener("ended", endedHandler, { once: true });
|
|
3450
|
-
}
|
|
3430
|
+
this.cursorScheduler.cleanup();
|
|
3451
3431
|
}
|
|
3452
3432
|
/**
|
|
3453
3433
|
* Validates URL requirement for a specific step with retry logic
|
|
@@ -3494,158 +3474,155 @@ class StateMachineActionHandler {
|
|
|
3494
3474
|
this.managers.uiManager.showPlayer();
|
|
3495
3475
|
const videoUrl = this.getVideoUrl(currentStep);
|
|
3496
3476
|
const isAudioFallback = this.isUsingAudioFallback(currentStep);
|
|
3497
|
-
|
|
3498
|
-
const store = getSaltfishStore();
|
|
3499
|
-
const manifest = store.manifest;
|
|
3500
|
-
const posterUrl = currentStep.gifUrl;
|
|
3501
|
-
const avatarThumbnailUrl = manifest == null ? void 0 : manifest.avatarThumbnailUrl;
|
|
3502
|
-
this.managers.videoManager.showAudioFallbackOverlay(posterUrl, avatarThumbnailUrl);
|
|
3503
|
-
} else {
|
|
3504
|
-
this.managers.videoManager.hideAudioFallbackOverlay();
|
|
3505
|
-
}
|
|
3477
|
+
this.showOrHideAudioOverlay(currentStep, isAudioFallback);
|
|
3506
3478
|
try {
|
|
3507
3479
|
this.managers.interactionManager.clearButtons();
|
|
3508
3480
|
if (currentStep.buttons) {
|
|
3509
3481
|
this.managers.interactionManager.createButtons(currentStep.buttons);
|
|
3510
3482
|
}
|
|
3511
|
-
log(`StateMachineActionHandler: Step has cursor animations: ${!!(currentStep.cursorAnimations && currentStep.cursorAnimations.length > 0)}`);
|
|
3512
3483
|
const hasSpecialTransitions = currentStep.buttons && currentStep.buttons.length > 0 || currentStep.transitions.some(
|
|
3513
3484
|
(t) => t.type === "dom-click" || t.type === "url-path" || t.type === "dom-element-visible"
|
|
3514
3485
|
);
|
|
3515
|
-
const completionPolicy = hasSpecialTransitions ? "manual" : "auto";
|
|
3516
3486
|
if (hasSpecialTransitions) {
|
|
3517
3487
|
log("StateMachineActionHandler: Setting up transitions immediately for step with special transitions");
|
|
3518
3488
|
this.managers.transitionManager.setupTransitions(currentStep, false, true);
|
|
3519
3489
|
}
|
|
3520
|
-
this.managers.videoManager.setCompletionPolicy(
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
const timeoutTransition = currentStep.transitions.find((t) => t.type === "timeout");
|
|
3525
|
-
const hasNonZeroTimeout = timeoutTransition && timeoutTransition.timeout && timeoutTransition.timeout > 0;
|
|
3526
|
-
if (hasNonZeroTimeout) {
|
|
3527
|
-
log(`StateMachineActionHandler: Setting up transitions after video ended with ${timeoutTransition.timeout}ms delay`);
|
|
3528
|
-
this.managers.transitionManager.setupTransitions(currentStep, false);
|
|
3529
|
-
} else {
|
|
3530
|
-
log("StateMachineActionHandler: Setting up transitions after video ended (immediate)");
|
|
3531
|
-
this.managers.transitionManager.setupTransitions(currentStep, true);
|
|
3532
|
-
}
|
|
3533
|
-
const hasValidNextSteps = currentStep.transitions.some((transition) => {
|
|
3534
|
-
return store.manifest.steps.some((s) => s.id === transition.nextStep);
|
|
3535
|
-
});
|
|
3536
|
-
if (!hasValidNextSteps) {
|
|
3537
|
-
log("StateMachineActionHandler: No valid next steps found, completing playlist");
|
|
3538
|
-
store.sendStateMachineEvent({
|
|
3539
|
-
type: "COMPLETE_PLAYLIST"
|
|
3540
|
-
});
|
|
3541
|
-
}
|
|
3542
|
-
} else {
|
|
3543
|
-
const timeoutTransition = currentStep.transitions.find((t) => t.type === "timeout");
|
|
3544
|
-
const hasStepTransitionButtons = (_a = currentStep.buttons) == null ? void 0 : _a.some(
|
|
3545
|
-
(button) => button.action.type === "goto" || button.action.type === "next"
|
|
3546
|
-
);
|
|
3547
|
-
if (timeoutTransition && !hasStepTransitionButtons) {
|
|
3548
|
-
const timeout = timeoutTransition.timeout || 0;
|
|
3549
|
-
log(`StateMachineActionHandler: Video ended, setting up timeout transition to ${timeoutTransition.nextStep} with ${timeout}ms delay`);
|
|
3550
|
-
setTimeout(() => {
|
|
3551
|
-
var _a2;
|
|
3552
|
-
const currentStore = getSaltfishStore();
|
|
3553
|
-
if (currentStore.currentStepId === currentStep.id && (currentStore.currentState === "waitingForInteraction" || currentStore.currentState === "playing")) {
|
|
3554
|
-
log(`StateMachineActionHandler: Timeout expired (${timeout}ms), transitioning to ${timeoutTransition.nextStep}`);
|
|
3555
|
-
(_a2 = currentStore.goToStep) == null ? void 0 : _a2.call(currentStore, timeoutTransition.nextStep);
|
|
3556
|
-
} else {
|
|
3557
|
-
log(`StateMachineActionHandler: Timeout cancelled - step changed or state is ${currentStore.currentState}`);
|
|
3558
|
-
}
|
|
3559
|
-
}, timeout);
|
|
3560
|
-
store.sendStateMachineEvent({
|
|
3561
|
-
type: "VIDEO_FINISHED_WAIT",
|
|
3562
|
-
step: currentStep
|
|
3563
|
-
});
|
|
3564
|
-
} else {
|
|
3565
|
-
if (hasStepTransitionButtons) {
|
|
3566
|
-
log("StateMachineActionHandler: Video ended, has step-transition button (goto/next) - waiting for user to click button");
|
|
3567
|
-
} else {
|
|
3568
|
-
log("StateMachineActionHandler: Video ended, waiting for user interaction");
|
|
3569
|
-
}
|
|
3570
|
-
store.sendStateMachineEvent({
|
|
3571
|
-
type: "VIDEO_FINISHED_WAIT",
|
|
3572
|
-
step: currentStep
|
|
3573
|
-
});
|
|
3574
|
-
}
|
|
3575
|
-
}
|
|
3576
|
-
});
|
|
3490
|
+
this.managers.videoManager.setCompletionPolicy(
|
|
3491
|
+
hasSpecialTransitions ? "manual" : "auto",
|
|
3492
|
+
() => this.handleStepVideoEnded(currentStep, hasSpecialTransitions)
|
|
3493
|
+
);
|
|
3577
3494
|
log("StateMachineActionHandler: Starting async video load");
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
}
|
|
3615
|
-
|
|
3616
|
-
|
|
3495
|
+
this.managers.videoManager.loadVideo(videoUrl).then(() => this.handleStepVideoLoaded(currentStep, isAudioFallback)).catch((error2) => this.handleStepVideoLoadError(error2, currentStep));
|
|
3496
|
+
} catch (error2) {
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
/**
|
|
3500
|
+
* Shows the audio-fallback overlay for audio-only steps, or hides it for regular video.
|
|
3501
|
+
*/
|
|
3502
|
+
showOrHideAudioOverlay(step, isAudioFallback) {
|
|
3503
|
+
if (isAudioFallback) {
|
|
3504
|
+
const manifest = getSaltfishStore().manifest;
|
|
3505
|
+
this.managers.videoManager.showAudioFallbackOverlay(step.gifUrl, manifest == null ? void 0 : manifest.avatarThumbnailUrl);
|
|
3506
|
+
} else {
|
|
3507
|
+
this.managers.videoManager.hideAudioFallbackOverlay();
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
/**
|
|
3511
|
+
* Runs when the current step's video ends. Decides how the playlist advances:
|
|
3512
|
+
* - auto policy: set up the step's transitions, or complete if there are none;
|
|
3513
|
+
* - manual policy: arm a timeout fallback (if any) and move to the waiting state.
|
|
3514
|
+
*/
|
|
3515
|
+
handleStepVideoEnded(currentStep, hasSpecialTransitions) {
|
|
3516
|
+
var _a;
|
|
3517
|
+
const store = getSaltfishStore();
|
|
3518
|
+
if (!hasSpecialTransitions) {
|
|
3519
|
+
const timeoutTransition2 = currentStep.transitions.find((t) => t.type === "timeout");
|
|
3520
|
+
const hasNonZeroTimeout = timeoutTransition2 && timeoutTransition2.timeout && timeoutTransition2.timeout > 0;
|
|
3521
|
+
if (hasNonZeroTimeout) {
|
|
3522
|
+
log(`StateMachineActionHandler: Setting up transitions after video ended with ${timeoutTransition2.timeout}ms delay`);
|
|
3523
|
+
this.managers.transitionManager.setupTransitions(currentStep, false);
|
|
3524
|
+
} else {
|
|
3525
|
+
this.managers.transitionManager.setupTransitions(currentStep, true);
|
|
3526
|
+
}
|
|
3527
|
+
const hasValidNextSteps = currentStep.transitions.some(
|
|
3528
|
+
(transition) => store.manifest.steps.some((s) => s.id === transition.nextStep)
|
|
3529
|
+
);
|
|
3530
|
+
if (!hasValidNextSteps) {
|
|
3531
|
+
store.sendStateMachineEvent({ type: "COMPLETE_PLAYLIST" });
|
|
3532
|
+
}
|
|
3533
|
+
return;
|
|
3534
|
+
}
|
|
3535
|
+
const timeoutTransition = currentStep.transitions.find((t) => t.type === "timeout");
|
|
3536
|
+
const hasStepTransitionButtons = (_a = currentStep.buttons) == null ? void 0 : _a.some(
|
|
3537
|
+
(button) => button.action.type === "goto" || button.action.type === "next"
|
|
3538
|
+
);
|
|
3539
|
+
if (timeoutTransition && !hasStepTransitionButtons) {
|
|
3540
|
+
const timeout = timeoutTransition.timeout || 0;
|
|
3541
|
+
log(`StateMachineActionHandler: Video ended, setting up timeout transition to ${timeoutTransition.nextStep} with ${timeout}ms delay`);
|
|
3542
|
+
setTimeout(() => {
|
|
3543
|
+
var _a2;
|
|
3544
|
+
const currentStore = getSaltfishStore();
|
|
3545
|
+
if (currentStore.currentStepId === currentStep.id && (currentStore.currentState === "waitingForInteraction" || currentStore.currentState === "playing")) {
|
|
3546
|
+
log(`StateMachineActionHandler: Timeout expired (${timeout}ms), transitioning to ${timeoutTransition.nextStep}`);
|
|
3547
|
+
(_a2 = currentStore.goToStep) == null ? void 0 : _a2.call(currentStore, timeoutTransition.nextStep);
|
|
3617
3548
|
} else {
|
|
3618
|
-
|
|
3549
|
+
log(`StateMachineActionHandler: Timeout cancelled - step changed or state is ${currentStore.currentState}`);
|
|
3619
3550
|
}
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
}
|
|
3551
|
+
}, timeout);
|
|
3552
|
+
}
|
|
3553
|
+
store.sendStateMachineEvent({ type: "VIDEO_FINISHED_WAIT", step: currentStep });
|
|
3554
|
+
}
|
|
3555
|
+
/**
|
|
3556
|
+
* Runs after the step's video has loaded: shows transcript, cursor animation,
|
|
3557
|
+
* starts/initializes audio, plays, and preloads the next video.
|
|
3558
|
+
*/
|
|
3559
|
+
handleStepVideoLoaded(currentStep, isAudioFallback) {
|
|
3560
|
+
this.managers.uiManager.hideError();
|
|
3561
|
+
this.loadTranscriptForStep(currentStep);
|
|
3562
|
+
if (currentStep.cursorAnimations && currentStep.cursorAnimations.length > 0) {
|
|
3563
|
+
log(`StateMachineActionHandler: Setting cursor visibility and scheduling animation for step ${currentStep.id}`);
|
|
3564
|
+
this.managers.cursorManager.setShouldShowCursor(true);
|
|
3565
|
+
this.cursorScheduler.schedule(currentStep.cursorAnimations[0], currentStep.id);
|
|
3566
|
+
} else {
|
|
3567
|
+
log(`StateMachineActionHandler: Setting cursor visibility to false for step ${currentStep.id} - step has no cursor animations`);
|
|
3568
|
+
this.managers.cursorManager.setShouldShowCursor(false);
|
|
3569
|
+
}
|
|
3570
|
+
if (isAudioFallback) {
|
|
3571
|
+
this.managers.videoManager.startAudioVisualization();
|
|
3572
|
+
} else {
|
|
3573
|
+
this.managers.videoManager.initializeAudioForVideo();
|
|
3574
|
+
}
|
|
3575
|
+
this.managers.videoManager.play();
|
|
3576
|
+
const nextVideoUrl = this.findNextVideoUrl(currentStep);
|
|
3577
|
+
if (nextVideoUrl) {
|
|
3578
|
+
this.managers.videoManager.preloadNextVideo(nextVideoUrl);
|
|
3579
|
+
}
|
|
3580
|
+
}
|
|
3581
|
+
/**
|
|
3582
|
+
* Runs when the step's video fails to load: shows an error and emits an
|
|
3583
|
+
* enriched error event for analytics.
|
|
3584
|
+
*/
|
|
3585
|
+
handleStepVideoLoadError(error2, currentStep) {
|
|
3586
|
+
var _a;
|
|
3587
|
+
const errorObj = error2 instanceof Error ? error2 : new Error(`Failed to load video: ${error2}`);
|
|
3588
|
+
this.managers.uiManager.showError(errorObj, "video");
|
|
3589
|
+
const store = getSaltfishStore();
|
|
3590
|
+
const enrichedError = error2;
|
|
3591
|
+
this.managers.eventManager.trigger("error", {
|
|
3592
|
+
timestamp: Date.now(),
|
|
3593
|
+
playlistId: ((_a = store.manifest) == null ? void 0 : _a.id) || void 0,
|
|
3594
|
+
stepId: currentStep.id,
|
|
3595
|
+
error: errorObj,
|
|
3596
|
+
errorType: "video",
|
|
3597
|
+
videoUrl: enrichedError == null ? void 0 : enrichedError.videoUrl,
|
|
3598
|
+
mediaErrorCode: enrichedError == null ? void 0 : enrichedError.mediaErrorCode,
|
|
3599
|
+
mediaErrorMessage: enrichedError == null ? void 0 : enrichedError.mediaErrorMessage,
|
|
3600
|
+
failureReason: enrichedError == null ? void 0 : enrichedError.failureReason
|
|
3601
|
+
});
|
|
3602
|
+
}
|
|
3603
|
+
/**
|
|
3604
|
+
* Loads the transcript for a step, preferring a translated transcript when a
|
|
3605
|
+
* language is configured. Falls back to clearing the transcript if none exists.
|
|
3606
|
+
*/
|
|
3607
|
+
loadTranscriptForStep(step) {
|
|
3608
|
+
var _a, _b;
|
|
3609
|
+
const store = getSaltfishStore();
|
|
3610
|
+
const captionsEnabled = ((_a = store.manifest) == null ? void 0 : _a.captions) ?? true;
|
|
3611
|
+
const language = (_b = store.userData) == null ? void 0 : _b.language;
|
|
3612
|
+
let transcript = step.transcript;
|
|
3613
|
+
if (language && step.translations && step.translations[language]) {
|
|
3614
|
+
const translatedTranscript = step.translations[language].transcript;
|
|
3615
|
+
if (translatedTranscript) {
|
|
3616
|
+
transcript = translatedTranscript;
|
|
3617
|
+
log(`StateMachineActionHandler: Loading translated transcript for step ${step.id} in language ${language}`);
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
if (transcript) {
|
|
3621
|
+
log(`StateMachineActionHandler: Loading transcript for step ${step.id}, initially visible: ${captionsEnabled}`);
|
|
3622
|
+
this.managers.videoManager.loadTranscript(transcript, captionsEnabled);
|
|
3623
|
+
} else {
|
|
3624
|
+
log(`StateMachineActionHandler: No transcript available for step ${step.id}`);
|
|
3625
|
+
this.managers.videoManager.loadTranscript(null, true);
|
|
3649
3626
|
}
|
|
3650
3627
|
}
|
|
3651
3628
|
handlePauseVideoPlayback() {
|
|
@@ -3668,15 +3645,7 @@ class StateMachineActionHandler {
|
|
|
3668
3645
|
const videoUrl = this.getVideoUrl(currentStep);
|
|
3669
3646
|
const isAudioFallback = this.isUsingAudioFallback(currentStep);
|
|
3670
3647
|
this.managers.uiManager.showPlayer();
|
|
3671
|
-
|
|
3672
|
-
const store = getSaltfishStore();
|
|
3673
|
-
const manifest = store.manifest;
|
|
3674
|
-
const posterUrl = currentStep.gifUrl;
|
|
3675
|
-
const avatarThumbnailUrl = manifest == null ? void 0 : manifest.avatarThumbnailUrl;
|
|
3676
|
-
this.managers.videoManager.showAudioFallbackOverlay(posterUrl, avatarThumbnailUrl);
|
|
3677
|
-
} else {
|
|
3678
|
-
this.managers.videoManager.hideAudioFallbackOverlay();
|
|
3679
|
-
}
|
|
3648
|
+
this.showOrHideAudioOverlay(currentStep, isAudioFallback);
|
|
3680
3649
|
this.managers.videoManager.loadVideo(videoUrl).then(() => {
|
|
3681
3650
|
this.managers.uiManager.hideError();
|
|
3682
3651
|
if (isAudioFallback) {
|
|
@@ -4134,12 +4103,11 @@ const _ManagerOrchestrator = class _ManagerOrchestrator {
|
|
|
4134
4103
|
// Store updater unsubscribe functions
|
|
4135
4104
|
__publicField(this, "uiUpdaterUnsubscribe", null);
|
|
4136
4105
|
__publicField(this, "eventUpdaterUnsubscribe", null);
|
|
4137
|
-
__publicField(this, "
|
|
4138
|
-
__publicField(this, "cursorAnimationVideoElement", null);
|
|
4139
|
-
__publicField(this, "cursorAnimationStepId", null);
|
|
4106
|
+
__publicField(this, "cursorScheduler");
|
|
4140
4107
|
// Initialization state
|
|
4141
4108
|
__publicField(this, "isInitialized", false);
|
|
4142
4109
|
this.managers = managers;
|
|
4110
|
+
this.cursorScheduler = new CursorAnimationScheduler(managers, "ManagerOrchestrator");
|
|
4143
4111
|
}
|
|
4144
4112
|
/**
|
|
4145
4113
|
* Set initialization state
|
|
@@ -4181,86 +4149,6 @@ const _ManagerOrchestrator = class _ManagerOrchestrator {
|
|
|
4181
4149
|
stepTimeoutUnsubscribe();
|
|
4182
4150
|
};
|
|
4183
4151
|
}
|
|
4184
|
-
/**
|
|
4185
|
-
* Cleans up the active cursor animation time listener
|
|
4186
|
-
* @param stepId - Optional step ID to verify we're cleaning up the right listener
|
|
4187
|
-
*/
|
|
4188
|
-
cleanupCursorAnimationListener(stepId) {
|
|
4189
|
-
if (stepId && this.cursorAnimationStepId && stepId !== this.cursorAnimationStepId) {
|
|
4190
|
-
log(`ManagerOrchestrator: Skipping cleanup - stepId mismatch (requested: ${stepId}, current: ${this.cursorAnimationStepId})`);
|
|
4191
|
-
return;
|
|
4192
|
-
}
|
|
4193
|
-
if (this.cursorAnimationListener && this.cursorAnimationVideoElement) {
|
|
4194
|
-
this.cursorAnimationVideoElement.removeEventListener("timeupdate", this.cursorAnimationListener);
|
|
4195
|
-
this.cursorAnimationListener = null;
|
|
4196
|
-
this.cursorAnimationVideoElement = null;
|
|
4197
|
-
this.cursorAnimationStepId = null;
|
|
4198
|
-
}
|
|
4199
|
-
}
|
|
4200
|
-
/**
|
|
4201
|
-
* Schedules a cursor animation to run either immediately or at a specific video time
|
|
4202
|
-
* Note: Works for both video and audio-only steps (audio files use the same video element)
|
|
4203
|
-
*/
|
|
4204
|
-
scheduleCursorAnimation(animation, stepId) {
|
|
4205
|
-
this.cleanupCursorAnimationListener();
|
|
4206
|
-
let showAtSeconds = animation.showAtSeconds ?? 0;
|
|
4207
|
-
if (typeof showAtSeconds !== "number" || !isFinite(showAtSeconds)) {
|
|
4208
|
-
showAtSeconds = 0;
|
|
4209
|
-
}
|
|
4210
|
-
if (showAtSeconds < 0) {
|
|
4211
|
-
showAtSeconds = 0;
|
|
4212
|
-
}
|
|
4213
|
-
if (showAtSeconds <= 0) {
|
|
4214
|
-
this.managers.cursorManager.animate(animation);
|
|
4215
|
-
} else {
|
|
4216
|
-
const videoElement = this.managers.videoManager.getVideoElement();
|
|
4217
|
-
if (!videoElement) {
|
|
4218
|
-
return;
|
|
4219
|
-
}
|
|
4220
|
-
let animationTriggered = false;
|
|
4221
|
-
let warningLogged = false;
|
|
4222
|
-
const timeUpdateHandler = () => {
|
|
4223
|
-
if (animationTriggered) {
|
|
4224
|
-
return;
|
|
4225
|
-
}
|
|
4226
|
-
const currentTime = videoElement.currentTime;
|
|
4227
|
-
const duration = videoElement.duration;
|
|
4228
|
-
if (duration && !isNaN(duration) && showAtSeconds > duration && !warningLogged) {
|
|
4229
|
-
warningLogged = true;
|
|
4230
|
-
animationTriggered = true;
|
|
4231
|
-
this.cleanupCursorAnimationListener();
|
|
4232
|
-
this.managers.cursorManager.animate(animation);
|
|
4233
|
-
return;
|
|
4234
|
-
}
|
|
4235
|
-
if (currentTime >= showAtSeconds) {
|
|
4236
|
-
animationTriggered = true;
|
|
4237
|
-
this.cleanupCursorAnimationListener();
|
|
4238
|
-
this.managers.cursorManager.animate(animation);
|
|
4239
|
-
}
|
|
4240
|
-
};
|
|
4241
|
-
const endedHandler = () => {
|
|
4242
|
-
if (!animationTriggered) {
|
|
4243
|
-
const store = getSaltfishStore();
|
|
4244
|
-
if (store.currentStepId !== stepId) {
|
|
4245
|
-
log(`ManagerOrchestrator: Video ended but step changed (was ${stepId}, now ${store.currentStepId}). Not triggering cursor animation.`);
|
|
4246
|
-
this.cleanupCursorAnimationListener(stepId);
|
|
4247
|
-
return;
|
|
4248
|
-
}
|
|
4249
|
-
videoElement.duration;
|
|
4250
|
-
animationTriggered = true;
|
|
4251
|
-
this.managers.cursorManager.animate(animation);
|
|
4252
|
-
this.cleanupCursorAnimationListener();
|
|
4253
|
-
}
|
|
4254
|
-
};
|
|
4255
|
-
this.cursorAnimationListener = () => {
|
|
4256
|
-
timeUpdateHandler();
|
|
4257
|
-
};
|
|
4258
|
-
this.cursorAnimationVideoElement = videoElement;
|
|
4259
|
-
this.cursorAnimationStepId = stepId;
|
|
4260
|
-
videoElement.addEventListener("timeupdate", this.cursorAnimationListener);
|
|
4261
|
-
videoElement.addEventListener("ended", endedHandler, { once: true });
|
|
4262
|
-
}
|
|
4263
|
-
}
|
|
4264
4152
|
/**
|
|
4265
4153
|
* Handle store state changes
|
|
4266
4154
|
*/
|
|
@@ -4292,7 +4180,7 @@ const _ManagerOrchestrator = class _ManagerOrchestrator {
|
|
|
4292
4180
|
const currentStep = manifest == null ? void 0 : manifest.steps.find((step) => step.id === store.currentStepId);
|
|
4293
4181
|
if ((currentStep == null ? void 0 : currentStep.cursorAnimations) && currentStep.cursorAnimations.length > 0) {
|
|
4294
4182
|
this.managers.cursorManager.setShouldShowCursor(true);
|
|
4295
|
-
this.
|
|
4183
|
+
this.cursorScheduler.schedule(currentStep.cursorAnimations[0], store.currentStepId || "unknown");
|
|
4296
4184
|
}
|
|
4297
4185
|
} else if (store.currentState === "completed" || store.currentState === "closing") {
|
|
4298
4186
|
this.cleanupPlaylist();
|
|
@@ -4311,7 +4199,7 @@ const _ManagerOrchestrator = class _ManagerOrchestrator {
|
|
|
4311
4199
|
*/
|
|
4312
4200
|
cleanupCurrentPlaylist() {
|
|
4313
4201
|
try {
|
|
4314
|
-
this.
|
|
4202
|
+
this.cursorScheduler.cleanup();
|
|
4315
4203
|
if (this.eventUpdaterUnsubscribe) {
|
|
4316
4204
|
this.eventUpdaterUnsubscribe();
|
|
4317
4205
|
this.eventUpdaterUnsubscribe = null;
|
|
@@ -4402,7 +4290,7 @@ const _ManagerOrchestrator = class _ManagerOrchestrator {
|
|
|
4402
4290
|
isMinimized: store.isMinimized,
|
|
4403
4291
|
manifestId: (_a = store.manifest) == null ? void 0 : _a.id
|
|
4404
4292
|
});
|
|
4405
|
-
this.
|
|
4293
|
+
this.cursorScheduler.cleanup();
|
|
4406
4294
|
if (this.uiUpdaterUnsubscribe) {
|
|
4407
4295
|
this.uiUpdaterUnsubscribe();
|
|
4408
4296
|
this.uiUpdaterUnsubscribe = null;
|
|
@@ -5552,27 +5440,37 @@ class VideoControlsUI {
|
|
|
5552
5440
|
event.preventDefault();
|
|
5553
5441
|
});
|
|
5554
5442
|
this.container.appendChild(this.ccButton);
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5443
|
+
this.attachProgressListeners(videoElement);
|
|
5444
|
+
}
|
|
5445
|
+
/**
|
|
5446
|
+
* Attaches progress-tracking listeners (seeking/seeked/timeupdate) to a video element.
|
|
5447
|
+
*/
|
|
5448
|
+
attachProgressListeners(video) {
|
|
5449
|
+
if (!video) {
|
|
5450
|
+
return;
|
|
5451
|
+
}
|
|
5452
|
+
video.addEventListener("seeking", this.handleSeeking);
|
|
5453
|
+
video.addEventListener("seeked", this.handleSeeked);
|
|
5454
|
+
video.addEventListener("timeupdate", this.handleDetailedTimeUpdate);
|
|
5455
|
+
}
|
|
5456
|
+
/**
|
|
5457
|
+
* Removes progress-tracking listeners from a video element.
|
|
5458
|
+
*/
|
|
5459
|
+
detachProgressListeners(video) {
|
|
5460
|
+
if (!video) {
|
|
5461
|
+
return;
|
|
5559
5462
|
}
|
|
5463
|
+
video.removeEventListener("seeking", this.handleSeeking);
|
|
5464
|
+
video.removeEventListener("seeked", this.handleSeeked);
|
|
5465
|
+
video.removeEventListener("timeupdate", this.handleDetailedTimeUpdate);
|
|
5560
5466
|
}
|
|
5561
5467
|
/**
|
|
5562
5468
|
* Updates the video element reference (used when swapping videos)
|
|
5563
5469
|
*/
|
|
5564
5470
|
updateVideoElement(videoElement) {
|
|
5565
|
-
|
|
5566
|
-
this.videoElement.removeEventListener("seeking", this.handleSeeking);
|
|
5567
|
-
this.videoElement.removeEventListener("seeked", this.handleSeeked);
|
|
5568
|
-
this.videoElement.removeEventListener("timeupdate", this.handleDetailedTimeUpdate);
|
|
5569
|
-
}
|
|
5471
|
+
this.detachProgressListeners(this.videoElement);
|
|
5570
5472
|
this.videoElement = videoElement;
|
|
5571
|
-
|
|
5572
|
-
videoElement.addEventListener("seeking", this.handleSeeking);
|
|
5573
|
-
videoElement.addEventListener("seeked", this.handleSeeked);
|
|
5574
|
-
videoElement.addEventListener("timeupdate", this.handleDetailedTimeUpdate);
|
|
5575
|
-
}
|
|
5473
|
+
this.attachProgressListeners(videoElement);
|
|
5576
5474
|
}
|
|
5577
5475
|
/**
|
|
5578
5476
|
* Resets controls for new video
|
|
@@ -5597,11 +5495,7 @@ class VideoControlsUI {
|
|
|
5597
5495
|
*/
|
|
5598
5496
|
destroy() {
|
|
5599
5497
|
this.stopProgressTracking();
|
|
5600
|
-
|
|
5601
|
-
this.videoElement.removeEventListener("seeking", this.handleSeeking);
|
|
5602
|
-
this.videoElement.removeEventListener("seeked", this.handleSeeked);
|
|
5603
|
-
this.videoElement.removeEventListener("timeupdate", this.handleDetailedTimeUpdate);
|
|
5604
|
-
}
|
|
5498
|
+
this.detachProgressListeners(this.videoElement);
|
|
5605
5499
|
if (this.controlsElement) {
|
|
5606
5500
|
const controlsConfig = this.deviceHandler.getControlsConfig();
|
|
5607
5501
|
if (controlsConfig.useTouch) {
|
|
@@ -6302,6 +6196,31 @@ class VideoManager {
|
|
|
6302
6196
|
getInactiveVideo() {
|
|
6303
6197
|
return this.activeVideoIndex === 0 ? this.nextVideo : this.currentVideo;
|
|
6304
6198
|
}
|
|
6199
|
+
/**
|
|
6200
|
+
* Runs a callback against each existing video element (current + next).
|
|
6201
|
+
*/
|
|
6202
|
+
forEachVideo(fn) {
|
|
6203
|
+
for (const video of [this.currentVideo, this.nextVideo]) {
|
|
6204
|
+
if (video) {
|
|
6205
|
+
fn(video);
|
|
6206
|
+
}
|
|
6207
|
+
}
|
|
6208
|
+
}
|
|
6209
|
+
/**
|
|
6210
|
+
* Applies the global mute state from the store to a single video element.
|
|
6211
|
+
*/
|
|
6212
|
+
applyStoreMuteState(video) {
|
|
6213
|
+
video.muted = getSaltfishStore().isMuted;
|
|
6214
|
+
}
|
|
6215
|
+
/**
|
|
6216
|
+
* Clears the pending autoplay fallback timeout, if any.
|
|
6217
|
+
*/
|
|
6218
|
+
clearAutoplayFallbackTimeout() {
|
|
6219
|
+
if (this.autoplayFallbackTimeout !== null) {
|
|
6220
|
+
window.clearTimeout(this.autoplayFallbackTimeout);
|
|
6221
|
+
this.autoplayFallbackTimeout = null;
|
|
6222
|
+
}
|
|
6223
|
+
}
|
|
6305
6224
|
/**
|
|
6306
6225
|
* Swaps between the two video elements
|
|
6307
6226
|
*/
|
|
@@ -6312,8 +6231,7 @@ class VideoManager {
|
|
|
6312
6231
|
return;
|
|
6313
6232
|
}
|
|
6314
6233
|
activeVideo.pause();
|
|
6315
|
-
|
|
6316
|
-
inactiveVideo.muted = store.isMuted;
|
|
6234
|
+
this.applyStoreMuteState(inactiveVideo);
|
|
6317
6235
|
activeVideo.classList.add("sf-hidden");
|
|
6318
6236
|
inactiveVideo.classList.remove("sf-hidden");
|
|
6319
6237
|
this.activeVideoIndex = this.activeVideoIndex === 0 ? 1 : 0;
|
|
@@ -6336,14 +6254,13 @@ class VideoManager {
|
|
|
6336
6254
|
if (this.controls) {
|
|
6337
6255
|
this.controls.reset90PercentTrigger();
|
|
6338
6256
|
}
|
|
6339
|
-
|
|
6340
|
-
activeVideo.muted = store.isMuted;
|
|
6257
|
+
this.applyStoreMuteState(activeVideo);
|
|
6341
6258
|
try {
|
|
6342
6259
|
if (this.controls) {
|
|
6343
6260
|
this.controls.reset();
|
|
6344
6261
|
}
|
|
6345
|
-
const
|
|
6346
|
-
const isPersistenceEnabled = ((_a =
|
|
6262
|
+
const store = getSaltfishStore();
|
|
6263
|
+
const isPersistenceEnabled = ((_a = store.playlistOptions) == null ? void 0 : _a.persistence) ?? true;
|
|
6347
6264
|
if (this.currentVideoUrl === url && activeVideo.src && (activeVideo.src === url || activeVideo.src.endsWith(url))) {
|
|
6348
6265
|
if (isPersistenceEnabled) {
|
|
6349
6266
|
const savedPosition = this.playbackPositions.get(url);
|
|
@@ -6481,8 +6398,7 @@ class VideoManager {
|
|
|
6481
6398
|
inactiveVideo.src = url;
|
|
6482
6399
|
inactiveVideo.load();
|
|
6483
6400
|
inactiveVideo.preload = "auto";
|
|
6484
|
-
|
|
6485
|
-
inactiveVideo.muted = store.isMuted;
|
|
6401
|
+
this.applyStoreMuteState(inactiveVideo);
|
|
6486
6402
|
} else {
|
|
6487
6403
|
fetch(url).then((response) => {
|
|
6488
6404
|
if (!response.ok) {
|
|
@@ -6639,10 +6555,7 @@ class VideoManager {
|
|
|
6639
6555
|
this.nextVideoUrl = "";
|
|
6640
6556
|
this.activeVideoIndex = 0;
|
|
6641
6557
|
this.hasUserInteracted = false;
|
|
6642
|
-
|
|
6643
|
-
window.clearTimeout(this.autoplayFallbackTimeout);
|
|
6644
|
-
this.autoplayFallbackTimeout = null;
|
|
6645
|
-
}
|
|
6558
|
+
this.clearAutoplayFallbackTimeout();
|
|
6646
6559
|
this.completionPolicy = "auto";
|
|
6647
6560
|
this.videoEndedCallback = null;
|
|
6648
6561
|
this.transcriptManager.reset();
|
|
@@ -6674,10 +6587,7 @@ class VideoManager {
|
|
|
6674
6587
|
}
|
|
6675
6588
|
this.transcriptManager.destroy();
|
|
6676
6589
|
this.hideAudioFallbackOverlay();
|
|
6677
|
-
|
|
6678
|
-
window.clearTimeout(this.autoplayFallbackTimeout);
|
|
6679
|
-
this.autoplayFallbackTimeout = null;
|
|
6680
|
-
}
|
|
6590
|
+
this.clearAutoplayFallbackTimeout();
|
|
6681
6591
|
if (this.deviceChangeCleanup) {
|
|
6682
6592
|
this.deviceChangeCleanup();
|
|
6683
6593
|
this.deviceChangeCleanup = null;
|
|
@@ -6696,31 +6606,19 @@ class VideoManager {
|
|
|
6696
6606
|
* Adds event listeners to the video elements
|
|
6697
6607
|
*/
|
|
6698
6608
|
addEventListeners() {
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
currentVideo.addEventListener("error", this.handleVideoError);
|
|
6704
|
-
}
|
|
6705
|
-
if (nextVideo) {
|
|
6706
|
-
nextVideo.addEventListener("ended", this.handleVideoEnded);
|
|
6707
|
-
nextVideo.addEventListener("error", this.handleVideoError);
|
|
6708
|
-
}
|
|
6609
|
+
this.forEachVideo((video) => {
|
|
6610
|
+
video.addEventListener("ended", this.handleVideoEnded);
|
|
6611
|
+
video.addEventListener("error", this.handleVideoError);
|
|
6612
|
+
});
|
|
6709
6613
|
}
|
|
6710
6614
|
/**
|
|
6711
6615
|
* Removes event listeners from the video elements
|
|
6712
6616
|
*/
|
|
6713
6617
|
removeEventListeners() {
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
currentVideo.removeEventListener("error", this.handleVideoError);
|
|
6719
|
-
}
|
|
6720
|
-
if (nextVideo) {
|
|
6721
|
-
nextVideo.removeEventListener("ended", this.handleVideoEnded);
|
|
6722
|
-
nextVideo.removeEventListener("error", this.handleVideoError);
|
|
6723
|
-
}
|
|
6618
|
+
this.forEachVideo((video) => {
|
|
6619
|
+
video.removeEventListener("ended", this.handleVideoEnded);
|
|
6620
|
+
video.removeEventListener("error", this.handleVideoError);
|
|
6621
|
+
});
|
|
6724
6622
|
}
|
|
6725
6623
|
/**
|
|
6726
6624
|
* Sets the muted state of both video elements
|
|
@@ -6810,10 +6708,8 @@ class VideoManager {
|
|
|
6810
6708
|
markUserInteraction() {
|
|
6811
6709
|
if (!this.hasUserInteracted) {
|
|
6812
6710
|
this.hasUserInteracted = true;
|
|
6813
|
-
if (this.autoplayFallbackTimeout !== null)
|
|
6814
|
-
|
|
6815
|
-
this.autoplayFallbackTimeout = null;
|
|
6816
|
-
}
|
|
6711
|
+
if (this.autoplayFallbackTimeout !== null) ;
|
|
6712
|
+
this.clearAutoplayFallbackTimeout();
|
|
6817
6713
|
}
|
|
6818
6714
|
}
|
|
6819
6715
|
/**
|
|
@@ -6828,10 +6724,7 @@ class VideoManager {
|
|
|
6828
6724
|
*/
|
|
6829
6725
|
resetUserInteraction() {
|
|
6830
6726
|
this.hasUserInteracted = false;
|
|
6831
|
-
|
|
6832
|
-
window.clearTimeout(this.autoplayFallbackTimeout);
|
|
6833
|
-
this.autoplayFallbackTimeout = null;
|
|
6834
|
-
}
|
|
6727
|
+
this.clearAutoplayFallbackTimeout();
|
|
6835
6728
|
}
|
|
6836
6729
|
/**
|
|
6837
6730
|
* Handles autoplay fallback click specifically for mobile Chrome
|
|
@@ -10492,7 +10385,8 @@ class TriggerManager {
|
|
|
10492
10385
|
}
|
|
10493
10386
|
const playlistData = watchedPlaylists && watchedPlaylists[playlistId];
|
|
10494
10387
|
const visitCount = (playlistData == null ? void 0 : playlistData.visitCount) ?? ((playlistData == null ? void 0 : playlistData.status) === "completed" || (playlistData == null ? void 0 : playlistData.status) === "dismissed" ? 1 : 0);
|
|
10495
|
-
|
|
10388
|
+
const effectiveVisits = visitCount + ((playlistData == null ? void 0 : playlistData.status) === "in_progress" ? 1 : 0);
|
|
10389
|
+
return effectiveVisits < maxVisits;
|
|
10496
10390
|
}
|
|
10497
10391
|
/**
|
|
10498
10392
|
* Normalizes a URL by removing trailing slash (unless it's the root path)
|
|
@@ -11519,7 +11413,6 @@ class PlaylistManager extends EventSubscriberManager {
|
|
|
11519
11413
|
*/
|
|
11520
11414
|
constructor(eventManager, storageManager2) {
|
|
11521
11415
|
super(eventManager);
|
|
11522
|
-
__publicField(this, "isUpdatingWatchedPlaylists", false);
|
|
11523
11416
|
__publicField(this, "playlistLoader");
|
|
11524
11417
|
__publicField(this, "storageManager");
|
|
11525
11418
|
this.playlistLoader = new PlaylistLoader();
|
|
@@ -11552,84 +11445,56 @@ class PlaylistManager extends EventSubscriberManager {
|
|
|
11552
11445
|
});
|
|
11553
11446
|
}
|
|
11554
11447
|
/**
|
|
11555
|
-
* Updates the watched playlist status
|
|
11448
|
+
* Updates the watched playlist status.
|
|
11449
|
+
*
|
|
11450
|
+
* The in-memory update is done first, atomically, via the store action so that
|
|
11451
|
+
* rapid status changes (per-step `in_progress` updates followed by the final
|
|
11452
|
+
* `completed` update) can never overwrite each other — this is what keeps the
|
|
11453
|
+
* `maxVisits`/"once" triggers correct on SPA navigation. Persistence (backend
|
|
11454
|
+
* for identified users, localStorage for anonymous) is a follow-up side effect
|
|
11455
|
+
* that mirrors the record the store just computed; it never gates the in-memory
|
|
11456
|
+
* update.
|
|
11457
|
+
*
|
|
11556
11458
|
* @param playlistId - ID of the playlist
|
|
11557
|
-
* @param status - New status
|
|
11459
|
+
* @param status - New status
|
|
11558
11460
|
* @param currentStepId - Optional current step ID
|
|
11559
11461
|
*/
|
|
11560
11462
|
async updateWatchedPlaylistStatus(playlistId, status, currentStepId) {
|
|
11561
|
-
var _a, _b, _c, _d, _e
|
|
11562
|
-
|
|
11463
|
+
var _a, _b, _c, _d, _e;
|
|
11464
|
+
const store = getSaltfishStore();
|
|
11465
|
+
store.updateWatchedPlaylist(playlistId, status, currentStepId);
|
|
11466
|
+
const record = (_b = (_a = store.userData) == null ? void 0 : _a.watchedPlaylists) == null ? void 0 : _b[playlistId];
|
|
11467
|
+
const isAnonymous = !((_c = store == null ? void 0 : store.config) == null ? void 0 : _c.token) || !((_d = store == null ? void 0 : store.user) == null ? void 0 : _d.id) || ((_e = store == null ? void 0 : store.user) == null ? void 0 : _e.__isAnonymous);
|
|
11468
|
+
if (isAnonymous) {
|
|
11469
|
+
if (record) {
|
|
11470
|
+
this.persistAnonymousWatchedPlaylist(playlistId, record);
|
|
11471
|
+
}
|
|
11563
11472
|
return;
|
|
11564
11473
|
}
|
|
11565
|
-
|
|
11474
|
+
const apiUrl = `https://player.saltfish.ai/clients/${store.config.token}/users/${store.user.id}/playlists/${playlistId}`;
|
|
11566
11475
|
try {
|
|
11567
|
-
const
|
|
11568
|
-
|
|
11569
|
-
|
|
11570
|
-
|
|
11571
|
-
const previousStatus = existingPlaylistData == null ? void 0 : existingPlaylistData.status;
|
|
11572
|
-
const currentVisitCount = (existingPlaylistData == null ? void 0 : existingPlaylistData.visitCount) ?? 0;
|
|
11573
|
-
const shouldIncrementVisitCount = (status === "completed" || status === "dismissed") && previousStatus !== status;
|
|
11574
|
-
const updatedPlaylistData = {
|
|
11575
|
-
status,
|
|
11576
|
-
// Don't save currentStepId for completed playlists - they should restart from beginning
|
|
11577
|
-
currentStepId: status === "completed" ? null : currentStepId || store.currentStepId || null,
|
|
11578
|
-
timestamp: Date.now(),
|
|
11579
|
-
// Use timestamp for consistency with checkAndResumeInProgressPlaylist
|
|
11580
|
-
lastProgressAt: Date.now(),
|
|
11581
|
-
// Keep for backward compatibility
|
|
11582
|
-
visitCount: shouldIncrementVisitCount ? currentVisitCount + 1 : currentVisitCount
|
|
11583
|
-
};
|
|
11584
|
-
const updatedWatchedPlaylists = {
|
|
11585
|
-
...currentWatchedPlaylists,
|
|
11586
|
-
[playlistId]: updatedPlaylistData
|
|
11587
|
-
};
|
|
11588
|
-
store.setUserData({
|
|
11589
|
-
...currentUserData,
|
|
11590
|
-
watchedPlaylists: updatedWatchedPlaylists
|
|
11476
|
+
const response = await fetch(apiUrl, {
|
|
11477
|
+
method: "POST",
|
|
11478
|
+
headers: { "Content-Type": "application/json" },
|
|
11479
|
+
body: JSON.stringify({ status, currentStepId: (record == null ? void 0 : record.currentStepId) ?? null })
|
|
11591
11480
|
});
|
|
11592
|
-
|
|
11593
|
-
|
|
11594
|
-
log(`PlaylistManager: Cannot update backend - token: ${!!((_d = store == null ? void 0 : store.config) == null ? void 0 : _d.token)}, userId: ${!!((_e = store == null ? void 0 : store.user) == null ? void 0 : _e.id)}, anonymous: ${(_f = store == null ? void 0 : store.user) == null ? void 0 : _f.__isAnonymous}`);
|
|
11595
|
-
this.updateAnonymousUserWatchedPlaylists(playlistId, status, currentStepId || store.currentStepId || null);
|
|
11596
|
-
return;
|
|
11597
|
-
}
|
|
11598
|
-
const apiUrl = `https://player.saltfish.ai/clients/${store.config.token}/users/${store.user.id}/playlists/${playlistId}`;
|
|
11599
|
-
log(`PlaylistManager: Making API call to update status - URL: ${apiUrl}, Status: ${status}`);
|
|
11600
|
-
try {
|
|
11601
|
-
const response = await fetch(apiUrl, {
|
|
11602
|
-
method: "POST",
|
|
11603
|
-
headers: {
|
|
11604
|
-
"Content-Type": "application/json"
|
|
11605
|
-
},
|
|
11606
|
-
body: JSON.stringify({
|
|
11607
|
-
status,
|
|
11608
|
-
currentStepId: currentStepId || store.currentStepId || null
|
|
11609
|
-
})
|
|
11610
|
-
});
|
|
11611
|
-
if (!response.ok) {
|
|
11612
|
-
throw new Error(`Failed to update watched playlist status: ${response.statusText} (${response.status})`);
|
|
11613
|
-
}
|
|
11614
|
-
log(`PlaylistManager: Successfully updated watched playlist status via API for ${playlistId} to '${status}'`);
|
|
11615
|
-
} catch (error2) {
|
|
11616
|
-
console.error(`PlaylistManager: Error updating watched playlist status for ${playlistId}:`, error2);
|
|
11481
|
+
if (!response.ok) {
|
|
11482
|
+
throw new Error(`Failed to update watched playlist status: ${response.statusText} (${response.status})`);
|
|
11617
11483
|
}
|
|
11618
|
-
|
|
11619
|
-
|
|
11484
|
+
log(`PlaylistManager: Successfully updated watched playlist status via API for ${playlistId} to '${status}'`);
|
|
11485
|
+
} catch (error2) {
|
|
11486
|
+
console.error(`PlaylistManager: Error updating watched playlist status for ${playlistId}:`, error2);
|
|
11620
11487
|
}
|
|
11621
11488
|
}
|
|
11622
11489
|
/**
|
|
11623
|
-
*
|
|
11624
|
-
*
|
|
11625
|
-
* @param status - New status ('in_progress', 'completed', or 'dismissed')
|
|
11626
|
-
* @param currentStepId - Optional current step ID
|
|
11490
|
+
* Mirrors a computed watched-playlist record into the anonymous user's
|
|
11491
|
+
* localStorage so it survives a full page reload.
|
|
11627
11492
|
*/
|
|
11628
|
-
|
|
11493
|
+
persistAnonymousWatchedPlaylist(playlistId, record) {
|
|
11629
11494
|
if (typeof window === "undefined") {
|
|
11630
11495
|
return;
|
|
11631
11496
|
}
|
|
11632
|
-
|
|
11497
|
+
const anonymousUserData = this.storageManager.getAnonymousUserData() || {
|
|
11633
11498
|
userId: "anonymous",
|
|
11634
11499
|
userData: {},
|
|
11635
11500
|
watchedPlaylists: {},
|
|
@@ -11638,21 +11503,10 @@ class PlaylistManager extends EventSubscriberManager {
|
|
|
11638
11503
|
if (!anonymousUserData.watchedPlaylists) {
|
|
11639
11504
|
anonymousUserData.watchedPlaylists = {};
|
|
11640
11505
|
}
|
|
11641
|
-
|
|
11642
|
-
const previousStatus = existingPlaylistData == null ? void 0 : existingPlaylistData.status;
|
|
11643
|
-
const currentVisitCount = (existingPlaylistData == null ? void 0 : existingPlaylistData.visitCount) ?? 0;
|
|
11644
|
-
const shouldIncrementVisitCount = (status === "completed" || status === "dismissed") && previousStatus !== status;
|
|
11645
|
-
anonymousUserData.watchedPlaylists[playlistId] = {
|
|
11646
|
-
status,
|
|
11647
|
-
currentStepId: currentStepId || null,
|
|
11648
|
-
timestamp: Date.now(),
|
|
11649
|
-
// Use timestamp for consistency with checkAndResumeInProgressPlaylist
|
|
11650
|
-
lastProgressAt: Date.now(),
|
|
11651
|
-
// Keep for backward compatibility
|
|
11652
|
-
visitCount: shouldIncrementVisitCount ? currentVisitCount + 1 : currentVisitCount
|
|
11653
|
-
};
|
|
11506
|
+
anonymousUserData.watchedPlaylists[playlistId] = record;
|
|
11654
11507
|
anonymousUserData.timestamp = Date.now();
|
|
11655
|
-
this.storageManager.setAnonymousUserData(anonymousUserData);
|
|
11508
|
+
const success = this.storageManager.setAnonymousUserData(anonymousUserData);
|
|
11509
|
+
log(`PlaylistManager: ${success ? "Updated" : "Failed to update"} anonymous user localStorage for playlist ${playlistId} with status ${record.status}`);
|
|
11656
11510
|
}
|
|
11657
11511
|
/**
|
|
11658
11512
|
* Loads a playlist manifest and sets up the store
|
|
@@ -12273,10 +12127,10 @@ class UIManager {
|
|
|
12273
12127
|
}
|
|
12274
12128
|
const token = (_a2 = currentStore.config) == null ? void 0 : _a2.token;
|
|
12275
12129
|
if (token) {
|
|
12276
|
-
window.open(`https://www.saltfish.ai
|
|
12130
|
+
window.open(`https://www.saltfish.ai/?utm_source=player&utm_medium=logo&clientId=${token}`, "_blank");
|
|
12277
12131
|
} else {
|
|
12278
12132
|
console.warn("UIManager: No token available, falling back to saltfish.ai homepage");
|
|
12279
|
-
window.open("https://www.saltfish.ai
|
|
12133
|
+
window.open("https://www.saltfish.ai/?utm_source=player&utm_medium=logo", "_blank");
|
|
12280
12134
|
}
|
|
12281
12135
|
});
|
|
12282
12136
|
}
|
|
@@ -12901,7 +12755,7 @@ const SaltfishPlayer$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.de
|
|
|
12901
12755
|
__proto__: null,
|
|
12902
12756
|
SaltfishPlayer
|
|
12903
12757
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
12904
|
-
const version = "0.3.
|
|
12758
|
+
const version = "0.3.91";
|
|
12905
12759
|
const packageJson = {
|
|
12906
12760
|
version
|
|
12907
12761
|
};
|