@ericchen1990/pano-view 3.4.0 → 3.4.2

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
@@ -229,6 +229,10 @@ type VideoHotspotProps = HotspotCommonProps & {
229
229
  loop?: boolean;
230
230
  muted?: boolean;
231
231
  volume?: number;
232
+ /**
233
+ * Keep playback in the page. Defaults to true, including the WebKit and
234
+ * X5 attributes for iPhone Safari, WeChat, QQ, and UC.
235
+ */
232
236
  playsInline?: boolean;
233
237
  preload?: "none" | "metadata" | "auto";
234
238
  crossOrigin?: "" | "anonymous" | "use-credentials";
@@ -1298,6 +1302,10 @@ type PanoVideoProps = {
1298
1302
  muted?: boolean;
1299
1303
  volume?: number;
1300
1304
  autoPlay?: boolean;
1305
+ /**
1306
+ * Keep playback in the page. Defaults to true, including the WebKit and
1307
+ * X5 attributes for iPhone Safari, WeChat, QQ, and UC.
1308
+ */
1301
1309
  playsInline?: boolean;
1302
1310
  preload?: "none" | "metadata" | "auto";
1303
1311
  crossOrigin?: "" | "anonymous" | "use-credentials";
package/dist/index.d.ts CHANGED
@@ -229,6 +229,10 @@ type VideoHotspotProps = HotspotCommonProps & {
229
229
  loop?: boolean;
230
230
  muted?: boolean;
231
231
  volume?: number;
232
+ /**
233
+ * Keep playback in the page. Defaults to true, including the WebKit and
234
+ * X5 attributes for iPhone Safari, WeChat, QQ, and UC.
235
+ */
232
236
  playsInline?: boolean;
233
237
  preload?: "none" | "metadata" | "auto";
234
238
  crossOrigin?: "" | "anonymous" | "use-credentials";
@@ -1298,6 +1302,10 @@ type PanoVideoProps = {
1298
1302
  muted?: boolean;
1299
1303
  volume?: number;
1300
1304
  autoPlay?: boolean;
1305
+ /**
1306
+ * Keep playback in the page. Defaults to true, including the WebKit and
1307
+ * X5 attributes for iPhone Safari, WeChat, QQ, and UC.
1308
+ */
1301
1309
  playsInline?: boolean;
1302
1310
  preload?: "none" | "metadata" | "auto";
1303
1311
  crossOrigin?: "" | "anonymous" | "use-credentials";
package/dist/index.mjs CHANGED
@@ -3404,7 +3404,9 @@ function KeyboardControls({
3404
3404
  return;
3405
3405
  }
3406
3406
  const previousTabIndex = element.getAttribute("tabindex");
3407
+ const previousOutline = element.style.outline;
3407
3408
  element.tabIndex = 0;
3409
+ element.style.outline = "none";
3408
3410
  const syncKeyboardActive = () => {
3409
3411
  let continuous = false;
3410
3412
  for (const action of pressedRef.current) {
@@ -3488,6 +3490,7 @@ function KeyboardControls({
3488
3490
  } else {
3489
3491
  element.setAttribute("tabindex", previousTabIndex);
3490
3492
  }
3493
+ element.style.outline = previousOutline;
3491
3494
  clearPressed();
3492
3495
  };
3493
3496
  }, [controlsRef, enabled, gl, keyMap]);
@@ -9110,6 +9113,56 @@ function useWebVRPointerLock(enabled, element) {
9110
9113
  // src/webvr/wakelock.ts
9111
9114
  import { useEffect as useEffect19 } from "react";
9112
9115
 
9116
+ // src/video/inline-playback.ts
9117
+ var INLINE_VIDEO_ATTRIBUTES = [
9118
+ ["playsinline", "true"],
9119
+ ["webkit-playsinline", "true"],
9120
+ ["x5-playsinline", "true"],
9121
+ ["x5-video-player-type", "h5-page"],
9122
+ ["x5-video-player-fullscreen", "false"]
9123
+ ];
9124
+ function applyInlineVideoPlayback(video, inline) {
9125
+ video.controls = false;
9126
+ video.playsInline = inline;
9127
+ video.disablePictureInPicture = true;
9128
+ video.disableRemotePlayback = true;
9129
+ video.setAttribute("disablepictureinpicture", "");
9130
+ video.setAttribute("disableremoteplayback", "");
9131
+ video.setAttribute("x-webkit-airplay", "deny");
9132
+ video.setAttribute(
9133
+ "controlsList",
9134
+ "nodownload nofullscreen noremoteplayback"
9135
+ );
9136
+ if (inline) {
9137
+ for (const [name, value] of INLINE_VIDEO_ATTRIBUTES) {
9138
+ video.setAttribute(name, value);
9139
+ }
9140
+ return;
9141
+ }
9142
+ for (const [name] of INLINE_VIDEO_ATTRIBUTES) {
9143
+ video.removeAttribute(name);
9144
+ }
9145
+ }
9146
+ function mountTextureVideo(video, canvas) {
9147
+ const parent = canvas.closest("[data-pano-viewer]") ?? canvas.parentElement;
9148
+ if (!parent) {
9149
+ return;
9150
+ }
9151
+ video.tabIndex = -1;
9152
+ video.setAttribute("aria-hidden", "true");
9153
+ video.setAttribute("data-pano-texture-video", "");
9154
+ video.style.position = "absolute";
9155
+ video.style.top = "0";
9156
+ video.style.left = "0";
9157
+ video.style.width = "1px";
9158
+ video.style.height = "1px";
9159
+ video.style.opacity = "0";
9160
+ video.style.pointerEvents = "none";
9161
+ if (video.parentElement !== parent) {
9162
+ parent.appendChild(video);
9163
+ }
9164
+ }
9165
+
9113
9166
  // src/webvr/wakelock-fallback.ts
9114
9167
  var WEBVR_WAKELOCK_FALLBACK_VIDEO_BASE64 = "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";
9115
9168
 
@@ -9135,17 +9188,12 @@ function decodeBase64(value) {
9135
9188
  function createFallbackVideo() {
9136
9189
  const video = document.createElement("video");
9137
9190
  video.autoplay = true;
9138
- video.controls = false;
9139
9191
  video.defaultMuted = true;
9140
9192
  video.loop = true;
9141
9193
  video.muted = true;
9142
- video.playsInline = true;
9143
9194
  video.tabIndex = -1;
9144
9195
  video.ariaHidden = "true";
9145
- video.disablePictureInPicture = true;
9146
- video.setAttribute("playsinline", "");
9147
- video.setAttribute("webkit-playsinline", "");
9148
- video.setAttribute("disablepictureinpicture", "");
9196
+ applyInlineVideoPlayback(video, true);
9149
9197
  video.style.cssText = "pointer-events:none;position:fixed;bottom:0;left:0;width:1px;height:1px;opacity:0;";
9150
9198
  const src = URL.createObjectURL(
9151
9199
  new Blob([decodeBase64(WEBVR_WAKELOCK_FALLBACK_VIDEO_BASE64)], {
@@ -11129,7 +11177,6 @@ function PanoVideo({
11129
11177
  usePanoMediaActivationIntent(autoPlay);
11130
11178
  const gl = useThree16((state) => state.gl);
11131
11179
  const host = useContext23(PanoVideoHostContext);
11132
- const overlay = useContext23(PanoChromeOverlayContext);
11133
11180
  const variants = useMemo17(
11134
11181
  () => resolvePanoVideoVariants({ poster, src, variants: variantsProp }),
11135
11182
  [poster, src, variantsProp]
@@ -11207,9 +11254,7 @@ function PanoVideo({
11207
11254
  }
11208
11255
  const video = document.createElement("video");
11209
11256
  video.muted = true;
11210
- video.playsInline = true;
11211
- video.setAttribute("playsinline", "true");
11212
- video.setAttribute("webkit-playsinline", "true");
11257
+ applyInlineVideoPlayback(video, true);
11213
11258
  video.preload = "metadata";
11214
11259
  const texture = new VideoTexture(video);
11215
11260
  applyEquirectangularLayout(texture, 1);
@@ -11239,22 +11284,15 @@ function PanoVideo({
11239
11284
  }
11240
11285
  resource.texture.anisotropy = gl.capabilities.getMaxAnisotropy();
11241
11286
  }, [gl, resource]);
11242
- useEffect25(() => {
11243
- if (!resource || !overlay?.overlayElement) {
11287
+ useLayoutEffect3(() => {
11288
+ if (!resource) {
11244
11289
  return;
11245
11290
  }
11246
- const { video } = resource;
11247
- video.setAttribute("data-pano-video", "");
11248
- video.style.position = "absolute";
11249
- video.style.width = "1px";
11250
- video.style.height = "1px";
11251
- video.style.opacity = "0";
11252
- video.style.pointerEvents = "none";
11253
- overlay.overlayElement.appendChild(video);
11291
+ mountTextureVideo(resource.video, gl.domElement);
11254
11292
  return () => {
11255
- video.remove();
11293
+ resource.video.remove();
11256
11294
  };
11257
- }, [overlay?.overlayElement, resource]);
11295
+ }, [gl, resource]);
11258
11296
  const resolvedVariantId = resolvePanoVideoVariantId(variants, variantId);
11259
11297
  const activeVariant = variants.find((variant) => variant.id === resolvedVariantId) ?? variants[0];
11260
11298
  const mediaKey = activeVariant ? `${activeVariant.id}:${activeVariant.sources.map((source) => source.src).join("|")}:${tracks.map((track) => `${panoVideoTrackId(track)}:${track.src}`).join("|")}:${crossOrigin ?? ""}` : "";
@@ -11544,7 +11582,7 @@ function PanoVideo({
11544
11582
  }
11545
11583
  };
11546
11584
  }, [emitSnapshot, resource]);
11547
- useEffect25(() => {
11585
+ useLayoutEffect3(() => {
11548
11586
  if (!resource) {
11549
11587
  return;
11550
11588
  }
@@ -11554,14 +11592,7 @@ function PanoVideo({
11554
11592
  if (mediaCapabilitiesRef.current.volumeAdjustable) {
11555
11593
  video.volume = clampVolume(volume);
11556
11594
  }
11557
- video.playsInline = playsInline;
11558
- if (playsInline) {
11559
- video.setAttribute("playsinline", "true");
11560
- video.setAttribute("webkit-playsinline", "true");
11561
- } else {
11562
- video.removeAttribute("playsinline");
11563
- video.removeAttribute("webkit-playsinline");
11564
- }
11595
+ applyInlineVideoPlayback(video, playsInline);
11565
11596
  video.preload = preload;
11566
11597
  }, [loop, muted, playsInline, preload, resource, volume]);
11567
11598
  useEffect25(() => {
@@ -15957,7 +15988,7 @@ function SequenceHotspot({
15957
15988
 
15958
15989
  // src/hotspot/video-hotspot.tsx
15959
15990
  import { useThree as useThree27 } from "@react-three/fiber";
15960
- import { useEffect as useEffect38, useRef as useRef39, useState as useState20 } from "react";
15991
+ import { useEffect as useEffect38, useLayoutEffect as useLayoutEffect7, useRef as useRef39, useState as useState20 } from "react";
15961
15992
  import {
15962
15993
  ClampToEdgeWrapping as ClampToEdgeWrapping6,
15963
15994
  LinearFilter as LinearFilter9,
@@ -16033,6 +16064,7 @@ function useVideoResource({
16033
16064
  poster,
16034
16065
  src
16035
16066
  }) {
16067
+ const gl = useThree27((state) => state.gl);
16036
16068
  const [resource, setResource] = useState20(null);
16037
16069
  const onEndedRef = useRef39(onEnded);
16038
16070
  const onErrorRef = useRef39(onError);
@@ -16050,12 +16082,7 @@ function useVideoResource({
16050
16082
  if (crossOrigin !== void 0) {
16051
16083
  video.crossOrigin = crossOrigin;
16052
16084
  }
16053
- const inline = playsInline ?? true;
16054
- video.playsInline = inline;
16055
- if (inline) {
16056
- video.setAttribute("playsinline", "true");
16057
- video.setAttribute("webkit-playsinline", "true");
16058
- }
16085
+ applyInlineVideoPlayback(video, playsInline ?? true);
16059
16086
  video.poster = poster ?? "";
16060
16087
  const texture = new VideoTexture2(video);
16061
16088
  texture.colorSpace = SRGBColorSpace8;
@@ -16097,6 +16124,7 @@ function useVideoResource({
16097
16124
  video.addEventListener("error", reportError);
16098
16125
  video.addEventListener("pause", handlePause);
16099
16126
  video.addEventListener("play", handlePlay);
16127
+ mountTextureVideo(video, gl.domElement);
16100
16128
  setResource({ video, texture, ready: false });
16101
16129
  video.src = src;
16102
16130
  video.load();
@@ -16110,9 +16138,19 @@ function useVideoResource({
16110
16138
  video.pause();
16111
16139
  video.removeAttribute("src");
16112
16140
  video.load();
16141
+ video.remove();
16113
16142
  texture.dispose();
16114
16143
  };
16115
- }, [crossOrigin, id, playsInline, poster, src]);
16144
+ }, [crossOrigin, gl, id, playsInline, poster, src]);
16145
+ useLayoutEffect7(() => {
16146
+ if (!resource) {
16147
+ return;
16148
+ }
16149
+ mountTextureVideo(resource.video, gl.domElement);
16150
+ return () => {
16151
+ resource.video.remove();
16152
+ };
16153
+ }, [gl, resource]);
16116
16154
  return resource;
16117
16155
  }
16118
16156
  function VideoHotspot({
@@ -16151,21 +16189,14 @@ function VideoHotspot({
16151
16189
  const onPlaybackStateChangeRef = useRef39(onPlaybackStateChange);
16152
16190
  onPlaybackErrorRef.current = onPlaybackError;
16153
16191
  onPlaybackStateChangeRef.current = onPlaybackStateChange;
16154
- useEffect38(() => {
16192
+ useLayoutEffect7(() => {
16155
16193
  if (!resource) {
16156
16194
  return;
16157
16195
  }
16158
16196
  resource.video.loop = loop;
16159
16197
  resource.video.muted = muted;
16160
16198
  resource.video.volume = clampVolume2(volume);
16161
- resource.video.playsInline = playsInline;
16162
- if (playsInline) {
16163
- resource.video.setAttribute("playsinline", "true");
16164
- resource.video.setAttribute("webkit-playsinline", "true");
16165
- } else {
16166
- resource.video.removeAttribute("playsinline");
16167
- resource.video.removeAttribute("webkit-playsinline");
16168
- }
16199
+ applyInlineVideoPlayback(resource.video, playsInline);
16169
16200
  resource.video.preload = preload;
16170
16201
  }, [loop, muted, playsInline, preload, resource, volume]);
16171
16202
  useEffect38(() => {