@ericchen1990/pano-view 2.2.2 → 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.
@@ -996,8 +1063,8 @@ color, border, corner radius, shadow, padding, and font size.
996
1063
 
997
1064
  For persistence or a host-owned editor, use the exported discriminated
998
1065
  `HotspotDefinition` union. Adding a variant is a TypeScript breaking change for
999
- exhaustive switches; handle `text` and `iframe` alongside the existing point
1000
- and path categories:
1066
+ exhaustive switches; handle `audio` alongside the existing point and path
1067
+ categories:
1001
1068
 
1002
1069
  ```ts
1003
1070
  import type { HotspotDefinition } from "@ericchen1990/pano-view";
@@ -1007,6 +1074,7 @@ const hotspots: HotspotDefinition[] = [
1007
1074
  { type: "graphic", id: "marker", position: { yaw: -18, pitch: 9 }, graphic: { kind: "ring" } },
1008
1075
  { type: "sequence", id: "pulse", position: { yaw: -42, pitch: -7 }, src: "/hotspots/pulse.png", frameCount: 20 },
1009
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 },
1010
1078
  { type: "text", id: "caption", position: { yaw: 0, pitch: -16 }, text: "Courtyard overlook" },
1011
1079
  { type: "iframe", id: "guide", position: { yaw: -62, pitch: 4 }, src: "/hotspots/embed.html" },
1012
1080
  { type: "polygon", id: "zone", vertices: [{ yaw: 12, pitch: 4 }, { yaw: 22, pitch: 4 }, { yaw: 18, pitch: 14 }] },
@@ -1281,6 +1349,55 @@ Video reports media and poster failures through `onError`. Like
1281
1349
  `SequenceHotspot`, it does not change `playing` by itself. This keeps source
1282
1350
  changes, unmounts, and React StrictMode lifecycles deterministic.
1283
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
+
1284
1401
  ## TextHotspot
1285
1402
 
1286
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。
@@ -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` 变化。