@ericchen1990/pano-view 2.2.1 → 2.3.0

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
@@ -81,6 +81,7 @@ the stylesheet, the overlays still mount, but they render unstyled.
81
81
  - [`PanoVideo`](#panovideo) — 2:1 equirectangular video on the panorama sphere
82
82
  - [`Tile`](#tile) — krpano-style multires cube tiles
83
83
  - [`Scenes`](#scenes) — controlled multi-scene transitions
84
+ - [`BackgroundAudio`](#backgroundaudio) — one shared track, or a different track per scene
84
85
  - [`PanoFilter`](#panofilter) — color and artistic filters on the panorama source
85
86
 
86
87
  ### Controls
@@ -104,6 +105,7 @@ the stylesheet, the overlays still mount, but they render unstyled.
104
105
  - [`GraphicHotspot`](#graphichotspot) — built-in shapes or SVG at a position
105
106
  - [`SequenceHotspot`](#sequencehotspot) — sprite-sheet animation
106
107
  - [`VideoHotspot`](#videohotspot) — HTML video texture
108
+ - [`AudioHotspot`](#audiohotspot) — directional sound at a spherical position
107
109
  - [`TextHotspot`](#texthotspot) — rasterized plain text at a position
108
110
  - [`IframeHotspot`](#iframehotspot) — embedded document overlay at a position
109
111
  - [`PolygonHotspot`](#polygonhotspot) — closed spherical area
@@ -500,6 +502,71 @@ Auto-rotation is off by default. Render `AutoRotate` inside `PanoViewer` to enab
500
502
  </PanoViewer>
501
503
  ```
502
504
 
505
+ ## BackgroundAudio
506
+
507
+ `BackgroundAudio` plays background music or ambience for the viewer. It is not
508
+ a hotspot: there is no spherical position, no marker, and volume does not
509
+ follow the camera. Render it as a child of `PanoViewer`, **alongside** `Scenes`,
510
+ `Sphere`, or `Tile`. Do not put it in `Scenes`' `renderHotspots` — hotspots
511
+ unmount during scene transitions and would stop the audio.
512
+
513
+ `playing` is controlled by the host; the component does not change it. `loop`
514
+ defaults to `true`. Browsers may block autoplay; `onPlaybackStateChange` then
515
+ receives `"blocked"`, and the component retries `play()` after the next click
516
+ or keypress while `playing` remains true.
517
+
518
+ Pick one of the two patterns.
519
+
520
+ **One track for every scene** — pass `src` only; omit `sources`. Scene changes
521
+ do not restart the audio.
522
+
523
+ ```tsx
524
+ import { BackgroundAudio, PanoViewer, Scenes } from "@ericchen1990/pano-view";
525
+
526
+ <PanoViewer style={{ height: 560 }}>
527
+ <BackgroundAudio src="/bgm/tour.mp3" playing={isBgmPlaying} />
528
+ <Scenes scenes={scenes} activeSceneId={activeSceneId} />
529
+ </PanoViewer>;
530
+ ```
531
+
532
+ **A different track per scene** — pass `sources` (keys are `Scene.id`) and the
533
+ current `sceneId`. Omitting `sceneId` throws. When the file changes, playback
534
+ crossfades over `fadeMs` (default 400 ms; `0` is a hard cut). When two scenes
535
+ point at the same file, playback continues without restarting.
536
+
537
+ ```tsx
538
+ <PanoViewer style={{ height: 560 }}>
539
+ <BackgroundAudio
540
+ sources={{
541
+ lobby: "/bgm/lobby.mp3",
542
+ terrace: "/bgm/terrace.mp3",
543
+ }}
544
+ sceneId={activeSceneId}
545
+ playing={isBgmPlaying}
546
+ />
547
+ <Scenes scenes={scenes} activeSceneId={activeSceneId} />
548
+ </PanoViewer>;
549
+ ```
550
+
551
+ Optional: also pass `src` as the default for scenes missing from `sources`. To
552
+ silence a scene, set that id to `""` (this overrides `src`; it does not fall
553
+ back to the default track).
554
+
555
+ ```tsx
556
+ <BackgroundAudio
557
+ src="/bgm/default.mp3"
558
+ sources={{ lobby: "/bgm/courtyard.mp3", terrace: "" }}
559
+ sceneId={activeSceneId}
560
+ playing={isBgmPlaying}
561
+ />;
562
+ ```
563
+
564
+ In that example, `lobby` plays the courtyard clip, `terrace` is silent, and
565
+ every other scene plays `default.mp3`.
566
+
567
+ For a sound attached to a point in the panorama that pans with the view, use
568
+ `AudioHotspot`, not `BackgroundAudio`.
569
+
503
570
  ## Gyro
504
571
 
505
572
  `Gyro` is an opt-in device-orientation control and is disabled by default.
@@ -810,10 +877,11 @@ xml/VR/frame-render events.
810
877
 
811
878
  ## PanoContextMenu
812
879
 
813
- `PanoViewer` mounts a default context menu that replaces the browser menu on
814
- right-click. Default items: **Reset view** and **Enter fullscreen** / **Exit
815
- fullscreen** (label and icon follow the current fullscreen state), with a
816
- separator between them.
880
+ `PanoViewer` always suppresses the browser's native context menu on the
881
+ viewer (canvas, chrome, and other overlays). It also mounts a default
882
+ context menu on right-click. Default items: **Reset view** and **Enter
883
+ fullscreen** / **Exit fullscreen** (label and icon follow the current
884
+ fullscreen state), with a separator between them.
817
885
 
818
886
  ```tsx
819
887
  <PanoViewer style={{ height: 560 }}>
@@ -821,7 +889,8 @@ separator between them.
821
889
  </PanoViewer>
822
890
  ```
823
891
 
824
- Disable the default menu (restore the browser menu, or mount your own):
892
+ Disable the default menu (the native browser menu stays suppressed; mount
893
+ your own `PanoContextMenu` if you still want a custom menu):
825
894
 
826
895
  ```tsx
827
896
  <PanoViewer contextMenu={false}>
@@ -994,8 +1063,8 @@ color, border, corner radius, shadow, padding, and font size.
994
1063
 
995
1064
  For persistence or a host-owned editor, use the exported discriminated
996
1065
  `HotspotDefinition` union. Adding a variant is a TypeScript breaking change for
997
- exhaustive switches; handle `text` and `iframe` alongside the existing point
998
- and path categories:
1066
+ exhaustive switches; handle `audio` alongside the existing point and path
1067
+ categories:
999
1068
 
1000
1069
  ```ts
1001
1070
  import type { HotspotDefinition } from "@ericchen1990/pano-view";
@@ -1005,6 +1074,7 @@ const hotspots: HotspotDefinition[] = [
1005
1074
  { type: "graphic", id: "marker", position: { yaw: -18, pitch: 9 }, graphic: { kind: "ring" } },
1006
1075
  { type: "sequence", id: "pulse", position: { yaw: -42, pitch: -7 }, src: "/hotspots/pulse.png", frameCount: 20 },
1007
1076
  { type: "video", id: "clip", position: { yaw: 48, pitch: 6 }, src: "/hotspots/clip.webm" },
1077
+ { type: "audio", id: "fountain", position: { yaw: -30, pitch: -8 }, src: "/hotspots/fountain.mp3", range: 90 },
1008
1078
  { type: "text", id: "caption", position: { yaw: 0, pitch: -16 }, text: "Courtyard overlook" },
1009
1079
  { type: "iframe", id: "guide", position: { yaw: -62, pitch: 4 }, src: "/hotspots/embed.html" },
1010
1080
  { type: "polygon", id: "zone", vertices: [{ yaw: 12, pitch: 4 }, { yaw: 22, pitch: 4 }, { yaw: 18, pitch: 14 }] },
@@ -1279,6 +1349,55 @@ Video reports media and poster failures through `onError`. Like
1279
1349
  `SequenceHotspot`, it does not change `playing` by itself. This keeps source
1280
1350
  changes, unmounts, and React StrictMode lifecycles deterministic.
1281
1351
 
1352
+ ## AudioHotspot
1353
+
1354
+ `AudioHotspot` plays a directional sound at a spherical position. Stereo
1355
+ panning follows the camera look direction; `range` is the look-away angle in
1356
+ degrees at which volume reaches silence. The default `360` disables look
1357
+ attenuation (stereo pan only). Use a smaller `range` such as `90` for a
1358
+ spot source.
1359
+
1360
+ Playback uses Web Audio via Howler.js (bundled; hosts do not install
1361
+ `howler`). The file must decode before play starts — MP3 is the recommended
1362
+ format. `src` may be a string or an array of fallback URLs. Do not use HTML5
1363
+ streaming (`html5`); it drops stereo panning.
1364
+
1365
+ `playing` is controlled like `VideoHotspot`. `loop` defaults to `false`,
1366
+ `muted` to `false`, `volume` to `1`, and `pauseWhenHidden` to `true` (pause
1367
+ when the tab is hidden, resume if `playing` is still true). A first user
1368
+ gesture unlocks audio; if autoplay is blocked, the hotspot reports
1369
+ `"blocked"` and retries `play()` after the next pointer or key event while
1370
+ `playing` remains true.
1371
+
1372
+ The default marker switches between a built-in **stopped** icon (speaker)
1373
+ and a **playing** icon (speaker plus sound waves). Pass `icon`
1374
+ for a custom stopped image and `playingIcon` for a custom playing image.
1375
+ If only `icon` is set, it is used for both states. `marker={false}` hides
1376
+ the visual for an invisible positional source. For a
1377
+ non-spatial tour or per-scene soundtrack, use [`BackgroundAudio`](#backgroundaudio).
1378
+
1379
+ ```tsx
1380
+ import { AudioHotspot } from "@ericchen1990/pano-view";
1381
+
1382
+ <AudioHotspot
1383
+ id="fountain"
1384
+ ariaLabel="Fountain"
1385
+ position={{ yaw: -30, pitch: -8 }}
1386
+ src="/hotspots/fountain.mp3"
1387
+ playing={isFountainPlaying}
1388
+ loop
1389
+ volume={0.8}
1390
+ range={90}
1391
+ onClick={() => setFountainPlaying((playing) => !playing)}
1392
+ onEnded={() => setFountainPlaying(false)}
1393
+ onPlaybackStateChange={(state) => console.log(state)}
1394
+ onPlaybackError={(error) => console.error(error)}
1395
+ />;
1396
+ ```
1397
+
1398
+ Like the other media hotspots, `AudioHotspot` does not change `playing` by
1399
+ itself.
1400
+
1282
1401
  ## TextHotspot
1283
1402
 
1284
1403
  `TextHotspot` rasterizes plain text onto a canvas texture. It does not accept
package/README.zh-CN.md CHANGED
@@ -64,6 +64,7 @@ import "@ericchen1990/pano-view/styles.css";
64
64
  - [`PanoVideo`](#panovideo) — 全景球面上的 2:1 等距圆柱视频
65
65
  - [`Tile`](#tile) — krpano 风格多分辨率立方体 tile
66
66
  - [`Scenes`](#scenes) — 受控多场景过渡
67
+ - [`BackgroundAudio`](#backgroundaudio) — 全部场景共用、或按场景切换的背景声
67
68
  - [`PanoFilter`](#panofilter) — 对全景源应用色彩与艺术滤镜
68
69
 
69
70
  ### 控件
@@ -87,6 +88,7 @@ import "@ericchen1990/pano-view/styles.css";
87
88
  - [`GraphicHotspot`](#graphichotspot) — 内置形状或 SVG
88
89
  - [`SequenceHotspot`](#sequencehotspot) — 精灵图动画
89
90
  - [`VideoHotspot`](#videohotspot) — HTML 视频纹理
91
+ - [`AudioHotspot`](#audiohotspot) — 球面位置上的定向声源
90
92
  - [`TextHotspot`](#texthotspot) — 球面位置上的栅格化纯文本
91
93
  - [`IframeHotspot`](#iframehotspot) — 球面位置上的嵌入文档叠加层
92
94
  - [`PolygonHotspot`](#polygonhotspot) — 闭合球面区域
@@ -418,6 +420,56 @@ const scenes: Scene[] = [
418
420
  </PanoViewer>
419
421
  ```
420
422
 
423
+ ## BackgroundAudio
424
+
425
+ `BackgroundAudio` 在查看器里播放背景音乐或环境声。它不是热点:没有球面位置、没有标记、音量也不随视角变化。必须作为 `PanoViewer` 的子组件,与 `Scenes`、`Sphere` 或 `Tile` **并列**。不要写进 `Scenes` 的 `renderHotspots`:场景过渡时热点会卸载,背景声也会被停掉。
426
+
427
+ `playing` 由宿主控制,组件不会自行改这个值。`loop` 默认 `true`。浏览器可能拦住自动播放;此时 `onPlaybackStateChange` 收到 `"blocked"`,在 `playing` 仍为 true 时会于下一次点击或按键后重试。
428
+
429
+ 有两种用法,选一种即可。
430
+
431
+ **全部场景共用一条音轨** — 只传 `src`,不要传 `sources`。切换场景时音频继续播放,不会重头开始。
432
+
433
+ ```tsx
434
+ import { BackgroundAudio, PanoViewer, Scenes } from "@ericchen1990/pano-view";
435
+
436
+ <PanoViewer style={{ height: 560 }}>
437
+ <BackgroundAudio src="/bgm/tour.mp3" playing={isBgmPlaying} />
438
+ <Scenes scenes={scenes} activeSceneId={activeSceneId} />
439
+ </PanoViewer>;
440
+ ```
441
+
442
+ **每个场景一条音轨** — 传 `sources`(键为 `Scene.id`)和当前的 `sceneId`。省略 `sceneId` 会抛错。切换场景时,若文件变了会按 `fadeMs` 交叉淡化(默认 400 毫秒;`0` 为硬切);若两个场景指向同一文件则继续播,不会重启。
443
+
444
+ ```tsx
445
+ <PanoViewer style={{ height: 560 }}>
446
+ <BackgroundAudio
447
+ sources={{
448
+ lobby: "/bgm/lobby.mp3",
449
+ terrace: "/bgm/terrace.mp3",
450
+ }}
451
+ sceneId={activeSceneId}
452
+ playing={isBgmPlaying}
453
+ />
454
+ <Scenes scenes={scenes} activeSceneId={activeSceneId} />
455
+ </PanoViewer>;
456
+ ```
457
+
458
+ 可选:同时传 `src` 作为未列出场景的默认音轨。某个场景要静音时,把该 id 写成 `""`(会覆盖 `src`,而不是回退到默认轨)。
459
+
460
+ ```tsx
461
+ <BackgroundAudio
462
+ src="/bgm/default.mp3"
463
+ sources={{ lobby: "/bgm/courtyard.mp3", terrace: "" }}
464
+ sceneId={activeSceneId}
465
+ playing={isBgmPlaying}
466
+ />;
467
+ ```
468
+
469
+ 上例中:`lobby` 播院子声,`terrace` 静音,其它场景播 `default.mp3`。
470
+
471
+ 需要绑在全景某一点、随视角左右移动的声音,请用 `AudioHotspot`,不要用 `BackgroundAudio`。
472
+
421
473
  ## Gyro
422
474
 
423
475
  `Gyro` 为可选的设备方向控制,默认关闭。在 `PanoViewer` 内渲染并通过 `enabled` 控制。相对模式(默认)将启用时的视图作为起始方向。设 `absolute` 可跟随罗盘朝向,用 `north` 指定指向北方的全景 yaw。
@@ -678,7 +730,7 @@ import {
678
730
 
679
731
  ## PanoContextMenu
680
732
 
681
- `PanoViewer` 挂载默认右键菜单,右键时替换浏览器菜单。默认项:**Reset view** 与 **Enter fullscreen** / **Exit fullscreen**(文案与图标随全屏状态变化),中间有分隔线。
733
+ `PanoViewer` 会始终屏蔽浏览器原生右键菜单(画布、chrome 及其他 overlay 均生效),并挂载默认右键菜单。默认项:**Reset view** 与 **Enter fullscreen** / **Exit fullscreen**(文案与图标随全屏状态变化),中间有分隔线。
682
734
 
683
735
  ```tsx
684
736
  <PanoViewer style={{ height: 560 }}>
@@ -686,7 +738,7 @@ import {
686
738
  </PanoViewer>
687
739
  ```
688
740
 
689
- 关闭默认菜单(恢复浏览器菜单,或挂载自定义菜单):
741
+ 关闭默认菜单(浏览器原生菜单仍会被屏蔽;若仍需自定义菜单,请自行挂载 `PanoContextMenu`):
690
742
 
691
743
  ```tsx
692
744
  <PanoViewer contextMenu={false}>
@@ -818,7 +870,7 @@ import {
818
870
  />
819
871
  ```
820
872
 
821
- 持久化或宿主编辑器可使用导出的判别联合类型 `HotspotDefinition`。新增变体会破坏 exhaustive switch 的类型兼容;除现有点与路径类别外,还需处理 `text` 与 `iframe`:
873
+ 持久化或宿主编辑器可使用导出的判别联合类型 `HotspotDefinition`。新增变体会破坏 exhaustive switch 的类型兼容;除现有点与路径类别外,还需处理 `audio`:
822
874
 
823
875
  ```ts
824
876
  import type { HotspotDefinition } from "@ericchen1990/pano-view";
@@ -828,6 +880,7 @@ const hotspots: HotspotDefinition[] = [
828
880
  { type: "graphic", id: "marker", position: { yaw: -18, pitch: 9 }, graphic: { kind: "ring" } },
829
881
  { type: "sequence", id: "pulse", position: { yaw: -42, pitch: -7 }, src: "/hotspots/pulse.png", frameCount: 20 },
830
882
  { type: "video", id: "clip", position: { yaw: 48, pitch: 6 }, src: "/hotspots/clip.webm" },
883
+ { type: "audio", id: "fountain", position: { yaw: -30, pitch: -8 }, src: "/hotspots/fountain.mp3", range: 90 },
831
884
  { type: "text", id: "caption", position: { yaw: 0, pitch: -16 }, text: "Courtyard overlook" },
832
885
  { type: "iframe", id: "guide", position: { yaw: -62, pitch: 4 }, src: "/hotspots/embed.html" },
833
886
  { type: "polygon", id: "zone", vertices: [{ yaw: 12, pitch: 4 }, { yaw: 22, pitch: 4 }, { yaw: 18, pitch: 14 }] },
@@ -1047,6 +1100,37 @@ import { VideoHotspot } from "@ericchen1990/pano-view";
1047
1100
 
1048
1101
  视频通过 `onError` 报告媒体与 poster 失败。与 `SequenceHotspot` 相同,不会自行改变 `playing`,以保证源变更、卸载与 React StrictMode 生命周期可预测。
1049
1102
 
1103
+ ## AudioHotspot
1104
+
1105
+ `AudioHotspot` 在球面位置播放定向声音。立体声像跟随相机朝向;`range` 为看离声源多少度后音量降到静音。默认 `360` 表示不因视角衰减(仅左右声像)。定点声源可使用较小值,例如 `90`。
1106
+
1107
+ 播放走 Howler.js 的 Web Audio(已打进本包,宿主不必安装 `howler`)。文件需先解码再起播,推荐 MP3。`src` 可以是字符串或回退 URL 数组。不要用 HTML5 流式播放,否则会失去立体声像。
1108
+
1109
+ `playing` 与 `VideoHotspot` 一样受控。`loop` 默认 `false`,`muted` 默认 `false`,`volume` 默认 `1`,`pauseWhenHidden` 默认 `true`(标签页隐藏时暂停,仍为 `playing` 则回来后恢复)。首次用户手势会解锁音频;若自动播放被拦,会报告 `"blocked"`,并在 `playing` 仍为 true 时于下一次指针或按键后重试 `play()`。
1110
+
1111
+ 默认标记在 **停止**(扬声器)与 **播放**(扬声器 + 声波)之间切换。可用 `icon` 自定义停止图标、`playingIcon` 自定义播放图标。只设 `icon` 时两种状态共用该图。`marker={false}` 则无视觉,仍作为定点声源。非空间的全 tour 或按场景背景声请使用 [`BackgroundAudio`](#backgroundaudio)。
1112
+
1113
+ ```tsx
1114
+ import { AudioHotspot } from "@ericchen1990/pano-view";
1115
+
1116
+ <AudioHotspot
1117
+ id="fountain"
1118
+ ariaLabel="Fountain"
1119
+ position={{ yaw: -30, pitch: -8 }}
1120
+ src="/hotspots/fountain.mp3"
1121
+ playing={isFountainPlaying}
1122
+ loop
1123
+ volume={0.8}
1124
+ range={90}
1125
+ onClick={() => setFountainPlaying((playing) => !playing)}
1126
+ onEnded={() => setFountainPlaying(false)}
1127
+ onPlaybackStateChange={(state) => console.log(state)}
1128
+ onPlaybackError={(error) => console.error(error)}
1129
+ />;
1130
+ ```
1131
+
1132
+ 与其他媒体热点相同,`AudioHotspot` 不会自行改变 `playing`。
1133
+
1050
1134
  ## TextHotspot
1051
1135
 
1052
1136
  `TextHotspot` 将纯文本栅格化到 canvas 纹理。不接受 HTML 或 Markdown。`\n` 为硬换行;`whiteSpace="normal"`(默认)在热点角向宽度内自动换行。尺寸与其他点热点一样为度。排版使用与 CSS 相同的名称(`fontFamily`、`fontSize`、`fontWeight`、`fontStyle`),但绘制在 canvas 上,非 DOM 样式。`fontSize` 为 canvas 像素(默认 96),不随面板高度缩放。`fontWeight` 默认 `600`,`fontStyle` 默认 `"normal"`。`padding` 与 `borderRadius` 仍为纹理较短边的比例,故仍随 `width` / `height` 变化。