@realsee/vreo 0.1.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.
Files changed (126) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +465 -0
  3. package/docs/.nojekyll +1 -0
  4. package/docs/assets/custom.css +52 -0
  5. package/docs/assets/highlight.css +127 -0
  6. package/docs/assets/icons.css +1043 -0
  7. package/docs/assets/icons.png +0 -0
  8. package/docs/assets/icons@2x.png +0 -0
  9. package/docs/assets/main.js +52 -0
  10. package/docs/assets/search.js +1 -0
  11. package/docs/assets/style.css +1388 -0
  12. package/docs/assets/widgets.png +0 -0
  13. package/docs/assets/widgets@2x.png +0 -0
  14. package/docs/classes/Player.Player-1.html +50 -0
  15. package/docs/enums/Player.InfoPanelTypeEnum.html +3 -0
  16. package/docs/enums/Player.PanoEffectEnum.html +5 -0
  17. package/docs/enums/Player.PanoTagEnum.html +3 -0
  18. package/docs/enums/Player.VreoKeyframeEnum.html +388 -0
  19. package/docs/enums/fivePlugins.CameraMovementEffect.html +1 -0
  20. package/docs/enums/fivePlugins.Rotation.html +1 -0
  21. package/docs/index.html +204 -0
  22. package/docs/interfaces/Player.PlayerConfigs.html +4 -0
  23. package/docs/interfaces/Player.Vertex.html +3 -0
  24. package/docs/interfaces/Player.VreoKeyframe.html +3 -0
  25. package/docs/interfaces/Player.VreoUnit.html +3 -0
  26. package/docs/interfaces/Player.VreoVideo.html +3 -0
  27. package/docs/interfaces/fivePlugins.CameraMovementOptsCallback.html +1 -0
  28. package/docs/interfaces/fivePlugins.CameraMovementPluginExportType.html +7 -0
  29. package/docs/interfaces/fivePlugins.CameraMovementPluginParameterType.html +1 -0
  30. package/docs/interfaces/fivePlugins.ModelTVVideoPluginData.html +1 -0
  31. package/docs/interfaces/fivePlugins.ModelTVVideoPluginExportType.html +1 -0
  32. package/docs/interfaces/fivePlugins.ModelTVVideoPluginParameterType.html +1 -0
  33. package/docs/interfaces/react.VreoActionCallbacks.html +15 -0
  34. package/docs/modules/Player.html +29 -0
  35. package/docs/modules/fivePlugins.html +3 -0
  36. package/docs/modules/react.html +1 -0
  37. package/docs/modules.html +1 -0
  38. package/lib/Player/App.d.ts +2 -0
  39. package/lib/Player/App.js +68 -0
  40. package/lib/Player/Controller.d.ts +39 -0
  41. package/lib/Player/Controller.js +226 -0
  42. package/lib/Player/hooks.d.ts +4 -0
  43. package/lib/Player/hooks.js +41 -0
  44. package/lib/Player/index.d.ts +15 -0
  45. package/lib/Player/index.js +191 -0
  46. package/lib/Player/modules/Drawer/index.d.ts +2 -0
  47. package/lib/Player/modules/Drawer/index.js +67 -0
  48. package/lib/Player/modules/VideoAgent/VideoAgentMesh.d.ts +30 -0
  49. package/lib/Player/modules/VideoAgent/VideoAgentMesh.js +324 -0
  50. package/lib/Player/modules/VideoAgent/VideoAgentScene.d.ts +10 -0
  51. package/lib/Player/modules/VideoAgent/VideoAgentScene.js +65 -0
  52. package/lib/Player/modules/VideoAgent/index.d.ts +4 -0
  53. package/lib/Player/modules/VideoAgent/index.js +44 -0
  54. package/lib/Player/modules/keyframes/CameraMovement/index.d.ts +2 -0
  55. package/lib/Player/modules/keyframes/CameraMovement/index.js +73 -0
  56. package/lib/Player/modules/keyframes/InfoPanel/index.d.ts +2 -0
  57. package/lib/Player/modules/keyframes/InfoPanel/index.js +89 -0
  58. package/lib/Player/modules/keyframes/ModelVideo/index.d.ts +2 -0
  59. package/lib/Player/modules/keyframes/ModelVideo/index.js +87 -0
  60. package/lib/Player/modules/keyframes/PanoEffect/index.d.ts +2 -0
  61. package/lib/Player/modules/keyframes/PanoEffect/index.js +142 -0
  62. package/lib/Player/modules/keyframes/PanoEffect/lineAnime.d.ts +5 -0
  63. package/lib/Player/modules/keyframes/PanoEffect/lineAnime.js +46 -0
  64. package/lib/Player/modules/keyframes/PanoTag/index.d.ts +2 -0
  65. package/lib/Player/modules/keyframes/PanoTag/index.js +168 -0
  66. package/lib/Player/modules/keyframes/PanoTextLabel/index.d.ts +2 -0
  67. package/lib/Player/modules/keyframes/PanoTextLabel/index.js +113 -0
  68. package/lib/Player/modules/keyframes/Prompter/index.d.ts +2 -0
  69. package/lib/Player/modules/keyframes/Prompter/index.js +105 -0
  70. package/lib/Player/modules/keyframes/UpdateVRPanorama/index.d.ts +2 -0
  71. package/lib/Player/modules/keyframes/UpdateVRPanorama/index.js +51 -0
  72. package/lib/Player/modules/keyframes/VideoEffect/index.d.ts +2 -0
  73. package/lib/Player/modules/keyframes/VideoEffect/index.js +176 -0
  74. package/lib/Player/typings.d.ts +14 -0
  75. package/lib/Player/typings.js +5 -0
  76. package/lib/fivePlugins/CameraMovementPlugin/index.d.ts +6 -0
  77. package/lib/fivePlugins/CameraMovementPlugin/index.js +307 -0
  78. package/lib/fivePlugins/CameraMovementPlugin/typings.d.ts +48 -0
  79. package/lib/fivePlugins/CameraMovementPlugin/typings.js +25 -0
  80. package/lib/fivePlugins/ModelTVVideoPlugin/index.d.ts +20 -0
  81. package/lib/fivePlugins/ModelTVVideoPlugin/index.js +305 -0
  82. package/lib/index.d.ts +1 -0
  83. package/lib/index.js +13 -0
  84. package/lib/react/index.d.ts +42 -0
  85. package/lib/react/index.js +120 -0
  86. package/lib/shared-utils/Preloader.d.ts +6 -0
  87. package/lib/shared-utils/Preloader.js +51 -0
  88. package/lib/shared-utils/animationFrame/BetterTween.d.ts +11 -0
  89. package/lib/shared-utils/animationFrame/BetterTween.js +102 -0
  90. package/lib/shared-utils/animationFrame/index.d.ts +15 -0
  91. package/lib/shared-utils/animationFrame/index.js +68 -0
  92. package/lib/shared-utils/animationFrame/tween.d.ts +75 -0
  93. package/lib/shared-utils/animationFrame/tween.js +76 -0
  94. package/lib/typings/VreoUnit.d.ts +204 -0
  95. package/lib/typings/VreoUnit.js +65 -0
  96. package/package.json +55 -0
  97. package/resources/Player/App.tsx +57 -0
  98. package/resources/Player/Controller.ts +154 -0
  99. package/resources/Player/hooks.ts +28 -0
  100. package/resources/Player/index.tsx +115 -0
  101. package/resources/Player/modules/Drawer/index.tsx +40 -0
  102. package/resources/Player/modules/VideoAgent/VideoAgentMesh.ts +202 -0
  103. package/resources/Player/modules/VideoAgent/VideoAgentScene.ts +38 -0
  104. package/resources/Player/modules/VideoAgent/index.tsx +31 -0
  105. package/resources/Player/modules/keyframes/CameraMovement/index.tsx +33 -0
  106. package/resources/Player/modules/keyframes/InfoPanel/index.tsx +52 -0
  107. package/resources/Player/modules/keyframes/ModelVideo/index.tsx +44 -0
  108. package/resources/Player/modules/keyframes/PanoEffect/index.tsx +99 -0
  109. package/resources/Player/modules/keyframes/PanoEffect/lineAnime.ts +31 -0
  110. package/resources/Player/modules/keyframes/PanoTag/index.tsx +127 -0
  111. package/resources/Player/modules/keyframes/PanoTextLabel/index.tsx +65 -0
  112. package/resources/Player/modules/keyframes/Prompter/index.tsx +65 -0
  113. package/resources/Player/modules/keyframes/UpdateVRPanorama/index.tsx +36 -0
  114. package/resources/Player/modules/keyframes/VideoEffect/index.tsx +115 -0
  115. package/resources/Player/typings.ts +15 -0
  116. package/resources/fivePlugins/CameraMovementPlugin/index.ts +203 -0
  117. package/resources/fivePlugins/CameraMovementPlugin/typings.ts +57 -0
  118. package/resources/fivePlugins/ModelTVVideoPlugin/index.ts +278 -0
  119. package/resources/index.ts +1 -0
  120. package/resources/react/index.tsx +95 -0
  121. package/resources/shared-utils/Preloader.ts +35 -0
  122. package/resources/shared-utils/animationFrame/BetterTween.ts +33 -0
  123. package/resources/shared-utils/animationFrame/index.ts +48 -0
  124. package/resources/shared-utils/animationFrame/tween.ts +51 -0
  125. package/resources/typings/VreoUnit.ts +225 -0
  126. package/stylesheets/default.css +497 -0
@@ -0,0 +1,203 @@
1
+ import { Five } from '@realsee/five'
2
+ import type { FivePlugin } from '@realsee/five'
3
+ import * as TWEEN from '@tweenjs/tween.js'
4
+
5
+ import {
6
+ CameraMovementPluginParameterType,
7
+ CameraMovementPluginExportType,
8
+ MoveArgs,
9
+ MoveOpts,
10
+ RotateArgs,
11
+ RotateOpts,
12
+ Rotation,
13
+ } from './typings'
14
+
15
+ import { Easing, tweenProgress } from '../../shared-utils/animationFrame/tween'
16
+
17
+ const PI_2 = Math.PI * 2
18
+
19
+ function formatLatitude(rad: number) {
20
+ return rad % PI_2
21
+ }
22
+ function formatLongitude(rad: number) {
23
+ return ((rad % PI_2) + PI_2) % PI_2
24
+ }
25
+
26
+ const getLongitudeParams = (from: number, to: number, rotation: Rotation): { from: number; to: number } => {
27
+ const PI = Math.PI
28
+ const PI_2 = PI * 2
29
+ from = formatLongitude(from)
30
+ to = formatLongitude(to)
31
+ // 逆时针旋转,初始值必须是较大的值
32
+ if (rotation === Rotation.Anticlockwise && from < to) from += PI_2
33
+ // 顺时针旋转,结束值必须是较大值
34
+ if (rotation === Rotation.Clockwise && to < from) to += PI_2
35
+ // Loop 旋转,找锐角旋转
36
+ // 如果 to 比 from 大 180°,逆时针转
37
+ // 如果 from 比 to 大 180°,顺时针转
38
+ if (rotation === Rotation.Loop && to - from > PI) return getLongitudeParams(from, to, Rotation.Anticlockwise)
39
+ if (rotation === Rotation.Loop && from - to > PI) return getLongitudeParams(from, to, Rotation.Clockwise)
40
+ return { from, to }
41
+ }
42
+
43
+ function progressNumber(from: number, to: number, pst: number) {
44
+ return from + (to - from) * pst
45
+ }
46
+
47
+ /**
48
+ * **运镜插件** 模拟类似于电影运镜效果。
49
+ */
50
+ export const CameraMovementPlugin: FivePlugin<CameraMovementPluginParameterType, CameraMovementPluginExportType> = (
51
+ five: Five,
52
+ ) => {
53
+ const move = async (args: Partial<MoveArgs>, duration: number, opts: MoveOpts = { preload: true }) => {
54
+ if (opts.asyncStartCallback) {
55
+ opts.asyncStartCallback()
56
+ }
57
+
58
+ if (args.mode && args.mode !== five.currentMode) {
59
+ await new Promise((resolve) => {
60
+ if (!args.mode) return
61
+ five.once('modeChange', (mode) => {
62
+ five.once('initAnimationEnded', () => {
63
+ if (mode === args.mode) {
64
+ resolve(true)
65
+ }
66
+ })
67
+ })
68
+ five.changeMode(args.mode, {
69
+ fov: args.fov || undefined,
70
+ latitude: args.latitude || undefined,
71
+ longitude: args.longitude || undefined,
72
+ panoIndex: args.panoIndex || undefined,
73
+ })
74
+ })
75
+ }
76
+
77
+ if (args.mode === Five.Mode.Floorplan) {
78
+ return true
79
+ }
80
+
81
+ if (opts.preload && args.panoIndex !== undefined && args.panoIndex !== five.panoIndex) {
82
+ await five.preloadPano(args.panoIndex)
83
+ }
84
+
85
+ if (opts.asyncEndCallback) {
86
+ opts.asyncEndCallback()
87
+ }
88
+
89
+ if (
90
+ args.panoIndex === undefined &&
91
+ args.fov === undefined &&
92
+ args.latitude === undefined &&
93
+ args.longitude === undefined
94
+ ) {
95
+ return true
96
+ }
97
+ return await new Promise<boolean>((resolve, reject) => {
98
+ const panoIndex = args.panoIndex !== undefined ? args.panoIndex : five.panoIndex
99
+ if (panoIndex !== undefined) {
100
+ five.moveToPano(
101
+ panoIndex,
102
+ Object.assign(
103
+ {
104
+ duration, // 移动耗时
105
+ moveEndCallback: () => resolve(true), // 移动结束
106
+ moveCancelCallback: () => reject(false), // 移动开始
107
+ },
108
+ args,
109
+ ),
110
+ )
111
+ } else {
112
+ reject(false)
113
+ }
114
+ })
115
+ }
116
+
117
+ const getAnimeParams = (args: RotateArgs) => {
118
+ const currentFiveState = five.state
119
+ // latitude
120
+ const fromLatitude = formatLatitude(currentFiveState.latitude)
121
+ const toLatitude = formatLatitude(args.latitude)
122
+ // longitude
123
+ const { from: fromLongitude, to: toLongitude } = getLongitudeParams(
124
+ currentFiveState.longitude,
125
+ args.longitude,
126
+ args.rotation || Rotation.Loop,
127
+ )
128
+ // fov
129
+ const fromFov = currentFiveState.fov
130
+ const toFov = args.fov
131
+ return {
132
+ from: { latitude: fromLatitude, longitude: fromLongitude, fov: fromFov },
133
+ to: { latitude: toLatitude, longitude: toLongitude, fov: toFov },
134
+ }
135
+ }
136
+
137
+ const rotate = async (args: RotateArgs, duration: number, opts: RotateOpts = {}) => {
138
+ if (opts.asyncStartCallback) {
139
+ opts.asyncStartCallback()
140
+ }
141
+
142
+ if (five.currentMode !== Five.Mode.Panorama) {
143
+ await five.changeMode(Five.Mode.Panorama)
144
+ }
145
+
146
+ if (args.panoIndex !== undefined && args.panoIndex !== five.panoIndex) {
147
+ if (opts.preload) await five.preloadPano(args.panoIndex)
148
+ await new Promise((resolve, reject) => {
149
+ five.moveToPano(args.panoIndex, {
150
+ moveEndCallback: () => resolve(true), // 移动结束
151
+ moveCancelCallback: () => reject(false), // 移动开始
152
+ })
153
+ })
154
+ }
155
+
156
+ if (opts.asyncEndCallback) {
157
+ opts.asyncEndCallback()
158
+ }
159
+
160
+ const { from, to } = getAnimeParams(args)
161
+ return await new Promise<boolean>((resolve, reject) => {
162
+ const onUpdate = ({ progress }: { progress: number }) => {
163
+ const state: Record<string, number> = {}
164
+ state.longitude = progressNumber(from.longitude, to.longitude, progress)
165
+ state.latitude = progressNumber(from.latitude, to.latitude, progress)
166
+ state.fov = progressNumber(from.fov, to.fov, progress)
167
+ five.setState(state, true)
168
+ }
169
+
170
+ const onComplete = () => {
171
+ resolve(true)
172
+ }
173
+
174
+ const onDestroy = () => {
175
+ resolve(false)
176
+ }
177
+
178
+ const animeDuration = args.rotateSpeed
179
+ ? Math.ceil((Math.abs(to.longitude - from.longitude) / args.rotateSpeed) * 1000)
180
+ : duration
181
+
182
+ type _Tween = TWEEN.Tween<{ progress: number }> & { onDestroy: (p: any) => _Tween; destroy: () => void }
183
+
184
+ const tween = (tweenProgress(animeDuration, Easing.Linear.None) as _Tween)
185
+ .onUpdate(onUpdate)
186
+ .onComplete(onComplete)
187
+ .onDestroy(onDestroy)
188
+ if (args.rotation === Rotation.Loop) {
189
+ tween.repeat(Infinity).yoyo(true)
190
+ }
191
+ setTimeout(() => {
192
+ // 总时间到了之后强制结束,同时也是 Loop 模式的结束方式
193
+ tween.destroy()
194
+ resolve(true)
195
+ }, duration)
196
+ })
197
+ }
198
+
199
+ return {
200
+ move,
201
+ rotate,
202
+ }
203
+ }
@@ -0,0 +1,57 @@
1
+ /* eslint-disable prettier/prettier */
2
+ import { Mode, Pose } from '@realsee/five'
3
+
4
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
5
+ export interface CameraMovementPluginParameterType { }
6
+
7
+ export interface CameraMovementPluginExportType {
8
+ /**
9
+ * 运镜:移动
10
+ * @param moveArgs
11
+ * @param duration
12
+ * @param opts
13
+ */
14
+ move(moveArgs: Partial<MoveArgs>, duration: number, opts?: MoveOpts): Promise<boolean>
15
+ /**
16
+ * 运镜:旋转
17
+ * @param args
18
+ * @param duration
19
+ * @param opts
20
+ */
21
+ rotate(args: RotateArgs, duration: number, opts?: RotateOpts): Promise<boolean>
22
+ }
23
+
24
+ export enum CameraMovementEffect {
25
+ Move = 'Move',
26
+ Rotate = 'Rotate',
27
+ }
28
+ export enum Rotation {
29
+ Clockwise = 'Clockwise',
30
+ Anticlockwise = 'Anticlockwise',
31
+ Loop = 'Loop',
32
+ }
33
+
34
+ export interface CameraMovementOptsCallback {
35
+ asyncStartCallback?: () => void
36
+ asyncEndCallback?: () => void
37
+ }
38
+
39
+ export type MoveArgs = {
40
+ mode: Mode
41
+ panoIndex: number
42
+ } & Pose
43
+
44
+ export type MoveOpts = {
45
+ preload?: boolean
46
+ } & CameraMovementOptsCallback
47
+
48
+ export type RotateArgs = {
49
+ panoIndex: number
50
+ loop: boolean
51
+ rotateSpeed: number
52
+ rotation: Rotation
53
+ } & Pose
54
+
55
+ export type RotateOpts = {
56
+ preload?: boolean
57
+ } & CameraMovementOptsCallback
@@ -0,0 +1,278 @@
1
+ import * as THREE from 'three'
2
+ import type { FivePlugin } from '@realsee/five'
3
+
4
+ export interface ModelTVVideoPluginData {
5
+ enable?: boolean
6
+ video_src: string
7
+ video_poster_src: string
8
+ points: { x: number; y: number; z: number }[][]
9
+ }
10
+
11
+ export interface ModelTVVideoPluginParameterType {
12
+ videoElement?: HTMLVideoElement
13
+ }
14
+ export interface ModelTVVideoPluginExportType {
15
+ enable: () => void
16
+ disable: () => void
17
+ load: (data: ModelTVVideoPluginData, videoElement?: HTMLVideoElement) => Promise<void>
18
+ }
19
+
20
+ type ModelTVVideoPluginPoints = THREE.Vector3[][]
21
+
22
+ type ModelTVVideoPluginState = {
23
+ videoMeshes: THREE.Mesh<THREE.BufferGeometry, THREE.MeshBasicMaterial>[]
24
+ videoTextureEnabled: boolean
25
+ videoSource: string
26
+ rectPoints: ModelTVVideoPluginPoints
27
+ enabled: boolean
28
+ imageTexture?: THREE.Texture
29
+ videoTexture?: THREE.VideoTexture
30
+ videoElement?: HTMLVideoElement
31
+ }
32
+
33
+ export const ModelTVVideoPlugin: FivePlugin<ModelTVVideoPluginParameterType, ModelTVVideoPluginExportType> = (
34
+ five,
35
+ { videoElement },
36
+ ) => {
37
+ const state: ModelTVVideoPluginState = {
38
+ videoMeshes: [],
39
+ videoTextureEnabled: false,
40
+ videoSource: '',
41
+ rectPoints: [],
42
+ enabled: false,
43
+ videoElement: videoElement,
44
+ }
45
+
46
+ const setMuted = (muted: boolean) => {
47
+ if (state.videoTexture) {
48
+ state.videoTexture.image.muted = muted
49
+ state.videoTexture.image.play()
50
+ }
51
+ }
52
+ const getMuted = () => {
53
+ if (state.videoTexture) return state.videoTexture.image.muted
54
+ else return true
55
+ }
56
+
57
+ const enable = () => {
58
+ if (state.enabled) return
59
+ if (!state.videoTexture) return
60
+
61
+ state.enabled = true
62
+ state.videoMeshes = createPanoVideoMeshes()
63
+ state.videoMeshes.forEach((mesh) => five.scene.add(mesh))
64
+
65
+ const play = () => {
66
+ if (!state.videoTexture) return
67
+
68
+ const timeupdate = () => {
69
+ if (!state.videoTexture) return
70
+ state.videoTexture?.image.removeEventListener('timeupdate', timeupdate)
71
+ state.videoTextureEnabled = true
72
+ state.videoMeshes.forEach((mesh) => {
73
+ if (mesh.material.map !== state.videoTexture) mesh.material.map = state.videoTexture!
74
+ })
75
+ five.needsRender = true
76
+ }
77
+ state.videoTexture.image.addEventListener('timeupdate', timeupdate)
78
+ if (state.videoTexture && state.videoMeshes.length) {
79
+ state.videoTexture.image.play()
80
+ }
81
+ }
82
+
83
+ if (five.model.loaded) play()
84
+ else {
85
+ return five.once('modelLoaded', () => play())
86
+ }
87
+ }
88
+
89
+ const disable = () => {
90
+ if (!state.enabled) return
91
+ state.enabled = false
92
+ state.videoMeshes.forEach((mesh) => {
93
+ mesh.geometry.dispose()
94
+ mesh.material.dispose()
95
+ five.scene.remove(mesh)
96
+ if (state.videoTexture) state.videoTexture.image.pause()
97
+ })
98
+ state.videoMeshes = []
99
+ five.needsRender = true
100
+ }
101
+
102
+ const createPanoVideoMeshes = () => {
103
+ return state.rectPoints.map((points, index) => {
104
+ const geometry = new THREE.BufferGeometry()
105
+ const segments = 128
106
+
107
+ const verticesArray: number[] = []
108
+ verticesArray.push(points[0].x, points[0].y, points[0].z)
109
+ for (let i = 1; i < segments; i++) {
110
+ verticesArray.push(
111
+ points[0].x + ((points[1].x - points[0].x) * i) / segments,
112
+ points[0].y + ((points[1].y - points[0].y) * i) / segments,
113
+ points[0].z + ((points[1].z - points[0].z) * i) / segments,
114
+ )
115
+ }
116
+ verticesArray.push(points[1].x, points[1].y, points[1].z)
117
+ verticesArray.push(points[2].x, points[2].y, points[2].z)
118
+ for (let i = 1; i < segments; i++) {
119
+ verticesArray.push(
120
+ points[2].x + ((points[3].x - points[2].x) * i) / segments,
121
+ points[2].y + ((points[3].y - points[2].y) * i) / segments,
122
+ points[2].z + ((points[3].z - points[2].z) * i) / segments,
123
+ )
124
+ }
125
+ verticesArray.push(points[3].x, points[3].y, points[3].z)
126
+
127
+ const uvArray: number[] = []
128
+ uvArray.push(0, 1)
129
+ for (let i = 1; i < segments; i++) {
130
+ uvArray.push(0, 1 - i / segments)
131
+ }
132
+ uvArray.push(0, 0)
133
+ uvArray.push(1, 0)
134
+ for (let i = 1; i < segments; i++) {
135
+ uvArray.push(1, i / segments)
136
+ }
137
+ uvArray.push(1, 1)
138
+
139
+ const indicesArray: number[] = []
140
+ for (let i = 0; i < segments; i++) {
141
+ indicesArray.push(i, i + 1, segments * 2 - i, i, segments * 2 - i, segments * 2 + 1 - i)
142
+ }
143
+ geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(verticesArray), 3))
144
+ geometry.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(uvArray), 2))
145
+ geometry.setIndex(new THREE.BufferAttribute(new Uint32Array(indicesArray), 1))
146
+ const material = new THREE.MeshBasicMaterial({
147
+ map: state.videoTextureEnabled ? state.videoTexture : state.imageTexture,
148
+ side: THREE.DoubleSide,
149
+ })
150
+ const mesh = new THREE.Mesh(geometry, material)
151
+ mesh.renderOrder = 1
152
+ mesh.name = `video-${index}-${performance.now()}`
153
+ return mesh
154
+ })
155
+ }
156
+
157
+ const getImageTexture = (source: string) => {
158
+ const texture = new THREE.TextureLoader().load(source)
159
+ texture.minFilter = THREE.LinearFilter
160
+ texture.magFilter = THREE.LinearFilter
161
+ texture.format = THREE.RGBFormat
162
+ return texture
163
+ }
164
+
165
+ const getVideoTexture = (
166
+ source: string,
167
+ video?: HTMLVideoElement,
168
+ ): Promise<THREE.VideoTexture & { videoSource: string }> => {
169
+ return new Promise((resolve, reject) => {
170
+ const xhr = new XMLHttpRequest()
171
+ xhr.onreadystatechange = () => {
172
+ if (xhr.readyState == 4) {
173
+ if (xhr.status == 200) {
174
+ const url = window.URL || window.webkitURL
175
+ video = video || document.createElement('video')
176
+ video.crossOrigin = 'anonymous'
177
+ video.autoplay = true
178
+ video.muted = true
179
+ video.loop = true
180
+ video.playsInline = true
181
+ video.src = url.createObjectURL(xhr.response)
182
+ const texture = new THREE.VideoTexture(video)
183
+ texture.minFilter = THREE.LinearFilter
184
+ texture.magFilter = THREE.LinearFilter
185
+ texture.format = THREE.RGBFormat
186
+ resolve(Object.assign(texture, { videoSource: source }))
187
+ } else {
188
+ reject(new Error('Video download Error: ' + xhr.status))
189
+ }
190
+ }
191
+ }
192
+ xhr.onerror = (error) => reject(error)
193
+ xhr.open('GET', source)
194
+ xhr.responseType = 'blob'
195
+ xhr.send()
196
+ })
197
+ }
198
+
199
+ const load = async (data: ModelTVVideoPluginData, videoElement?: HTMLVideoElement) => {
200
+ const { video_src, video_poster_src, points } = data
201
+ state.videoSource = video_src
202
+ state.rectPoints = points.map((items) => items.map(({ x, y, z }) => new THREE.Vector3(x, y, z)))
203
+ state.imageTexture = getImageTexture(video_poster_src)
204
+
205
+ if (videoElement) {
206
+ state.videoElement = videoElement
207
+ }
208
+
209
+ state.videoTexture = await getVideoTexture(state.videoSource, state.videoElement)
210
+
211
+ state.enabled = !!data.enable
212
+ if (state.enabled) enable()
213
+ }
214
+
215
+ five.on('modeChange', () => setMuted(true))
216
+
217
+ five.on('wantsTapGesture', (raycaster) => {
218
+ if (!state.enabled) return
219
+ const [intersect] = raycaster.intersectObjects(five.scene.children, true)
220
+ if (!!intersect && /^video/.test(intersect.object.name)) {
221
+ if (state.videoTexture) setMuted(!state.videoTexture.image.muted)
222
+ return false
223
+ }
224
+ })
225
+
226
+ five.on('panoArrived', () => {
227
+ if (!state.enabled) return
228
+ if (getMuted()) return
229
+ const cameraPosition = five.camera.position
230
+
231
+ const visible = state.rectPoints.find((points) => {
232
+ const centerVector = points.reduce(
233
+ (total, curr) =>
234
+ new THREE.Vector3(
235
+ total.x + curr.x / points.length,
236
+ total.y + curr.y / points.length,
237
+ total.z + curr.z / points.length,
238
+ ),
239
+ new THREE.Vector3(),
240
+ )
241
+
242
+ return (
243
+ points
244
+ .map(
245
+ (point) =>
246
+ new THREE.Vector3(
247
+ (point.x + centerVector.x) / 2,
248
+ (point.y + centerVector.y) / 2,
249
+ (point.z + centerVector.z) / 2,
250
+ ),
251
+ )
252
+ .filter((vector) => {
253
+ vector = vector.clone().sub(cameraPosition).normalize()
254
+ const raycaster = new THREE.Raycaster(cameraPosition, vector)
255
+ const [intersect] = raycaster.intersectObjects(five.scene.children, true)
256
+ return !!intersect && /^video/.test(intersect.object.name)
257
+ }).length >= 2
258
+ )
259
+ })
260
+ if (!visible) setMuted(true)
261
+ })
262
+
263
+ five.on('renderFrame', () => {
264
+ state.videoMeshes.forEach((meshes) => {
265
+ if (meshes)
266
+ //@ts-ignore
267
+ meshes.needsRender = true
268
+ })
269
+ })
270
+
271
+ five.on('load', (input) => {
272
+ if (input.modelTVVideoData) {
273
+ load(input.modelTVVideoData)
274
+ }
275
+ })
276
+
277
+ return { enable, disable, load }
278
+ }
@@ -0,0 +1 @@
1
+ export { Player } from './Player'
@@ -0,0 +1,95 @@
1
+ import { unsafe__useFiveInstance } from '@realsee/five/react'
2
+ import * as React from 'react'
3
+ import { Player } from '../Player'
4
+ import { VreoKeyframeEvent, VreoUnit } from '../typings/VreoUnit'
5
+
6
+ const VreoContext = React.createContext<Player | null>(null)
7
+
8
+ export const VreoProvider: React.FC = (props) => {
9
+ const five = unsafe__useFiveInstance()
10
+
11
+ const player = new Player(five)
12
+ return <VreoContext.Provider value={player}>{props.children}</VreoContext.Provider>
13
+ }
14
+
15
+ function useVreoInstance() {
16
+ const context = React.useContext(VreoContext)
17
+ if (!context) {
18
+ throw new Error('VreoProvider never found.')
19
+ }
20
+
21
+ return context
22
+ }
23
+
24
+ /**
25
+ * VreoPlayer 命令集合。
26
+ */
27
+ export interface VreoActionCallbacks {
28
+ /**
29
+ * 载入剧本数据。
30
+ */
31
+ load: (vreoUnit: VreoUnit, currentTime?: number) => Promise<boolean>
32
+ /**
33
+ * 播放。
34
+ */
35
+ play: () => void
36
+ /**
37
+ * 暂停。
38
+ */
39
+ pause: () => void
40
+ /**
41
+ * 显示 UI 面板。
42
+ */
43
+ show: () => void
44
+ /**
45
+ * 隐藏 UI 面板。
46
+ */
47
+ hide: () => void
48
+ /**
49
+ * 销毁数据及部分定时任务。
50
+ */
51
+ dispose: () => void
52
+ }
53
+
54
+ export function useVreoAction() {
55
+ const player = useVreoInstance()
56
+
57
+ const load = React.useCallback(
58
+ (vreoUnit: VreoUnit, currentTime?: number) => player.load(vreoUnit, currentTime),
59
+ [player]
60
+ )
61
+
62
+ const play = React.useCallback(() => player.play(), [player])
63
+ const pause = React.useCallback(() => player.pause(), [player])
64
+ const show = React.useCallback(() => player.show(), [player])
65
+ const hide = React.useCallback(() => player.hide(), [player])
66
+ const dispose = React.useCallback(() => player.dispose(), [player])
67
+
68
+ return { load, pause, show, play, hide, dispose }
69
+ }
70
+
71
+ export function useVreoEventCallback<T extends keyof VreoKeyframeEvent>(
72
+ name: T,
73
+ callback: VreoKeyframeEvent[T],
74
+ deps?: React.DependencyList | undefined
75
+ ) {
76
+ const player = useVreoInstance()
77
+ let dependencyList: React.DependencyList = [player, name]
78
+ if (deps !== undefined) {
79
+ dependencyList = dependencyList.concat(deps)
80
+ }
81
+ React.useLayoutEffect(() => {
82
+ // @ts-ignore
83
+ return player.on(name, callback)
84
+ }, dependencyList)
85
+ }
86
+
87
+ export function useVreoPausedState() {
88
+ const player = useVreoInstance()
89
+ const [state, setState] = React.useState(player.paused)
90
+
91
+ useVreoEventCallback('paused', () => setState(true))
92
+ useVreoEventCallback('playing', () => setState(false))
93
+
94
+ return state
95
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * 预载器
3
+ */
4
+ export const Preloader = {
5
+ blob: (url: string) => {
6
+ const noop = () => null
7
+ return new Promise<string>((resolve, reject) => {
8
+ const xhr = new XMLHttpRequest()
9
+ const clear = () => {
10
+ xhr.ontimeout = noop
11
+ xhr.onreadystatechange = noop
12
+ xhr.onprogress = noop
13
+ xhr.onabort = noop
14
+ }
15
+ xhr.ontimeout = () => {
16
+ reject(new Error('timeout'))
17
+ clear()
18
+ }
19
+ xhr.onreadystatechange = () => {
20
+ if (xhr.readyState !== 4) return
21
+ if (xhr.status === 0 && !xhr.response) return
22
+ if (xhr.status === 200 || xhr.status === 0) {
23
+ resolve(xhr.response)
24
+ clear()
25
+ } else {
26
+ reject(new Error('status:' + xhr.status))
27
+ clear()
28
+ }
29
+ }
30
+ xhr.open('GET', url, true)
31
+ xhr.responseType = 'blob'
32
+ xhr.send(null)
33
+ })
34
+ },
35
+ }
@@ -0,0 +1,33 @@
1
+ import TWEEN from '@tweenjs/tween.js'
2
+ import { requestAnimationFrameInterval } from '.'
3
+
4
+ export class BetterTween<G> extends TWEEN.Tween<G> {
5
+ private disposeMethods: (() => void)[] = []
6
+ private animationFrameDisposer?: () => void
7
+
8
+ public onDispose = (callback: () => void) => {
9
+ this.disposeMethods.push(callback)
10
+ return this
11
+ }
12
+
13
+ public play = () => {
14
+ this.start()
15
+ this.animationFrameDisposer = requestAnimationFrameInterval(() => this.update())
16
+ return this
17
+ }
18
+
19
+ public dispose = () => {
20
+ this.stop()
21
+ this.animationFrameDisposer?.()
22
+ this.disposeMethods.forEach((fn) => fn())
23
+ this.disposeMethods = []
24
+ TWEEN.remove(this)
25
+ }
26
+ }
27
+
28
+ export function tweenProgress(duration?: number, easing = TWEEN.Easing.Linear.None) {
29
+ const tween = new BetterTween({ progress: 0 }).to({ progress: 1 })
30
+ if (duration !== undefined) tween.duration(duration)
31
+ if (easing !== undefined) tween.easing(easing)
32
+ return tween
33
+ }