@scalemule/nextjs 0.1.22 → 0.1.24

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/index.d.mts CHANGED
@@ -898,10 +898,11 @@ interface NarrationPlayerProps {
898
898
  providerLabel?: string;
899
899
  narrationLabel?: string;
900
900
  refreshing?: boolean;
901
- onRefresh?: () => void;
901
+ onRefresh?: () => void | Promise<void>;
902
+ showRefreshButton?: boolean;
902
903
  onPlaybackError?: () => void;
903
904
  }
904
- declare function NarrationPlayer({ audio, className, providerLabel, narrationLabel, refreshing, onRefresh, onPlaybackError, }: NarrationPlayerProps): react_jsx_runtime.JSX.Element | null;
905
+ declare function NarrationPlayer({ audio, className, providerLabel, narrationLabel, refreshing, onRefresh, showRefreshButton, onPlaybackError, }: NarrationPlayerProps): react_jsx_runtime.JSX.Element | null;
905
906
 
906
907
  /**
907
908
  * Client-side validation helpers
package/dist/index.d.ts CHANGED
@@ -898,10 +898,11 @@ interface NarrationPlayerProps {
898
898
  providerLabel?: string;
899
899
  narrationLabel?: string;
900
900
  refreshing?: boolean;
901
- onRefresh?: () => void;
901
+ onRefresh?: () => void | Promise<void>;
902
+ showRefreshButton?: boolean;
902
903
  onPlaybackError?: () => void;
903
904
  }
904
- declare function NarrationPlayer({ audio, className, providerLabel, narrationLabel, refreshing, onRefresh, onPlaybackError, }: NarrationPlayerProps): react_jsx_runtime.JSX.Element | null;
905
+ declare function NarrationPlayer({ audio, className, providerLabel, narrationLabel, refreshing, onRefresh, showRefreshButton, onPlaybackError, }: NarrationPlayerProps): react_jsx_runtime.JSX.Element | null;
905
906
 
906
907
  /**
907
908
  * Client-side validation helpers
package/dist/index.js CHANGED
@@ -11289,6 +11289,7 @@ function FeedbackWidget(props) {
11289
11289
  ] });
11290
11290
  }
11291
11291
  var SPEEDS = [1, 1.25, 1.5, 2];
11292
+ var EXPIRY_REFRESH_WINDOW_MS = 3e4;
11292
11293
  var styles = {
11293
11294
  root: {
11294
11295
  borderRadius: "1.75rem",
@@ -11462,10 +11463,14 @@ function NarrationPlayer({
11462
11463
  narrationLabel = "AI-Narrated",
11463
11464
  refreshing = false,
11464
11465
  onRefresh,
11466
+ showRefreshButton = false,
11465
11467
  onPlaybackError
11466
11468
  }) {
11467
11469
  const audioRef = React.useRef(null);
11468
11470
  const scrubRef = React.useRef(null);
11471
+ const pendingResumeRef = React.useRef(false);
11472
+ const pendingResumeTimeRef = React.useRef(null);
11473
+ const recoveredUrlRef = React.useRef(null);
11469
11474
  const [playing, setPlaying] = React.useState(false);
11470
11475
  const [currentTime, setCurrentTime] = React.useState(0);
11471
11476
  const [duration, setDuration] = React.useState(audio.duration_ms ? audio.duration_ms / 1e3 : 0);
@@ -11480,10 +11485,26 @@ function NarrationPlayer({
11480
11485
  React.useEffect(() => {
11481
11486
  const element = audioRef.current;
11482
11487
  if (!element) return;
11483
- setCurrentTime(0);
11488
+ recoveredUrlRef.current = null;
11489
+ const resumeTime = pendingResumeTimeRef.current;
11490
+ setCurrentTime(resumeTime ?? 0);
11484
11491
  setPlaying(false);
11485
11492
  element.pause();
11486
- element.currentTime = 0;
11493
+ try {
11494
+ element.currentTime = resumeTime ?? 0;
11495
+ } catch {
11496
+ element.currentTime = 0;
11497
+ }
11498
+ if (pendingResumeRef.current) {
11499
+ pendingResumeRef.current = false;
11500
+ pendingResumeTimeRef.current = null;
11501
+ element.play().catch(() => {
11502
+ setPlaying(false);
11503
+ onPlaybackError?.();
11504
+ });
11505
+ return;
11506
+ }
11507
+ pendingResumeTimeRef.current = null;
11487
11508
  }, [audio.url]);
11488
11509
  React.useEffect(() => {
11489
11510
  const element = audioRef.current;
@@ -11491,10 +11512,49 @@ function NarrationPlayer({
11491
11512
  element.playbackRate = speed;
11492
11513
  element.preservesPitch = true;
11493
11514
  }, [speed]);
11515
+ function isUrlExpiringSoon() {
11516
+ if (!audio.expires_at) {
11517
+ return false;
11518
+ }
11519
+ const expiresAtMs = Date.parse(audio.expires_at);
11520
+ if (!Number.isFinite(expiresAtMs)) {
11521
+ return false;
11522
+ }
11523
+ return expiresAtMs - Date.now() <= EXPIRY_REFRESH_WINDOW_MS;
11524
+ }
11525
+ async function refreshAndResume() {
11526
+ if (!onRefresh || refreshing) {
11527
+ return false;
11528
+ }
11529
+ if (recoveredUrlRef.current === (audio.url ?? null)) {
11530
+ return false;
11531
+ }
11532
+ recoveredUrlRef.current = audio.url ?? null;
11533
+ pendingResumeRef.current = true;
11534
+ pendingResumeTimeRef.current = currentTime;
11535
+ try {
11536
+ await Promise.resolve(onRefresh());
11537
+ return true;
11538
+ } catch {
11539
+ pendingResumeRef.current = false;
11540
+ pendingResumeTimeRef.current = null;
11541
+ onPlaybackError?.();
11542
+ return false;
11543
+ }
11544
+ }
11494
11545
  function togglePlayback() {
11495
11546
  const element = audioRef.current;
11496
11547
  if (!element) return;
11497
11548
  if (element.paused) {
11549
+ if (refreshing) {
11550
+ pendingResumeRef.current = true;
11551
+ pendingResumeTimeRef.current = currentTime;
11552
+ return;
11553
+ }
11554
+ if (onRefresh && isUrlExpiringSoon()) {
11555
+ void refreshAndResume();
11556
+ return;
11557
+ }
11498
11558
  element.play().catch(() => {
11499
11559
  setPlaying(false);
11500
11560
  onPlaybackError?.();
@@ -11537,7 +11597,17 @@ function NarrationPlayer({
11537
11597
  onPlay: () => setPlaying(true),
11538
11598
  onPause: () => setPlaying(false),
11539
11599
  onEnded: () => setPlaying(false),
11540
- onError: () => onPlaybackError?.()
11600
+ onError: () => {
11601
+ if (onRefresh) {
11602
+ void refreshAndResume().then((didRefresh) => {
11603
+ if (!didRefresh) {
11604
+ onPlaybackError?.();
11605
+ }
11606
+ });
11607
+ return;
11608
+ }
11609
+ onPlaybackError?.();
11610
+ }
11541
11611
  }
11542
11612
  ),
11543
11613
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.layout, children: [
@@ -11649,7 +11719,7 @@ function NarrationPlayer({
11649
11719
  ]
11650
11720
  }
11651
11721
  ),
11652
- onRefresh && /* @__PURE__ */ jsxRuntime.jsx(
11722
+ onRefresh && showRefreshButton && /* @__PURE__ */ jsxRuntime.jsx(
11653
11723
  "button",
11654
11724
  {
11655
11725
  type: "button",
package/dist/index.mjs CHANGED
@@ -11269,6 +11269,7 @@ function FeedbackWidget(props) {
11269
11269
  ] });
11270
11270
  }
11271
11271
  var SPEEDS = [1, 1.25, 1.5, 2];
11272
+ var EXPIRY_REFRESH_WINDOW_MS = 3e4;
11272
11273
  var styles = {
11273
11274
  root: {
11274
11275
  borderRadius: "1.75rem",
@@ -11442,10 +11443,14 @@ function NarrationPlayer({
11442
11443
  narrationLabel = "AI-Narrated",
11443
11444
  refreshing = false,
11444
11445
  onRefresh,
11446
+ showRefreshButton = false,
11445
11447
  onPlaybackError
11446
11448
  }) {
11447
11449
  const audioRef = useRef(null);
11448
11450
  const scrubRef = useRef(null);
11451
+ const pendingResumeRef = useRef(false);
11452
+ const pendingResumeTimeRef = useRef(null);
11453
+ const recoveredUrlRef = useRef(null);
11449
11454
  const [playing, setPlaying] = useState(false);
11450
11455
  const [currentTime, setCurrentTime] = useState(0);
11451
11456
  const [duration, setDuration] = useState(audio.duration_ms ? audio.duration_ms / 1e3 : 0);
@@ -11460,10 +11465,26 @@ function NarrationPlayer({
11460
11465
  useEffect(() => {
11461
11466
  const element = audioRef.current;
11462
11467
  if (!element) return;
11463
- setCurrentTime(0);
11468
+ recoveredUrlRef.current = null;
11469
+ const resumeTime = pendingResumeTimeRef.current;
11470
+ setCurrentTime(resumeTime ?? 0);
11464
11471
  setPlaying(false);
11465
11472
  element.pause();
11466
- element.currentTime = 0;
11473
+ try {
11474
+ element.currentTime = resumeTime ?? 0;
11475
+ } catch {
11476
+ element.currentTime = 0;
11477
+ }
11478
+ if (pendingResumeRef.current) {
11479
+ pendingResumeRef.current = false;
11480
+ pendingResumeTimeRef.current = null;
11481
+ element.play().catch(() => {
11482
+ setPlaying(false);
11483
+ onPlaybackError?.();
11484
+ });
11485
+ return;
11486
+ }
11487
+ pendingResumeTimeRef.current = null;
11467
11488
  }, [audio.url]);
11468
11489
  useEffect(() => {
11469
11490
  const element = audioRef.current;
@@ -11471,10 +11492,49 @@ function NarrationPlayer({
11471
11492
  element.playbackRate = speed;
11472
11493
  element.preservesPitch = true;
11473
11494
  }, [speed]);
11495
+ function isUrlExpiringSoon() {
11496
+ if (!audio.expires_at) {
11497
+ return false;
11498
+ }
11499
+ const expiresAtMs = Date.parse(audio.expires_at);
11500
+ if (!Number.isFinite(expiresAtMs)) {
11501
+ return false;
11502
+ }
11503
+ return expiresAtMs - Date.now() <= EXPIRY_REFRESH_WINDOW_MS;
11504
+ }
11505
+ async function refreshAndResume() {
11506
+ if (!onRefresh || refreshing) {
11507
+ return false;
11508
+ }
11509
+ if (recoveredUrlRef.current === (audio.url ?? null)) {
11510
+ return false;
11511
+ }
11512
+ recoveredUrlRef.current = audio.url ?? null;
11513
+ pendingResumeRef.current = true;
11514
+ pendingResumeTimeRef.current = currentTime;
11515
+ try {
11516
+ await Promise.resolve(onRefresh());
11517
+ return true;
11518
+ } catch {
11519
+ pendingResumeRef.current = false;
11520
+ pendingResumeTimeRef.current = null;
11521
+ onPlaybackError?.();
11522
+ return false;
11523
+ }
11524
+ }
11474
11525
  function togglePlayback() {
11475
11526
  const element = audioRef.current;
11476
11527
  if (!element) return;
11477
11528
  if (element.paused) {
11529
+ if (refreshing) {
11530
+ pendingResumeRef.current = true;
11531
+ pendingResumeTimeRef.current = currentTime;
11532
+ return;
11533
+ }
11534
+ if (onRefresh && isUrlExpiringSoon()) {
11535
+ void refreshAndResume();
11536
+ return;
11537
+ }
11478
11538
  element.play().catch(() => {
11479
11539
  setPlaying(false);
11480
11540
  onPlaybackError?.();
@@ -11517,7 +11577,17 @@ function NarrationPlayer({
11517
11577
  onPlay: () => setPlaying(true),
11518
11578
  onPause: () => setPlaying(false),
11519
11579
  onEnded: () => setPlaying(false),
11520
- onError: () => onPlaybackError?.()
11580
+ onError: () => {
11581
+ if (onRefresh) {
11582
+ void refreshAndResume().then((didRefresh) => {
11583
+ if (!didRefresh) {
11584
+ onPlaybackError?.();
11585
+ }
11586
+ });
11587
+ return;
11588
+ }
11589
+ onPlaybackError?.();
11590
+ }
11521
11591
  }
11522
11592
  ),
11523
11593
  /* @__PURE__ */ jsxs("div", { style: styles.layout, children: [
@@ -11629,7 +11699,7 @@ function NarrationPlayer({
11629
11699
  ]
11630
11700
  }
11631
11701
  ),
11632
- onRefresh && /* @__PURE__ */ jsx(
11702
+ onRefresh && showRefreshButton && /* @__PURE__ */ jsx(
11633
11703
  "button",
11634
11704
  {
11635
11705
  type: "button",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalemule/nextjs",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "ScaleMule SDK for Next.js applications - authentication, storage, and user management",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",