@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.
- package/LICENSE +21 -0
- package/README.md +465 -0
- package/docs/.nojekyll +1 -0
- package/docs/assets/custom.css +52 -0
- package/docs/assets/highlight.css +127 -0
- package/docs/assets/icons.css +1043 -0
- package/docs/assets/icons.png +0 -0
- package/docs/assets/icons@2x.png +0 -0
- package/docs/assets/main.js +52 -0
- package/docs/assets/search.js +1 -0
- package/docs/assets/style.css +1388 -0
- package/docs/assets/widgets.png +0 -0
- package/docs/assets/widgets@2x.png +0 -0
- package/docs/classes/Player.Player-1.html +50 -0
- package/docs/enums/Player.InfoPanelTypeEnum.html +3 -0
- package/docs/enums/Player.PanoEffectEnum.html +5 -0
- package/docs/enums/Player.PanoTagEnum.html +3 -0
- package/docs/enums/Player.VreoKeyframeEnum.html +388 -0
- package/docs/enums/fivePlugins.CameraMovementEffect.html +1 -0
- package/docs/enums/fivePlugins.Rotation.html +1 -0
- package/docs/index.html +204 -0
- package/docs/interfaces/Player.PlayerConfigs.html +4 -0
- package/docs/interfaces/Player.Vertex.html +3 -0
- package/docs/interfaces/Player.VreoKeyframe.html +3 -0
- package/docs/interfaces/Player.VreoUnit.html +3 -0
- package/docs/interfaces/Player.VreoVideo.html +3 -0
- package/docs/interfaces/fivePlugins.CameraMovementOptsCallback.html +1 -0
- package/docs/interfaces/fivePlugins.CameraMovementPluginExportType.html +7 -0
- package/docs/interfaces/fivePlugins.CameraMovementPluginParameterType.html +1 -0
- package/docs/interfaces/fivePlugins.ModelTVVideoPluginData.html +1 -0
- package/docs/interfaces/fivePlugins.ModelTVVideoPluginExportType.html +1 -0
- package/docs/interfaces/fivePlugins.ModelTVVideoPluginParameterType.html +1 -0
- package/docs/interfaces/react.VreoActionCallbacks.html +15 -0
- package/docs/modules/Player.html +29 -0
- package/docs/modules/fivePlugins.html +3 -0
- package/docs/modules/react.html +1 -0
- package/docs/modules.html +1 -0
- package/lib/Player/App.d.ts +2 -0
- package/lib/Player/App.js +68 -0
- package/lib/Player/Controller.d.ts +39 -0
- package/lib/Player/Controller.js +226 -0
- package/lib/Player/hooks.d.ts +4 -0
- package/lib/Player/hooks.js +41 -0
- package/lib/Player/index.d.ts +15 -0
- package/lib/Player/index.js +191 -0
- package/lib/Player/modules/Drawer/index.d.ts +2 -0
- package/lib/Player/modules/Drawer/index.js +67 -0
- package/lib/Player/modules/VideoAgent/VideoAgentMesh.d.ts +30 -0
- package/lib/Player/modules/VideoAgent/VideoAgentMesh.js +324 -0
- package/lib/Player/modules/VideoAgent/VideoAgentScene.d.ts +10 -0
- package/lib/Player/modules/VideoAgent/VideoAgentScene.js +65 -0
- package/lib/Player/modules/VideoAgent/index.d.ts +4 -0
- package/lib/Player/modules/VideoAgent/index.js +44 -0
- package/lib/Player/modules/keyframes/CameraMovement/index.d.ts +2 -0
- package/lib/Player/modules/keyframes/CameraMovement/index.js +73 -0
- package/lib/Player/modules/keyframes/InfoPanel/index.d.ts +2 -0
- package/lib/Player/modules/keyframes/InfoPanel/index.js +89 -0
- package/lib/Player/modules/keyframes/ModelVideo/index.d.ts +2 -0
- package/lib/Player/modules/keyframes/ModelVideo/index.js +87 -0
- package/lib/Player/modules/keyframes/PanoEffect/index.d.ts +2 -0
- package/lib/Player/modules/keyframes/PanoEffect/index.js +142 -0
- package/lib/Player/modules/keyframes/PanoEffect/lineAnime.d.ts +5 -0
- package/lib/Player/modules/keyframes/PanoEffect/lineAnime.js +46 -0
- package/lib/Player/modules/keyframes/PanoTag/index.d.ts +2 -0
- package/lib/Player/modules/keyframes/PanoTag/index.js +168 -0
- package/lib/Player/modules/keyframes/PanoTextLabel/index.d.ts +2 -0
- package/lib/Player/modules/keyframes/PanoTextLabel/index.js +113 -0
- package/lib/Player/modules/keyframes/Prompter/index.d.ts +2 -0
- package/lib/Player/modules/keyframes/Prompter/index.js +105 -0
- package/lib/Player/modules/keyframes/UpdateVRPanorama/index.d.ts +2 -0
- package/lib/Player/modules/keyframes/UpdateVRPanorama/index.js +51 -0
- package/lib/Player/modules/keyframes/VideoEffect/index.d.ts +2 -0
- package/lib/Player/modules/keyframes/VideoEffect/index.js +176 -0
- package/lib/Player/typings.d.ts +14 -0
- package/lib/Player/typings.js +5 -0
- package/lib/fivePlugins/CameraMovementPlugin/index.d.ts +6 -0
- package/lib/fivePlugins/CameraMovementPlugin/index.js +307 -0
- package/lib/fivePlugins/CameraMovementPlugin/typings.d.ts +48 -0
- package/lib/fivePlugins/CameraMovementPlugin/typings.js +25 -0
- package/lib/fivePlugins/ModelTVVideoPlugin/index.d.ts +20 -0
- package/lib/fivePlugins/ModelTVVideoPlugin/index.js +305 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +13 -0
- package/lib/react/index.d.ts +42 -0
- package/lib/react/index.js +120 -0
- package/lib/shared-utils/Preloader.d.ts +6 -0
- package/lib/shared-utils/Preloader.js +51 -0
- package/lib/shared-utils/animationFrame/BetterTween.d.ts +11 -0
- package/lib/shared-utils/animationFrame/BetterTween.js +102 -0
- package/lib/shared-utils/animationFrame/index.d.ts +15 -0
- package/lib/shared-utils/animationFrame/index.js +68 -0
- package/lib/shared-utils/animationFrame/tween.d.ts +75 -0
- package/lib/shared-utils/animationFrame/tween.js +76 -0
- package/lib/typings/VreoUnit.d.ts +204 -0
- package/lib/typings/VreoUnit.js +65 -0
- package/package.json +55 -0
- package/resources/Player/App.tsx +57 -0
- package/resources/Player/Controller.ts +154 -0
- package/resources/Player/hooks.ts +28 -0
- package/resources/Player/index.tsx +115 -0
- package/resources/Player/modules/Drawer/index.tsx +40 -0
- package/resources/Player/modules/VideoAgent/VideoAgentMesh.ts +202 -0
- package/resources/Player/modules/VideoAgent/VideoAgentScene.ts +38 -0
- package/resources/Player/modules/VideoAgent/index.tsx +31 -0
- package/resources/Player/modules/keyframes/CameraMovement/index.tsx +33 -0
- package/resources/Player/modules/keyframes/InfoPanel/index.tsx +52 -0
- package/resources/Player/modules/keyframes/ModelVideo/index.tsx +44 -0
- package/resources/Player/modules/keyframes/PanoEffect/index.tsx +99 -0
- package/resources/Player/modules/keyframes/PanoEffect/lineAnime.ts +31 -0
- package/resources/Player/modules/keyframes/PanoTag/index.tsx +127 -0
- package/resources/Player/modules/keyframes/PanoTextLabel/index.tsx +65 -0
- package/resources/Player/modules/keyframes/Prompter/index.tsx +65 -0
- package/resources/Player/modules/keyframes/UpdateVRPanorama/index.tsx +36 -0
- package/resources/Player/modules/keyframes/VideoEffect/index.tsx +115 -0
- package/resources/Player/typings.ts +15 -0
- package/resources/fivePlugins/CameraMovementPlugin/index.ts +203 -0
- package/resources/fivePlugins/CameraMovementPlugin/typings.ts +57 -0
- package/resources/fivePlugins/ModelTVVideoPlugin/index.ts +278 -0
- package/resources/index.ts +1 -0
- package/resources/react/index.tsx +95 -0
- package/resources/shared-utils/Preloader.ts +35 -0
- package/resources/shared-utils/animationFrame/BetterTween.ts +33 -0
- package/resources/shared-utils/animationFrame/index.ts +48 -0
- package/resources/shared-utils/animationFrame/tween.ts +51 -0
- package/resources/typings/VreoUnit.ts +225 -0
- 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
|
+
}
|