storysplat-viewer 2.9.47 → 2.9.48

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 CHANGED
@@ -29,6 +29,9 @@ A powerful npm package for embedding and interacting with 3D Gaussian Splatting
29
29
  - [Post-Processing](#post-processing)
30
30
  - [Custom Menu Links](#custom-menu-links)
31
31
  - [Entity Animations](#entity-animations)
32
+ - [Guided Narration](#guided-narration)
33
+ - [Custom Scripts](#custom-scripts)
34
+ - [8th Wall AR](#8th-wall-ar)
32
35
  - [React Integration](#react-integration)
33
36
  - [Native App Integration](#native-app-integration)
34
37
  - [Analytics & Tracking](#analytics--tracking)
@@ -599,7 +602,7 @@ viewer.getFrameProgress(); // Get current progress
599
602
  ### 4DGS Events
600
603
 
601
604
  ```javascript
602
- viewer.on('frameChange', ({ frame, total }) => {
605
+ viewer.on('frameChange', (frame, total) => {
603
606
  console.log(`Frame ${frame} of ${total}`);
604
607
  });
605
608
 
@@ -800,8 +803,8 @@ viewer.on('loaded', () => {
800
803
  });
801
804
 
802
805
  // Loading progress
803
- viewer.on('progress', ({ loaded, total, percent }) => {
804
- console.log(`Loading: ${percent}%`);
806
+ viewer.on('progress', ({ progress, text }) => {
807
+ console.log(`Loading ${Math.round(progress * 100)}%: ${text}`);
805
808
  });
806
809
 
807
810
  // Waypoint changed
@@ -839,7 +842,7 @@ viewer.on('splatChange', ({ url, isOriginal }) => {
839
842
  });
840
843
 
841
844
  // 4DGS frame sequence
842
- viewer.on('frameChange', ({ frame, total }) => {
845
+ viewer.on('frameChange', (frame, total) => {
843
846
  console.log(`Frame ${frame} of ${total}`);
844
847
  });
845
848
  viewer.on('frameComplete', () => {
@@ -847,8 +850,8 @@ viewer.on('frameComplete', () => {
847
850
  });
848
851
 
849
852
  // Portal activated
850
- viewer.on('portalActivated', ({ sceneId }) => {
851
- console.log(`Navigating to scene: ${sceneId}`);
853
+ viewer.on('portalActivated', ({ targetSceneId }) => {
854
+ console.log(`Navigating to scene: ${targetSceneId}`);
852
855
  });
853
856
 
854
857
  // Errors and warnings
@@ -865,9 +868,9 @@ viewer.on('warning', ({ type, message }) => {
865
868
  | Event | Data | Description |
866
869
  |-------|------|-------------|
867
870
  | `ready` | — | Viewer initialized |
868
- | `loaded` | | Scene fully loaded |
869
- | `progress` | `{ loaded, total, percent }` | Loading progress |
870
- | `waypointChange` | `{ index, waypoint }` | Waypoint changed |
871
+ | `loaded` | `{ bandwidthUsed, isStorySplatHosted }` | Scene fully loaded |
872
+ | `progress` | `{ progress, text }` | Loading progress |
873
+ | `waypointChange` | `{ index, waypoint, prevIndex, cameraMode }` | Waypoint changed |
871
874
  | `playbackStart` | — | Auto-play started |
872
875
  | `playbackStop` | — | Auto-play paused/stopped |
873
876
  | `playbackComplete` | — | Tour reached the end |
@@ -877,12 +880,11 @@ viewer.on('warning', ({ type, message }) => {
877
880
  | `xrStart` | `{ type }` | Entered VR/AR |
878
881
  | `xrEnd` | — | Exited VR/AR |
879
882
  | `splatChange` | `{ url, isOriginal }` | Splat file swapped |
880
- | `frameChange` | `{ frame, total }` | 4DGS frame changed |
883
+ | `frameChange` | `(frame, total)` | 4DGS frame changed |
881
884
  | `frameComplete` | — | 4DGS sequence finished |
882
885
  | `portalActivated` | `{ portalId, targetSceneId, targetSceneName }` | Portal triggered |
883
- | `portalNavigationStart` | `{ targetSceneId }` | Portal navigation beginning |
884
- | `portalNavigationComplete` | `{ sceneId }` | Portal navigation complete |
885
- | `portalNavigationError` | `{ error, targetSceneId }` | Portal navigation failed |
886
+ | `portalClick` | `{ portal }` | Portal clicked in editor mode |
887
+ | `panoramaModeChange` | `{ enabled }` | 360 panorama portal mode changed |
886
888
  | `error` | `Error` | Viewer error |
887
889
  | `warning` | `{ type, message }` | Non-fatal warning |
888
890
 
@@ -963,9 +965,8 @@ The `targetSceneId` is just a string you define — it doesn't need to exist on
963
965
  | Event | Description | Data |
964
966
  |-------|-------------|------|
965
967
  | `portalActivated` | Portal clicked or proximity triggered | `{ portalId, targetSceneId, targetSceneName }` |
966
- | `portalNavigationStart` | Navigation beginning | `{ targetSceneId }` |
967
- | `portalNavigationComplete` | New scene loaded | `{ sceneId }` |
968
- | `portalNavigationError` | Navigation failed | `{ error, targetSceneId }` |
968
+ | `portalClick` | Portal clicked in editor mode | `{ portal }` |
969
+ | `panoramaModeChange` | 360 panorama portal mode changed | `{ enabled }` |
969
970
 
970
971
  ## Audio Emitters
971
972
 
@@ -1247,6 +1248,113 @@ Keyframe-based animation system for any scene entity (hotspots, portals, lights,
1247
1248
  | Scale | `scale.x`, `scale.y`, `scale.z` |
1248
1249
  | Particles | `rate`, `lifetime`, `speed`, `scale`, `gravity`, `opacity`, `rotationSpeed` |
1249
1250
 
1251
+ ## Guided Narration
1252
+
1253
+ Add a continuous voiceover that synchronizes with the camera tour. The camera speed adjusts automatically per segment to match the narration timing.
1254
+
1255
+ ### Scene Data
1256
+
1257
+ ```json
1258
+ {
1259
+ "narrationTrack": {
1260
+ "enabled": true,
1261
+ "audioUrl": "https://example.com/narration.mp3",
1262
+ "duration": 120,
1263
+ "volume": 0.8,
1264
+ "segments": [
1265
+ {
1266
+ "id": "intro",
1267
+ "audioStart": 0,
1268
+ "audioEnd": 15,
1269
+ "progressStart": 0,
1270
+ "progressEnd": 25
1271
+ },
1272
+ {
1273
+ "id": "gallery",
1274
+ "audioStart": 15,
1275
+ "audioEnd": 45,
1276
+ "progressStart": 25,
1277
+ "progressEnd": 60
1278
+ }
1279
+ ]
1280
+ }
1281
+ }
1282
+ ```
1283
+
1284
+ | Property | Type | Description |
1285
+ |----------|------|-------------|
1286
+ | `enabled` | `boolean` | Master toggle for narration |
1287
+ | `audioUrl` | `string` | URL to the narration audio file (MP3, WAV, OGG) |
1288
+ | `duration` | `number` | Total audio duration in seconds |
1289
+ | `volume` | `number` | Playback volume (0–1) |
1290
+ | `segments` | `array` | Maps audio time ranges to tour progress ranges |
1291
+ | `segments[].id` | `string` | Stable segment identifier |
1292
+ | `segments[].audioStart` / `audioEnd` | `number` | Time range in the audio file (seconds) |
1293
+ | `segments[].progressStart` / `progressEnd` | `number` | Corresponding tour progress range (0–100) |
1294
+
1295
+ The viewer derives camera progress from the active audio segment and only seeks the audio when drift exceeds 0.5 seconds. Narration pauses when the viewer switches to Explore or Walk mode and resumes when returning to Tour mode.
1296
+
1297
+ ## Custom Scripts
1298
+
1299
+ Execute custom JavaScript in the viewer sandbox for advanced behaviors. Scripts have access to the viewer API but run in a restricted environment with no network, DOM, or storage access.
1300
+
1301
+ ### Scene Data
1302
+
1303
+ ```json
1304
+ {
1305
+ "customScript": "viewer.on('waypointChange', ({ index }) => {\n console.log('Arrived at waypoint', index);\n});"
1306
+ }
1307
+ ```
1308
+
1309
+ ### Available Globals
1310
+
1311
+ | Global | Description |
1312
+ |--------|-------------|
1313
+ | `viewer` | The main viewer API (navigation, camera, audio, hotspots, splats, 4DGS) |
1314
+ | `canvas` | Read-only access to the rendering canvas (dimensions and pointer events) |
1315
+ | `getScrollPercentage()` | Returns current scroll progress (0–1) |
1316
+ | `getCurrentWaypointIndex()` | Returns the current waypoint index |
1317
+ | `registerUpdate(fn)` | Register a function that runs every frame |
1318
+ | `registerCleanup(fn)` | Register a function that runs on disposal |
1319
+ | `console` | Standard log, warn, error, info, debug |
1320
+ | `setTimeout`, `setInterval`, `clearTimeout`, `clearInterval` | Timer functions |
1321
+ | `requestAnimationFrame`, `cancelAnimationFrame` | Animation frame functions |
1322
+ | `queueMicrotask` | Microtask scheduling |
1323
+
1324
+ Scripts are limited to 200,000 characters. The `registerUpdate` callback count is capped at 200.
1325
+
1326
+ ### Blocked (Security)
1327
+
1328
+ No `fetch`, `XMLHttpRequest`, `WebSocket`, `localStorage`, `document`, `window`, `eval`, `Function`, `Worker`, or dynamic `import()`.
1329
+
1330
+ ## 8th Wall AR
1331
+
1332
+ For AR experiences on iOS devices (which lack native WebXR), the viewer supports 8th Wall (XR8) as an alternative AR runtime.
1333
+
1334
+ ```typescript
1335
+ import { loadXR8, isXR8Available, isNativeWebXRAvailable, ARPlacement } from 'storysplat-viewer';
1336
+
1337
+ // Check available AR runtimes
1338
+ if (await isNativeWebXRAvailable()) {
1339
+ // Use native WebXR (Android Chrome, Quest, etc.)
1340
+ } else {
1341
+ // Load 8th Wall (iOS Safari, etc.)
1342
+ await loadXR8();
1343
+ if (isXR8Available()) {
1344
+ // XR8 is ready
1345
+ }
1346
+ }
1347
+ ```
1348
+
1349
+ | Export | Description |
1350
+ |--------|-------------|
1351
+ | `loadXR8()` | Loads the 8th Wall XR8 runtime. Returns a Promise. |
1352
+ | `isXR8Available()` | Checks synchronously whether 8th Wall is already loaded. |
1353
+ | `isNativeWebXRAvailable()` | Checks asynchronously whether the browser supports native WebXR AR. |
1354
+ | `ARPlacement` | AR placement controller for positioning scenes in real-world space. |
1355
+
1356
+ The bundled 8th Wall engine is self-hosted and does not require an API key. Use `eighthWallBaseUrl` or a `data-8thwall-base` attribute to point at custom engine files, and keep `scale: 'absolute'` for ground detection.
1357
+
1250
1358
  ## React Integration
1251
1359
 
1252
1360
  ```jsx
@@ -1456,9 +1564,25 @@ This data appears in your StorySplat admin dashboard.
1456
1564
  | `createViewer` (self-hosted scenes) | No | No |
1457
1565
  | iframe embed | Yes | Yes |
1458
1566
 
1567
+ ### Manual Analytics Setup
1568
+
1569
+ If you use `createViewer` (self-hosted) but still want StorySplat analytics, use `setupAnalyticsTracking`:
1570
+
1571
+ ```typescript
1572
+ import { createViewer, setupAnalyticsTracking } from 'storysplat-viewer';
1573
+
1574
+ const viewer = createViewer(container, sceneData);
1575
+ const tracker = setupAnalyticsTracking(viewer, 'https://discover.storysplat.com', 'SCENE_ID', 'OWNER_UID');
1576
+
1577
+ // Clean up when done
1578
+ tracker.destroy();
1579
+ ```
1580
+
1581
+ This enables view counting, bandwidth tracking, and session recording for self-hosted scenes.
1582
+
1459
1583
  ### Privacy Note
1460
1584
 
1461
- Tracking only occurs for scenes loaded via `createViewerFromSceneId`. If you use `createViewer` with your own scene data, no data is sent to StorySplat servers.
1585
+ Tracking only occurs for scenes loaded via `createViewerFromSceneId` (or when `setupAnalyticsTracking` is explicitly called). If you use `createViewer` without manual tracking setup, no data is sent to StorySplat servers.
1462
1586
 
1463
1587
  ### Comparison: Scene ID vs JSON File
1464
1588
 
@@ -1555,6 +1679,18 @@ import type {
1555
1679
  RevealPresetConfig,
1556
1680
  } from 'storysplat-viewer';
1557
1681
 
1682
+ // Analytics
1683
+ import { setupAnalyticsTracking } from 'storysplat-viewer';
1684
+
1685
+ // Custom Scripts
1686
+ import { CustomScriptSystem, setupCustomScript } from 'storysplat-viewer';
1687
+
1688
+ // 8th Wall AR
1689
+ import { loadXR8, isXR8Available, isNativeWebXRAvailable, ARPlacement } from 'storysplat-viewer';
1690
+
1691
+ // 4DGS
1692
+ import { FrameSequencePlayer } from 'storysplat-viewer';
1693
+
1558
1694
  // Utilities
1559
1695
  import {
1560
1696
  DEFAULT_BUTTON_LABELS,