@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/README.md CHANGED
@@ -581,10 +581,12 @@ export function VideoExample() {
581
581
  ```
582
582
 
583
583
  `muted` defaults to `true` so `autoPlay` has a chance under browser autoplay
584
- rules. `playsInline` also defaults to true and writes both `playsinline` and
585
- `webkit-playsinline`, keeping the default 360-video path inline on iPhone
586
- instead of entering the native player. Unmuted playback still needs a user
587
- gesture; `onPlaybackStateChange` receives `"blocked"` when `play()` is
584
+ rules. `playsInline` also defaults to true and writes `playsinline`,
585
+ `webkit-playsinline`, and the X5 `h5-page` attributes, so the video stays in
586
+ the page on iPhone Safari and in WeChat, QQ, and UC. Picture-in-picture and
587
+ remote playback stay disabled so the element can keep feeding the sphere
588
+ texture. The element is mounted inside the viewer at 1×1px. Unmuted playback
589
+ still needs a user gesture; `onPlaybackStateChange` receives `"blocked"` when `play()` is
588
590
  rejected. Remote videos and VTT files that are not same-origin should set
589
591
  `crossOrigin` (usually `"anonymous"`). For mobile delivery, provide an H.264 +
590
592
  AAC MP4 fallback, poster, CORS, and Range support.
@@ -1641,7 +1643,9 @@ handler, `onEnded`, or playback error.
1641
1643
  `VideoHotspot` uses an `HTMLVideoElement` and `VideoTexture`. Its `playing`
1642
1644
  prop is likewise controlled: toggle it from your click handler or application
1643
1645
  state. On source change or unmount, the element is paused and its texture is
1644
- released. `playsInline` defaults to `true`, `muted` to `true`, `loop` to
1646
+ released. `playsInline` defaults to `true` and uses the same inline-playback
1647
+ attributes as `PanoVideo`. The element is mounted inside the viewer, hidden,
1648
+ so mobile browsers keep it in the page. `muted` defaults to `true`, `loop` to
1645
1649
  `false`, and `preload` to `"metadata"`.
1646
1650
 
1647
1651
  ```tsx
package/README.zh-CN.md CHANGED
@@ -538,7 +538,7 @@ export function VideoExample() {
538
538
  }
539
539
  ```
540
540
 
541
- `muted` 默认为 `true`,以便在浏览器自动播放规则下有机会自动播放;`playsInline` 也默认开启,并同时写入 `playsinline` / `webkit-playsinline`,避免 iPhone 将默认的 360 视频提取为原生全屏播放器。非静音播放仍需要用户手势;`play()` 被拒绝时 `onPlaybackStateChange` 会收到 `"blocked"`。非同源的远程视频与 VTT 文件应设置 `crossOrigin`(通常为 `"anonymous"`)。移动端请提供 H.264 + AAC 的 MP4 备选源、poster、CORS 与 Range 支持。
541
+ `muted` 默认为 `true`,以便在浏览器自动播放规则下有机会自动播放。`playsInline` 也默认开启,并写入 `playsinline`、`webkit-playsinline` 以及 X5 的 `h5-page` 属性,让视频在 iPhone Safari、微信、QQ 和 UC 中留在页面内。画中画和远程播放保持关闭,以便该元素持续作为球面纹理。元素会以 1×1 像素挂在查看器内。非静音播放仍需要用户手势;`play()` 被拒绝时 `onPlaybackStateChange` 会收到 `"blocked"`。非同源的远程视频与 VTT 文件应设置 `crossOrigin`(通常为 `"anonymous"`)。移动端请提供 H.264 + AAC 的 MP4 备选源、poster、CORS 与 Range 支持。
542
542
 
543
543
  ## Tile
544
544
 
@@ -1374,7 +1374,7 @@ import { SequenceHotspot } from "@ericchen1990/pano-view";
1374
1374
 
1375
1375
  ## VideoHotspot
1376
1376
 
1377
- `VideoHotspot` 使用 `HTMLVideoElement` 与 `VideoTexture`。其 `playing` prop 同样受控:在点击处理或应用状态中切换。源变更或卸载时元素暂停并释放纹理。`playsInline` 默认 `true`,`muted` 默认 `true`,`loop` 默认 `false`,`preload` 默认 `"metadata"`。
1377
+ `VideoHotspot` 使用 `HTMLVideoElement` 与 `VideoTexture`。其 `playing` prop 同样受控:在点击处理或应用状态中切换。源变更或卸载时元素暂停并释放纹理。`playsInline` 默认 `true`,并使用与 `PanoVideo` 相同的内联播放属性。元素会隐藏挂在查看器内,以便移动端浏览器把它留在页面中。`muted` 默认 `true`,`loop` 默认 `false`,`preload` 默认 `"metadata"`。
1378
1378
 
1379
1379
  ```tsx
1380
1380
  import { VideoHotspot } from "@ericchen1990/pano-view";
package/dist/index.cjs CHANGED
@@ -3446,7 +3446,9 @@ function KeyboardControls({
3446
3446
  return;
3447
3447
  }
3448
3448
  const previousTabIndex = element.getAttribute("tabindex");
3449
+ const previousOutline = element.style.outline;
3449
3450
  element.tabIndex = 0;
3451
+ element.style.outline = "none";
3450
3452
  const syncKeyboardActive = () => {
3451
3453
  let continuous = false;
3452
3454
  for (const action of pressedRef.current) {
@@ -3530,6 +3532,7 @@ function KeyboardControls({
3530
3532
  } else {
3531
3533
  element.setAttribute("tabindex", previousTabIndex);
3532
3534
  }
3535
+ element.style.outline = previousOutline;
3533
3536
  clearPressed();
3534
3537
  };
3535
3538
  }, [controlsRef, enabled, gl, keyMap]);
@@ -9093,6 +9096,56 @@ function useWebVRPointerLock(enabled, element) {
9093
9096
  // src/webvr/wakelock.ts
9094
9097
  var import_react33 = require("react");
9095
9098
 
9099
+ // src/video/inline-playback.ts
9100
+ var INLINE_VIDEO_ATTRIBUTES = [
9101
+ ["playsinline", "true"],
9102
+ ["webkit-playsinline", "true"],
9103
+ ["x5-playsinline", "true"],
9104
+ ["x5-video-player-type", "h5-page"],
9105
+ ["x5-video-player-fullscreen", "false"]
9106
+ ];
9107
+ function applyInlineVideoPlayback(video, inline) {
9108
+ video.controls = false;
9109
+ video.playsInline = inline;
9110
+ video.disablePictureInPicture = true;
9111
+ video.disableRemotePlayback = true;
9112
+ video.setAttribute("disablepictureinpicture", "");
9113
+ video.setAttribute("disableremoteplayback", "");
9114
+ video.setAttribute("x-webkit-airplay", "deny");
9115
+ video.setAttribute(
9116
+ "controlsList",
9117
+ "nodownload nofullscreen noremoteplayback"
9118
+ );
9119
+ if (inline) {
9120
+ for (const [name, value] of INLINE_VIDEO_ATTRIBUTES) {
9121
+ video.setAttribute(name, value);
9122
+ }
9123
+ return;
9124
+ }
9125
+ for (const [name] of INLINE_VIDEO_ATTRIBUTES) {
9126
+ video.removeAttribute(name);
9127
+ }
9128
+ }
9129
+ function mountTextureVideo(video, canvas) {
9130
+ const parent = canvas.closest("[data-pano-viewer]") ?? canvas.parentElement;
9131
+ if (!parent) {
9132
+ return;
9133
+ }
9134
+ video.tabIndex = -1;
9135
+ video.setAttribute("aria-hidden", "true");
9136
+ video.setAttribute("data-pano-texture-video", "");
9137
+ video.style.position = "absolute";
9138
+ video.style.top = "0";
9139
+ video.style.left = "0";
9140
+ video.style.width = "1px";
9141
+ video.style.height = "1px";
9142
+ video.style.opacity = "0";
9143
+ video.style.pointerEvents = "none";
9144
+ if (video.parentElement !== parent) {
9145
+ parent.appendChild(video);
9146
+ }
9147
+ }
9148
+
9096
9149
  // src/webvr/wakelock-fallback.ts
9097
9150
  var WEBVR_WAKELOCK_FALLBACK_VIDEO_BASE64 = "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";
9098
9151
 
@@ -9118,17 +9171,12 @@ function decodeBase64(value) {
9118
9171
  function createFallbackVideo() {
9119
9172
  const video = document.createElement("video");
9120
9173
  video.autoplay = true;
9121
- video.controls = false;
9122
9174
  video.defaultMuted = true;
9123
9175
  video.loop = true;
9124
9176
  video.muted = true;
9125
- video.playsInline = true;
9126
9177
  video.tabIndex = -1;
9127
9178
  video.ariaHidden = "true";
9128
- video.disablePictureInPicture = true;
9129
- video.setAttribute("playsinline", "");
9130
- video.setAttribute("webkit-playsinline", "");
9131
- video.setAttribute("disablepictureinpicture", "");
9179
+ applyInlineVideoPlayback(video, true);
9132
9180
  video.style.cssText = "pointer-events:none;position:fixed;bottom:0;left:0;width:1px;height:1px;opacity:0;";
9133
9181
  const src = URL.createObjectURL(
9134
9182
  new Blob([decodeBase64(WEBVR_WAKELOCK_FALLBACK_VIDEO_BASE64)], {
@@ -11063,7 +11111,6 @@ function PanoVideo({
11063
11111
  usePanoMediaActivationIntent(autoPlay);
11064
11112
  const gl = (0, import_fiber18.useThree)((state) => state.gl);
11065
11113
  const host = (0, import_react42.useContext)(PanoVideoHostContext);
11066
- const overlay = (0, import_react42.useContext)(PanoChromeOverlayContext);
11067
11114
  const variants = (0, import_react42.useMemo)(
11068
11115
  () => resolvePanoVideoVariants({ poster, src, variants: variantsProp }),
11069
11116
  [poster, src, variantsProp]
@@ -11141,9 +11188,7 @@ function PanoVideo({
11141
11188
  }
11142
11189
  const video = document.createElement("video");
11143
11190
  video.muted = true;
11144
- video.playsInline = true;
11145
- video.setAttribute("playsinline", "true");
11146
- video.setAttribute("webkit-playsinline", "true");
11191
+ applyInlineVideoPlayback(video, true);
11147
11192
  video.preload = "metadata";
11148
11193
  const texture = new import_three16.VideoTexture(video);
11149
11194
  applyEquirectangularLayout(texture, 1);
@@ -11173,22 +11218,15 @@ function PanoVideo({
11173
11218
  }
11174
11219
  resource.texture.anisotropy = gl.capabilities.getMaxAnisotropy();
11175
11220
  }, [gl, resource]);
11176
- (0, import_react42.useEffect)(() => {
11177
- if (!resource || !overlay?.overlayElement) {
11221
+ (0, import_react42.useLayoutEffect)(() => {
11222
+ if (!resource) {
11178
11223
  return;
11179
11224
  }
11180
- const { video } = resource;
11181
- video.setAttribute("data-pano-video", "");
11182
- video.style.position = "absolute";
11183
- video.style.width = "1px";
11184
- video.style.height = "1px";
11185
- video.style.opacity = "0";
11186
- video.style.pointerEvents = "none";
11187
- overlay.overlayElement.appendChild(video);
11225
+ mountTextureVideo(resource.video, gl.domElement);
11188
11226
  return () => {
11189
- video.remove();
11227
+ resource.video.remove();
11190
11228
  };
11191
- }, [overlay?.overlayElement, resource]);
11229
+ }, [gl, resource]);
11192
11230
  const resolvedVariantId = resolvePanoVideoVariantId(variants, variantId);
11193
11231
  const activeVariant = variants.find((variant) => variant.id === resolvedVariantId) ?? variants[0];
11194
11232
  const mediaKey = activeVariant ? `${activeVariant.id}:${activeVariant.sources.map((source) => source.src).join("|")}:${tracks.map((track) => `${panoVideoTrackId(track)}:${track.src}`).join("|")}:${crossOrigin ?? ""}` : "";
@@ -11478,7 +11516,7 @@ function PanoVideo({
11478
11516
  }
11479
11517
  };
11480
11518
  }, [emitSnapshot, resource]);
11481
- (0, import_react42.useEffect)(() => {
11519
+ (0, import_react42.useLayoutEffect)(() => {
11482
11520
  if (!resource) {
11483
11521
  return;
11484
11522
  }
@@ -11488,14 +11526,7 @@ function PanoVideo({
11488
11526
  if (mediaCapabilitiesRef.current.volumeAdjustable) {
11489
11527
  video.volume = clampVolume(volume);
11490
11528
  }
11491
- video.playsInline = playsInline;
11492
- if (playsInline) {
11493
- video.setAttribute("playsinline", "true");
11494
- video.setAttribute("webkit-playsinline", "true");
11495
- } else {
11496
- video.removeAttribute("playsinline");
11497
- video.removeAttribute("webkit-playsinline");
11498
- }
11529
+ applyInlineVideoPlayback(video, playsInline);
11499
11530
  video.preload = preload;
11500
11531
  }, [loop, muted, playsInline, preload, resource, volume]);
11501
11532
  (0, import_react42.useEffect)(() => {
@@ -15862,6 +15893,7 @@ function useVideoResource({
15862
15893
  poster,
15863
15894
  src
15864
15895
  }) {
15896
+ const gl = (0, import_fiber29.useThree)((state) => state.gl);
15865
15897
  const [resource, setResource] = (0, import_react57.useState)(null);
15866
15898
  const onEndedRef = (0, import_react57.useRef)(onEnded);
15867
15899
  const onErrorRef = (0, import_react57.useRef)(onError);
@@ -15879,12 +15911,7 @@ function useVideoResource({
15879
15911
  if (crossOrigin !== void 0) {
15880
15912
  video.crossOrigin = crossOrigin;
15881
15913
  }
15882
- const inline = playsInline ?? true;
15883
- video.playsInline = inline;
15884
- if (inline) {
15885
- video.setAttribute("playsinline", "true");
15886
- video.setAttribute("webkit-playsinline", "true");
15887
- }
15914
+ applyInlineVideoPlayback(video, playsInline ?? true);
15888
15915
  video.poster = poster ?? "";
15889
15916
  const texture = new import_three35.VideoTexture(video);
15890
15917
  texture.colorSpace = import_three35.SRGBColorSpace;
@@ -15926,6 +15953,7 @@ function useVideoResource({
15926
15953
  video.addEventListener("error", reportError);
15927
15954
  video.addEventListener("pause", handlePause);
15928
15955
  video.addEventListener("play", handlePlay);
15956
+ mountTextureVideo(video, gl.domElement);
15929
15957
  setResource({ video, texture, ready: false });
15930
15958
  video.src = src;
15931
15959
  video.load();
@@ -15939,9 +15967,19 @@ function useVideoResource({
15939
15967
  video.pause();
15940
15968
  video.removeAttribute("src");
15941
15969
  video.load();
15970
+ video.remove();
15942
15971
  texture.dispose();
15943
15972
  };
15944
- }, [crossOrigin, id, playsInline, poster, src]);
15973
+ }, [crossOrigin, gl, id, playsInline, poster, src]);
15974
+ (0, import_react57.useLayoutEffect)(() => {
15975
+ if (!resource) {
15976
+ return;
15977
+ }
15978
+ mountTextureVideo(resource.video, gl.domElement);
15979
+ return () => {
15980
+ resource.video.remove();
15981
+ };
15982
+ }, [gl, resource]);
15945
15983
  return resource;
15946
15984
  }
15947
15985
  function VideoHotspot({
@@ -15980,21 +16018,14 @@ function VideoHotspot({
15980
16018
  const onPlaybackStateChangeRef = (0, import_react57.useRef)(onPlaybackStateChange);
15981
16019
  onPlaybackErrorRef.current = onPlaybackError;
15982
16020
  onPlaybackStateChangeRef.current = onPlaybackStateChange;
15983
- (0, import_react57.useEffect)(() => {
16021
+ (0, import_react57.useLayoutEffect)(() => {
15984
16022
  if (!resource) {
15985
16023
  return;
15986
16024
  }
15987
16025
  resource.video.loop = loop;
15988
16026
  resource.video.muted = muted;
15989
16027
  resource.video.volume = clampVolume2(volume);
15990
- resource.video.playsInline = playsInline;
15991
- if (playsInline) {
15992
- resource.video.setAttribute("playsinline", "true");
15993
- resource.video.setAttribute("webkit-playsinline", "true");
15994
- } else {
15995
- resource.video.removeAttribute("playsinline");
15996
- resource.video.removeAttribute("webkit-playsinline");
15997
- }
16028
+ applyInlineVideoPlayback(resource.video, playsInline);
15998
16029
  resource.video.preload = preload;
15999
16030
  }, [loop, muted, playsInline, preload, resource, volume]);
16000
16031
  (0, import_react57.useEffect)(() => {