saltfish 0.3.60 → 0.3.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/services/PlayerInitializationService.d.ts +14 -0
- package/dist/core/services/PlayerInitializationService.d.ts.map +1 -1
- package/dist/loaders/PlaylistLoader.d.ts +15 -0
- package/dist/loaders/PlaylistLoader.d.ts.map +1 -1
- package/dist/managers/StorageManager.d.ts +13 -13
- package/dist/managers/StorageManager.d.ts.map +1 -1
- package/dist/managers/TransitionManager.d.ts +19 -0
- package/dist/managers/TransitionManager.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 +246 -83
- package/dist/saltfish-playlist-player.umd.js +1 -1
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/constants.d.ts +2 -0
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/deviceDetection.d.ts +0 -9
- package/dist/utils/deviceDetection.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -955,7 +955,8 @@ const playerStateMachineConfig = {
|
|
|
955
955
|
const STORAGE_KEYS = {
|
|
956
956
|
PROGRESS: "saltfish_progress",
|
|
957
957
|
SESSION: "saltfish_session",
|
|
958
|
-
ANONYMOUS_USER: "saltfish_anonymous_user_data"
|
|
958
|
+
ANONYMOUS_USER: "saltfish_anonymous_user_data",
|
|
959
|
+
PENDING_NAVIGATION: "saltfish_pending_navigation"
|
|
959
960
|
};
|
|
960
961
|
const API = {
|
|
961
962
|
BASE_URL: "https://player.saltfish.ai",
|
|
@@ -1155,15 +1156,30 @@ const _StorageManager = class _StorageManager {
|
|
|
1155
1156
|
this.safeClearItem(STORAGE_KEYS.ANONYMOUS_USER);
|
|
1156
1157
|
}
|
|
1157
1158
|
// =============================================================================
|
|
1158
|
-
//
|
|
1159
|
+
// Pending Navigation Methods (Cross-page URL transitions)
|
|
1159
1160
|
// =============================================================================
|
|
1160
1161
|
/**
|
|
1161
|
-
* Get
|
|
1162
|
-
*
|
|
1162
|
+
* Get pending navigation data for cross-page URL transitions
|
|
1163
|
+
* Used when a step has a url-path transition and user navigates causing hard refresh
|
|
1163
1164
|
*/
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1165
|
+
getPendingNavigation() {
|
|
1166
|
+
return this.safeGetItem(STORAGE_KEYS.PENDING_NAVIGATION);
|
|
1167
|
+
}
|
|
1168
|
+
/**
|
|
1169
|
+
* Set pending navigation data
|
|
1170
|
+
* Called when setting up a url-path transition to enable resuming after hard refresh
|
|
1171
|
+
* @param data - The pending navigation data to save
|
|
1172
|
+
*/
|
|
1173
|
+
setPendingNavigation(data) {
|
|
1174
|
+
log(`[StorageManager] Saving pending navigation to step ${data.nextStepId} for pattern ${data.urlPattern}`);
|
|
1175
|
+
return this.safeSetItem(STORAGE_KEYS.PENDING_NAVIGATION, data);
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Clear pending navigation data
|
|
1179
|
+
* Called after successful transition or when navigation is no longer valid
|
|
1180
|
+
*/
|
|
1181
|
+
clearPendingNavigation() {
|
|
1182
|
+
this.safeClearItem(STORAGE_KEYS.PENDING_NAVIGATION);
|
|
1167
1183
|
}
|
|
1168
1184
|
// =============================================================================
|
|
1169
1185
|
// Utility Methods
|
|
@@ -1175,33 +1191,7 @@ const _StorageManager = class _StorageManager {
|
|
|
1175
1191
|
this.clearProgress();
|
|
1176
1192
|
this.clearSession();
|
|
1177
1193
|
this.clearAnonymousUserData();
|
|
1178
|
-
|
|
1179
|
-
/**
|
|
1180
|
-
* Get storage availability status
|
|
1181
|
-
*/
|
|
1182
|
-
isStorageAvailable() {
|
|
1183
|
-
return this.isLocalStorageAvailable;
|
|
1184
|
-
}
|
|
1185
|
-
/**
|
|
1186
|
-
* Get storage usage information (if available)
|
|
1187
|
-
*/
|
|
1188
|
-
getStorageInfo() {
|
|
1189
|
-
const keys = [];
|
|
1190
|
-
if (this.isLocalStorageAvailable) {
|
|
1191
|
-
try {
|
|
1192
|
-
for (let i = 0; i < localStorage.length; i++) {
|
|
1193
|
-
const key = localStorage.key(i);
|
|
1194
|
-
if (key && key.startsWith("saltfish_")) {
|
|
1195
|
-
keys.push(key);
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
} catch (error2) {
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
return {
|
|
1202
|
-
available: this.isLocalStorageAvailable,
|
|
1203
|
-
keys
|
|
1204
|
-
};
|
|
1194
|
+
this.clearPendingNavigation();
|
|
1205
1195
|
}
|
|
1206
1196
|
};
|
|
1207
1197
|
__publicField(_StorageManager, "instance", null);
|
|
@@ -1874,6 +1864,47 @@ class ShareLinkService {
|
|
|
1874
1864
|
}
|
|
1875
1865
|
}
|
|
1876
1866
|
}
|
|
1867
|
+
const ANALYTICS = {
|
|
1868
|
+
/** Interval for flushing analytics events to the backend (30 seconds) */
|
|
1869
|
+
FLUSH_INTERVAL_MS: 3e4
|
|
1870
|
+
};
|
|
1871
|
+
const TIMING = {
|
|
1872
|
+
// Polling and updates
|
|
1873
|
+
/** Video progress polling interval (50ms) */
|
|
1874
|
+
VIDEO_PROGRESS_POLL_INTERVAL: 50,
|
|
1875
|
+
/** Cursor position update throttle interval (100ms) */
|
|
1876
|
+
CURSOR_UPDATE_THROTTLE: 100,
|
|
1877
|
+
/** Delay for state processing operations (100ms) */
|
|
1878
|
+
STATE_PROCESSING_DELAY_MS: 100,
|
|
1879
|
+
// Analytics
|
|
1880
|
+
/** Analytics event flush interval (30 seconds) */
|
|
1881
|
+
ANALYTICS_FLUSH_INTERVAL: 3e4,
|
|
1882
|
+
// Timeouts
|
|
1883
|
+
/** User data loading timeout (5 seconds) */
|
|
1884
|
+
USER_DATA_TIMEOUT: 5e3,
|
|
1885
|
+
/** Step timeout - player will be destroyed if user stays on same step (120 seconds) */
|
|
1886
|
+
STEP_TIMEOUT: 12e4,
|
|
1887
|
+
/** Retry delay for failed operations (0.5 seconds) */
|
|
1888
|
+
RETRY_DELAY_MS: 500,
|
|
1889
|
+
// DOM and cursor operations
|
|
1890
|
+
/** Delay for DOM stabilization before cursor operations (0.5 seconds) */
|
|
1891
|
+
DOM_STABILIZATION_DELAY_MS: 500,
|
|
1892
|
+
/** Default cursor animation distance in pixels */
|
|
1893
|
+
CURSOR_DEFAULT_DISTANCE: 100,
|
|
1894
|
+
// URL monitoring
|
|
1895
|
+
/** Interval for checking URL path changes (5 seconds) */
|
|
1896
|
+
URL_PATH_CHECK_INTERVAL_MS: 5e3,
|
|
1897
|
+
// Session persistence
|
|
1898
|
+
/** Session expiry time (30 minutes) */
|
|
1899
|
+
SESSION_EXPIRY: 30 * 60 * 1e3,
|
|
1900
|
+
// Cross-page navigation
|
|
1901
|
+
/** Pending navigation expiry time (60 seconds) - longer than normal 6s rule for URL transitions */
|
|
1902
|
+
PENDING_NAVIGATION_EXPIRY: 60 * 1e3
|
|
1903
|
+
};
|
|
1904
|
+
const THRESHOLDS = {
|
|
1905
|
+
/** Minimum scroll distance in pixels to trigger scroll events */
|
|
1906
|
+
SCROLL_THRESHOLD_PX: 10
|
|
1907
|
+
};
|
|
1877
1908
|
class PlayerInitializationService {
|
|
1878
1909
|
constructor(managers) {
|
|
1879
1910
|
__publicField(this, "managers");
|
|
@@ -2038,7 +2069,11 @@ class PlayerInitializationService {
|
|
|
2038
2069
|
if (this.userManagementService) {
|
|
2039
2070
|
this.userManagementService.resolveUserDataLoaded();
|
|
2040
2071
|
}
|
|
2041
|
-
const
|
|
2072
|
+
const resumedFromPendingNav = await this.checkAndResumeFromPendingNavigation();
|
|
2073
|
+
if (resumedFromPendingNav) {
|
|
2074
|
+
log("[PlayerInitializationService.fetchUserData] Resumed from pending URL navigation, skipping other checks");
|
|
2075
|
+
}
|
|
2076
|
+
const resumedPlaylist = resumedFromPendingNav || await this.checkAndResumeInProgressPlaylist(data.watchedPlaylists || {});
|
|
2042
2077
|
if (!resumedPlaylist) {
|
|
2043
2078
|
const shareData = await this.shareLinkService.shouldAutoStartSharePlaylist();
|
|
2044
2079
|
if (shareData && this.playlistOrchestrator) {
|
|
@@ -2124,8 +2159,12 @@ class PlayerInitializationService {
|
|
|
2124
2159
|
watchedPlaylists: anonymousUserData.watchedPlaylists || {}
|
|
2125
2160
|
}
|
|
2126
2161
|
});
|
|
2162
|
+
const resumedFromPendingNav = await this.checkAndResumeFromPendingNavigation();
|
|
2163
|
+
if (resumedFromPendingNav) {
|
|
2164
|
+
log("[PlayerInitializationService.loadAnonymousUserData] Resumed from pending URL navigation, skipping other checks");
|
|
2165
|
+
}
|
|
2127
2166
|
const watchedPlaylists = anonymousUserData.watchedPlaylists || {};
|
|
2128
|
-
const resumedPlaylist = await this.checkAndResumeInProgressPlaylist(watchedPlaylists);
|
|
2167
|
+
const resumedPlaylist = resumedFromPendingNav || await this.checkAndResumeInProgressPlaylist(watchedPlaylists);
|
|
2129
2168
|
if (!resumedPlaylist) {
|
|
2130
2169
|
const shareData = await this.shareLinkService.shouldAutoStartSharePlaylist();
|
|
2131
2170
|
if (shareData && this.playlistOrchestrator) {
|
|
@@ -2197,6 +2236,60 @@ class PlayerInitializationService {
|
|
|
2197
2236
|
}
|
|
2198
2237
|
return false;
|
|
2199
2238
|
}
|
|
2239
|
+
/**
|
|
2240
|
+
* Check for pending navigation from cross-page URL transitions and auto-start the playlist
|
|
2241
|
+
* This handles the case where user navigated to a new page (hard refresh)
|
|
2242
|
+
* and we need to resume from the step that was waiting for that URL
|
|
2243
|
+
* @returns true if a playlist was started from pending navigation
|
|
2244
|
+
*/
|
|
2245
|
+
async checkAndResumeFromPendingNavigation() {
|
|
2246
|
+
const pending = this.managers.storageManager.getPendingNavigation();
|
|
2247
|
+
if (!pending) {
|
|
2248
|
+
return false;
|
|
2249
|
+
}
|
|
2250
|
+
const ageMs = Date.now() - pending.timestamp;
|
|
2251
|
+
if (ageMs > TIMING.PENDING_NAVIGATION_EXPIRY) {
|
|
2252
|
+
this.managers.storageManager.clearPendingNavigation();
|
|
2253
|
+
return false;
|
|
2254
|
+
}
|
|
2255
|
+
if (!this.isURLPathMatch(pending.urlPattern)) {
|
|
2256
|
+
log(`[PlayerInitializationService.checkAndResumeFromPendingNavigation] URL doesn't match pending pattern '${pending.urlPattern}'`);
|
|
2257
|
+
this.managers.storageManager.clearPendingNavigation();
|
|
2258
|
+
return false;
|
|
2259
|
+
}
|
|
2260
|
+
log(`[PlayerInitializationService.checkAndResumeFromPendingNavigation] URL matches! Starting playlist ${pending.playlistId} at step ${pending.nextStepId}`);
|
|
2261
|
+
this.managers.storageManager.clearPendingNavigation();
|
|
2262
|
+
try {
|
|
2263
|
+
if (this.playlistOrchestrator) {
|
|
2264
|
+
await this.playlistOrchestrator.startPlaylist(pending.playlistId, {
|
|
2265
|
+
startNodeId: pending.nextStepId
|
|
2266
|
+
});
|
|
2267
|
+
log(`[PlayerInitializationService.checkAndResumeFromPendingNavigation] Successfully started playlist from pending navigation`);
|
|
2268
|
+
return true;
|
|
2269
|
+
}
|
|
2270
|
+
} catch (error2) {
|
|
2271
|
+
return false;
|
|
2272
|
+
}
|
|
2273
|
+
return false;
|
|
2274
|
+
}
|
|
2275
|
+
/**
|
|
2276
|
+
* Checks if the current URL path matches a pattern
|
|
2277
|
+
* Uses the same logic as TransitionManager for consistency
|
|
2278
|
+
* @param pattern - The URL pattern to match (supports wildcards)
|
|
2279
|
+
* @returns true if the current URL matches the pattern
|
|
2280
|
+
*/
|
|
2281
|
+
isURLPathMatch(pattern) {
|
|
2282
|
+
if (!pattern || typeof window === "undefined") {
|
|
2283
|
+
return false;
|
|
2284
|
+
}
|
|
2285
|
+
const currentUrl = window.location.href;
|
|
2286
|
+
const currentPath = window.location.pathname;
|
|
2287
|
+
const escapedPattern = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2288
|
+
const regexPattern = escapedPattern.replace(/\\\*/g, ".*");
|
|
2289
|
+
const regex = new RegExp(regexPattern);
|
|
2290
|
+
const match = regex.test(currentUrl) || regex.test(currentPath);
|
|
2291
|
+
return match;
|
|
2292
|
+
}
|
|
2200
2293
|
/**
|
|
2201
2294
|
* Get or create persistent anonymous user ID
|
|
2202
2295
|
*/
|
|
@@ -4283,7 +4376,7 @@ class DeviceDetector {
|
|
|
4283
4376
|
return this.cachedDeviceInfo;
|
|
4284
4377
|
}
|
|
4285
4378
|
const userAgent = typeof navigator !== "undefined" ? navigator.userAgent : "";
|
|
4286
|
-
const
|
|
4379
|
+
const isTouchDevice = this.detectTouchSupport();
|
|
4287
4380
|
const { width, height } = this.getScreenDimensions();
|
|
4288
4381
|
const mobileRegex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile|mobile|CriOS/i;
|
|
4289
4382
|
const tabletRegex = /iPad|Android(?!.*Mobile)|Tablet|tablet/i;
|
|
@@ -4292,14 +4385,14 @@ class DeviceDetector {
|
|
|
4292
4385
|
const screenSize = this.getScreenSize(width, height);
|
|
4293
4386
|
const isMobileByScreen = screenSize === "small" && Math.min(width, height) < 768;
|
|
4294
4387
|
const isTabletByScreen = screenSize === "medium" && !isMobileByScreen;
|
|
4295
|
-
const
|
|
4296
|
-
const
|
|
4297
|
-
const
|
|
4388
|
+
const isMobile = isMobileUserAgent || isMobileByScreen && isTouchDevice;
|
|
4389
|
+
const isTablet = isTabletUserAgent || isTabletByScreen && isTouchDevice && !isMobile;
|
|
4390
|
+
const isDesktop = !isMobile && !isTablet;
|
|
4298
4391
|
const deviceInfo = {
|
|
4299
|
-
isMobile
|
|
4300
|
-
isTablet
|
|
4301
|
-
isDesktop
|
|
4302
|
-
isTouchDevice
|
|
4392
|
+
isMobile,
|
|
4393
|
+
isTablet,
|
|
4394
|
+
isDesktop,
|
|
4395
|
+
isTouchDevice,
|
|
4303
4396
|
screenSize,
|
|
4304
4397
|
orientation: this.detectOrientation(),
|
|
4305
4398
|
userAgent
|
|
@@ -6648,44 +6741,6 @@ class VideoManager {
|
|
|
6648
6741
|
this.soundbarElement = null;
|
|
6649
6742
|
}
|
|
6650
6743
|
}
|
|
6651
|
-
const ANALYTICS = {
|
|
6652
|
-
/** Interval for flushing analytics events to the backend (30 seconds) */
|
|
6653
|
-
FLUSH_INTERVAL_MS: 3e4
|
|
6654
|
-
};
|
|
6655
|
-
const TIMING = {
|
|
6656
|
-
// Polling and updates
|
|
6657
|
-
/** Video progress polling interval (50ms) */
|
|
6658
|
-
VIDEO_PROGRESS_POLL_INTERVAL: 50,
|
|
6659
|
-
/** Cursor position update throttle interval (100ms) */
|
|
6660
|
-
CURSOR_UPDATE_THROTTLE: 100,
|
|
6661
|
-
/** Delay for state processing operations (100ms) */
|
|
6662
|
-
STATE_PROCESSING_DELAY_MS: 100,
|
|
6663
|
-
// Analytics
|
|
6664
|
-
/** Analytics event flush interval (30 seconds) */
|
|
6665
|
-
ANALYTICS_FLUSH_INTERVAL: 3e4,
|
|
6666
|
-
// Timeouts
|
|
6667
|
-
/** User data loading timeout (5 seconds) */
|
|
6668
|
-
USER_DATA_TIMEOUT: 5e3,
|
|
6669
|
-
/** Step timeout - player will be destroyed if user stays on same step (120 seconds) */
|
|
6670
|
-
STEP_TIMEOUT: 12e4,
|
|
6671
|
-
/** Retry delay for failed operations (0.5 seconds) */
|
|
6672
|
-
RETRY_DELAY_MS: 500,
|
|
6673
|
-
// DOM and cursor operations
|
|
6674
|
-
/** Delay for DOM stabilization before cursor operations (0.5 seconds) */
|
|
6675
|
-
DOM_STABILIZATION_DELAY_MS: 500,
|
|
6676
|
-
/** Default cursor animation distance in pixels */
|
|
6677
|
-
CURSOR_DEFAULT_DISTANCE: 100,
|
|
6678
|
-
// URL monitoring
|
|
6679
|
-
/** Interval for checking URL path changes (5 seconds) */
|
|
6680
|
-
URL_PATH_CHECK_INTERVAL_MS: 5e3,
|
|
6681
|
-
// Session persistence
|
|
6682
|
-
/** Session expiry time (30 minutes) */
|
|
6683
|
-
SESSION_EXPIRY: 30 * 60 * 1e3
|
|
6684
|
-
};
|
|
6685
|
-
const THRESHOLDS = {
|
|
6686
|
-
/** Minimum scroll distance in pixels to trigger scroll events */
|
|
6687
|
-
SCROLL_THRESHOLD_PX: 10
|
|
6688
|
-
};
|
|
6689
6744
|
const DEFAULT_CONFIG = {
|
|
6690
6745
|
tolerance: 0.3,
|
|
6691
6746
|
// 30% size difference allowed (70% match required)
|
|
@@ -9249,6 +9304,8 @@ class TransitionManager {
|
|
|
9249
9304
|
__publicField(this, "isStateMachineValidating", false);
|
|
9250
9305
|
// Reference to TriggerManager for coordinating playlist triggers
|
|
9251
9306
|
__publicField(this, "triggerManager", null);
|
|
9307
|
+
// beforeunload handler for cross-page URL transitions
|
|
9308
|
+
__publicField(this, "beforeUnloadHandler", null);
|
|
9252
9309
|
/**
|
|
9253
9310
|
* Handles URL changes by checking active URL path transitions and playlist triggers
|
|
9254
9311
|
*/
|
|
@@ -9285,6 +9342,7 @@ class TransitionManager {
|
|
|
9285
9342
|
}
|
|
9286
9343
|
const { pattern, nextStepId } = transition.data;
|
|
9287
9344
|
if (this.isURLPathMatch(pattern)) {
|
|
9345
|
+
StorageManager.getInstance().clearPendingNavigation();
|
|
9288
9346
|
this.triggerTransition(nextStepId);
|
|
9289
9347
|
break;
|
|
9290
9348
|
}
|
|
@@ -9475,6 +9533,8 @@ class TransitionManager {
|
|
|
9475
9533
|
}
|
|
9476
9534
|
const pathPattern = transition.target;
|
|
9477
9535
|
const nextStepId = transition.nextStep;
|
|
9536
|
+
this.savePendingNavigation(pathPattern, nextStepId);
|
|
9537
|
+
this.setupBeforeUnloadHandler(pathPattern, nextStepId);
|
|
9478
9538
|
const initialMatch = this.isURLPathMatch(pathPattern);
|
|
9479
9539
|
if (initialMatch) {
|
|
9480
9540
|
this.triggerTransition(nextStepId);
|
|
@@ -9501,6 +9561,7 @@ class TransitionManager {
|
|
|
9501
9561
|
const match = this.isURLPathMatch(pathPattern);
|
|
9502
9562
|
if (match) {
|
|
9503
9563
|
clearInterval(intervalId);
|
|
9564
|
+
StorageManager.getInstance().clearPendingNavigation();
|
|
9504
9565
|
this.triggerTransition(nextStepId);
|
|
9505
9566
|
}
|
|
9506
9567
|
}, TIMING.URL_PATH_CHECK_INTERVAL_MS);
|
|
@@ -9517,6 +9578,47 @@ class TransitionManager {
|
|
|
9517
9578
|
}
|
|
9518
9579
|
});
|
|
9519
9580
|
}
|
|
9581
|
+
/**
|
|
9582
|
+
* Saves pending navigation data for cross-page URL transitions
|
|
9583
|
+
* This enables resuming from the correct step after a hard page refresh
|
|
9584
|
+
* @param urlPattern - The URL pattern to match
|
|
9585
|
+
* @param nextStepId - The step ID to navigate to
|
|
9586
|
+
*/
|
|
9587
|
+
savePendingNavigation(urlPattern, nextStepId) {
|
|
9588
|
+
const store = getSaltfishStore();
|
|
9589
|
+
if (!store.manifest) {
|
|
9590
|
+
return;
|
|
9591
|
+
}
|
|
9592
|
+
const storageManager2 = StorageManager.getInstance();
|
|
9593
|
+
storageManager2.setPendingNavigation({
|
|
9594
|
+
playlistId: store.manifest.id,
|
|
9595
|
+
nextStepId,
|
|
9596
|
+
urlPattern,
|
|
9597
|
+
timestamp: Date.now()
|
|
9598
|
+
});
|
|
9599
|
+
}
|
|
9600
|
+
/**
|
|
9601
|
+
* Sets up beforeunload handler as backup for cross-page URL transitions
|
|
9602
|
+
* This ensures pending navigation is saved even if the page unloads unexpectedly
|
|
9603
|
+
* @param urlPattern - The URL pattern to match
|
|
9604
|
+
* @param nextStepId - The step ID to navigate to
|
|
9605
|
+
*/
|
|
9606
|
+
setupBeforeUnloadHandler(urlPattern, nextStepId) {
|
|
9607
|
+
this.removeBeforeUnloadHandler();
|
|
9608
|
+
this.beforeUnloadHandler = () => {
|
|
9609
|
+
this.savePendingNavigation(urlPattern, nextStepId);
|
|
9610
|
+
};
|
|
9611
|
+
window.addEventListener("beforeunload", this.beforeUnloadHandler);
|
|
9612
|
+
}
|
|
9613
|
+
/**
|
|
9614
|
+
* Removes the beforeunload handler if it exists
|
|
9615
|
+
*/
|
|
9616
|
+
removeBeforeUnloadHandler() {
|
|
9617
|
+
if (this.beforeUnloadHandler) {
|
|
9618
|
+
window.removeEventListener("beforeunload", this.beforeUnloadHandler);
|
|
9619
|
+
this.beforeUnloadHandler = null;
|
|
9620
|
+
}
|
|
9621
|
+
}
|
|
9520
9622
|
/**
|
|
9521
9623
|
* Checks if the current URL path matches a pattern
|
|
9522
9624
|
*/
|
|
@@ -9626,6 +9728,7 @@ class TransitionManager {
|
|
|
9626
9728
|
});
|
|
9627
9729
|
this.activeTransitions.clear();
|
|
9628
9730
|
this.waitingForInteraction = false;
|
|
9731
|
+
this.removeBeforeUnloadHandler();
|
|
9629
9732
|
}
|
|
9630
9733
|
/**
|
|
9631
9734
|
* Sets the waiting for interaction state
|
|
@@ -10862,6 +10965,11 @@ class PlaylistLoader {
|
|
|
10862
10965
|
const manifestIdForProgress = manifest.id;
|
|
10863
10966
|
const wasTriggered = options._triggeredByTriggerManager === true;
|
|
10864
10967
|
let startStepId = manifest.startStep;
|
|
10968
|
+
const pendingNav = this.checkPendingNavigation(manifest);
|
|
10969
|
+
if (pendingNav) {
|
|
10970
|
+
log(`PlaylistLoader: Resuming from pending URL navigation to step '${pendingNav.nextStepId}'`);
|
|
10971
|
+
return pendingNav.nextStepId;
|
|
10972
|
+
}
|
|
10865
10973
|
if (options.startNodeId) {
|
|
10866
10974
|
const customStep = manifest.steps.find((step) => step.id === options.startNodeId);
|
|
10867
10975
|
if (customStep) {
|
|
@@ -10893,6 +11001,61 @@ class PlaylistLoader {
|
|
|
10893
11001
|
}
|
|
10894
11002
|
return startStepId;
|
|
10895
11003
|
}
|
|
11004
|
+
/**
|
|
11005
|
+
* Checks for pending navigation from a cross-page URL transition
|
|
11006
|
+
* This handles the case where user navigated to a new page (hard refresh)
|
|
11007
|
+
* and we need to resume from the step that was waiting for that URL
|
|
11008
|
+
* @param manifest - The loaded playlist manifest
|
|
11009
|
+
* @returns The pending navigation data if valid, null otherwise
|
|
11010
|
+
*/
|
|
11011
|
+
checkPendingNavigation(manifest) {
|
|
11012
|
+
const storageManager2 = StorageManager.getInstance();
|
|
11013
|
+
const pending = storageManager2.getPendingNavigation();
|
|
11014
|
+
if (!pending) {
|
|
11015
|
+
return null;
|
|
11016
|
+
}
|
|
11017
|
+
if (pending.playlistId !== manifest.id) {
|
|
11018
|
+
log(`PlaylistLoader: Pending navigation is for different playlist (${pending.playlistId}), ignoring`);
|
|
11019
|
+
return null;
|
|
11020
|
+
}
|
|
11021
|
+
const ageMs = Date.now() - pending.timestamp;
|
|
11022
|
+
if (ageMs > TIMING.PENDING_NAVIGATION_EXPIRY) {
|
|
11023
|
+
storageManager2.clearPendingNavigation();
|
|
11024
|
+
return null;
|
|
11025
|
+
}
|
|
11026
|
+
if (!this.isURLPathMatch(pending.urlPattern)) {
|
|
11027
|
+
log(`PlaylistLoader: URL doesn't match pending pattern '${pending.urlPattern}', clearing`);
|
|
11028
|
+
storageManager2.clearPendingNavigation();
|
|
11029
|
+
return null;
|
|
11030
|
+
}
|
|
11031
|
+
const targetStep = manifest.steps.find((step) => step.id === pending.nextStepId);
|
|
11032
|
+
if (!targetStep) {
|
|
11033
|
+
log(`PlaylistLoader: Pending navigation target step '${pending.nextStepId}' not found in manifest, clearing`);
|
|
11034
|
+
storageManager2.clearPendingNavigation();
|
|
11035
|
+
return null;
|
|
11036
|
+
}
|
|
11037
|
+
log(`PlaylistLoader: Using pending navigation to step '${pending.nextStepId}' (${Math.round(ageMs / 1e3)}s old)`);
|
|
11038
|
+
storageManager2.clearPendingNavigation();
|
|
11039
|
+
return pending;
|
|
11040
|
+
}
|
|
11041
|
+
/**
|
|
11042
|
+
* Checks if the current URL path matches a pattern
|
|
11043
|
+
* Uses the same logic as TransitionManager for consistency
|
|
11044
|
+
* @param pattern - The URL pattern to match (supports wildcards)
|
|
11045
|
+
* @returns true if the current URL matches the pattern
|
|
11046
|
+
*/
|
|
11047
|
+
isURLPathMatch(pattern) {
|
|
11048
|
+
if (!pattern) {
|
|
11049
|
+
return false;
|
|
11050
|
+
}
|
|
11051
|
+
const currentUrl = window.location.href;
|
|
11052
|
+
const currentPath = window.location.pathname;
|
|
11053
|
+
const escapedPattern = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
11054
|
+
const regexPattern = escapedPattern.replace(/\\\*/g, ".*");
|
|
11055
|
+
const regex = new RegExp(regexPattern);
|
|
11056
|
+
const match = regex.test(currentUrl) || regex.test(currentPath);
|
|
11057
|
+
return match;
|
|
11058
|
+
}
|
|
10896
11059
|
}
|
|
10897
11060
|
class PlaylistManager extends EventSubscriberManager {
|
|
10898
11061
|
/**
|
|
@@ -12306,7 +12469,7 @@ const SaltfishPlayer$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.de
|
|
|
12306
12469
|
__proto__: null,
|
|
12307
12470
|
SaltfishPlayer
|
|
12308
12471
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
12309
|
-
const version = "0.3.
|
|
12472
|
+
const version = "0.3.62";
|
|
12310
12473
|
const packageJson = {
|
|
12311
12474
|
version
|
|
12312
12475
|
};
|