@ray-js/ipc-player-integration 0.0.50-beta.2 → 0.0.50-beta.4
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/lib/iconfont/iconfont.css +3 -3
- package/lib/iconfont/iconfont.json +1 -1
- package/lib/iconfont/iconfont.ttf +0 -0
- package/lib/iconfont/iconfont.woff +0 -0
- package/lib/iconfont/iconfont.woff2 +0 -0
- package/lib/ui/ui.js +43 -17
- package/lib/widgets/voiceIntercom/voiceIntercom.js +6 -1
- package/lib/widgets/zoomTurntable/constants.d.ts +204 -39
- package/lib/widgets/zoomTurntable/constants.js +323 -56
- package/lib/widgets/zoomTurntable/lensModel.d.ts +10 -2
- package/lib/widgets/zoomTurntable/lensModel.js +24 -7
- package/lib/widgets/zoomTurntable/lensModel.test.js +20 -8
- package/lib/widgets/zoomTurntable/measureVisible.d.ts +9 -0
- package/lib/widgets/zoomTurntable/measureVisible.js +10 -0
- package/lib/widgets/zoomTurntable/turntable/index.js +5 -0
- package/lib/widgets/zoomTurntable/turntable/rjs/index.js +27 -4
- package/lib/widgets/zoomTurntable/turntable/rjs/index.rjs +132 -32
- package/lib/widgets/zoomTurntable/turntable/type.d.ts +41 -2
- package/lib/widgets/zoomTurntable/useLensZoom.d.ts +1 -0
- package/lib/widgets/zoomTurntable/useLensZoom.js +304 -25
- package/lib/widgets/zoomTurntable/zoomTurntableDock.js +94 -36
- package/lib/widgets/zoomTurntable/zoomTurntableEntry.js +96 -18
- package/lib/widgets/zoomTurntable/zoomTurntableTip.js +34 -22
- package/lib/widgets/zoomTurntable/zoomTurntableTip.less +23 -7
- package/package.json +1 -1
|
@@ -12,7 +12,7 @@ import { PlayState } from '../../interface';
|
|
|
12
12
|
import { pauseTimeToHideAllComponent, playerTap } from '../../ui/constant';
|
|
13
13
|
import { useLensZoom } from './useLensZoom';
|
|
14
14
|
import ZoomTurntable from './turntable';
|
|
15
|
-
import { CROP_RATIO_BOTTOM, CROP_RATIO_RIGHT, RELAYOUT_DELAYS,
|
|
15
|
+
import { CROP_RATIO_BOTTOM, CROP_RATIO_RIGHT, RELAYOUT_DELAYS, VIBRATE_FAST_STEPS, getStyleOptionsBottom, getVerticalPlayerHeight, getZoomLayoutDebugPx, getZoomVisibleHeightBottom, STYLE_OPTIONS_RIGHT, VIBRATE_THROTTLE, zoomTurntableClose } from './constants';
|
|
16
16
|
import './zoomTurntableDock.less';
|
|
17
17
|
/**
|
|
18
18
|
* 当前页面 id,ty.nativeDisabled 需要它定位要禁用手势分发的页面。
|
|
@@ -47,11 +47,20 @@ export const ZoomTurntableDock = props => {
|
|
|
47
47
|
} = props;
|
|
48
48
|
const {
|
|
49
49
|
screenType,
|
|
50
|
-
playState
|
|
50
|
+
playState,
|
|
51
|
+
isVerticalFullLayout
|
|
51
52
|
} = useStore({
|
|
52
53
|
screenType: props.screenType,
|
|
53
|
-
playState: props.playState
|
|
54
|
+
playState: props.playState,
|
|
55
|
+
isVerticalFullLayout: props.isVerticalFullLayout
|
|
54
56
|
});
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 竖屏全屏:底部工具条常驻,转盘停在它之上(canvas 底部不留触摸余量,否则会盖住按钮)。
|
|
60
|
+
* 普通竖屏:工具条已被 Entry 藏掉,转盘贴播放器底边(余量上下对称,底部那份沉到播放器外)。
|
|
61
|
+
* 判断必须与 Entry 的 absolutePosition 用同一套,否则 canvas 高度与 dock 位置对不上。
|
|
62
|
+
*/
|
|
63
|
+
const isVerticalFull = screenType === 'vertical' && isVerticalFullLayout;
|
|
55
64
|
const {
|
|
56
65
|
multiLensReady,
|
|
57
66
|
curM,
|
|
@@ -61,7 +70,8 @@ export const ZoomTurntableDock = props => {
|
|
|
61
70
|
marks,
|
|
62
71
|
ticks,
|
|
63
72
|
beginDrag,
|
|
64
|
-
applyZoom
|
|
73
|
+
applyZoom,
|
|
74
|
+
endDrag
|
|
65
75
|
} = useLensZoom(_objectSpread(_objectSpread({}, props), {}, {
|
|
66
76
|
skipLoad: true
|
|
67
77
|
}));
|
|
@@ -76,13 +86,14 @@ export const ZoomTurntableDock = props => {
|
|
|
76
86
|
const lastVibrateValueRef = useRef(curM);
|
|
77
87
|
|
|
78
88
|
/**
|
|
79
|
-
* 转盘起始值:只在打开转盘 /
|
|
80
|
-
*
|
|
81
|
-
* curM 会因为切镜头收尾、画面回读而变,受控回写会在拖动中把指针拽走 ——
|
|
82
|
-
* 转盘自己维护值、只在落位时把结果下发出去,手感才连续。
|
|
89
|
+
* 转盘起始值:只在打开转盘 / 重建画布时取一次,作为首帧的 defaultValue。
|
|
83
90
|
*
|
|
84
91
|
* 取值时机要等镜头能力就绪:能力没就绪时 curM 只是 D_MIN 占位,
|
|
85
|
-
*
|
|
92
|
+
* 拿它当起点会让转盘停在错误刻度。
|
|
93
|
+
*
|
|
94
|
+
* 稳态读数由下面的受控 `value={curM}` 负责跟随(手势缩放画面时读数要跟着动)。
|
|
95
|
+
* 早先只传 defaultValue、完全不回写,是担心「回写在拖动中把指针拽走」——
|
|
96
|
+
* 而 rjs 的 setValue 本身就有拖动中 + 松手后 300ms 冷却期的双重忽略,这个顾虑不成立。
|
|
86
97
|
*/
|
|
87
98
|
const initialValueRef = useRef(null);
|
|
88
99
|
const lastRemountRef = useRef(remountKey);
|
|
@@ -130,38 +141,58 @@ export const ZoomTurntableDock = props => {
|
|
|
130
141
|
relayoutTimersRef.current = [];
|
|
131
142
|
// 卸载兜底:手指还按着就被收起时,必须恢复原生手势
|
|
132
143
|
setNativeDisabled(false);
|
|
144
|
+
// 拖动中被收起的话 onChangeEnd 不会来,draggingRef 会挂着 true 把后续的
|
|
145
|
+
// zoomLevelChange 全丢掉(手势缩放读数就不动了),这里补一次复位
|
|
146
|
+
endDrag();
|
|
133
147
|
};
|
|
134
148
|
}, []);
|
|
135
149
|
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
150
|
+
// 诊断:把浮层、canvas 与播放器容器的真实布局打出来,逐项核对两类历史坑:
|
|
151
|
+
// 1. dock 实测高 ≈ 期望_dock高 → absolutePosition 的 rpx 换算对了
|
|
152
|
+
// (曾经给 194 只量到 101px,dock 只有 canvas 一半高,canvas 顶部被裁、收不到 touch)
|
|
153
|
+
// 2. canvas 实测高 ≈ 期望_canvas高 → rjs/index.js 的 computeCanvasViewport 读到了 ellipseRx/Ry
|
|
154
|
+
// (曾经它硬编码 258×175.5,DOM 208 高、绘制 186 高,画面被拉伸且顶部溢出被裁)
|
|
155
|
+
// canvas顶溢出 > 0 就是「手势不生效」的直接信号:溢出 dock 的那部分收不到 touch。
|
|
156
|
+
// canvas底距播底 应 ≈ 48(让出底部工具条):小于它就说明 canvas 压在按钮上、会吃掉按钮点击。
|
|
140
157
|
useEffect(() => {
|
|
141
158
|
const timer = setTimeout(() => {
|
|
142
159
|
const query = ty.createSelectorQuery();
|
|
143
160
|
query.select('.ipc-player-plugin-zoom-turntable-dock').boundingClientRect();
|
|
144
161
|
query.select('.ipc-zoom-turntable-canvas').boundingClientRect();
|
|
145
|
-
query.
|
|
162
|
+
query.select('.ipc-player-content').boundingClientRect();
|
|
146
163
|
query.exec(res => {
|
|
147
164
|
const dock = res === null || res === void 0 ? void 0 : res[0];
|
|
148
165
|
const canvas = res === null || res === void 0 ? void 0 : res[1];
|
|
149
|
-
const
|
|
166
|
+
const player = res === null || res === void 0 ? void 0 : res[2];
|
|
167
|
+
const visibleH = getZoomVisibleHeightBottom();
|
|
168
|
+
const expect = getZoomLayoutDebugPx(isVerticalFull);
|
|
150
169
|
console.log('[zoomTurntable] 浮层布局诊断', JSON.stringify({
|
|
151
170
|
dock: dock && {
|
|
152
171
|
top: dock.top,
|
|
153
|
-
left: dock.left,
|
|
154
172
|
w: dock.width,
|
|
155
173
|
h: dock.height
|
|
156
174
|
},
|
|
157
175
|
canvas: canvas && {
|
|
158
176
|
top: canvas.top,
|
|
159
|
-
left: canvas.left,
|
|
160
177
|
w: canvas.width,
|
|
161
178
|
h: canvas.height
|
|
162
179
|
},
|
|
163
|
-
|
|
164
|
-
|
|
180
|
+
player: player && {
|
|
181
|
+
w: player.width,
|
|
182
|
+
h: player.height
|
|
183
|
+
},
|
|
184
|
+
// 16:9 推导值 vs 实测值,两者差得多说明容器不是满宽 16:9
|
|
185
|
+
播放器高_推导: Math.round(getVerticalPlayerHeight()),
|
|
186
|
+
竖屏全屏: isVerticalFull,
|
|
187
|
+
期望_dock高: expect.dock高,
|
|
188
|
+
期望_canvas高: expect.canvas高,
|
|
189
|
+
背景带高: visibleH,
|
|
190
|
+
背景带占比: player !== null && player !== void 0 && player.height ? +(visibleH / player.height).toFixed(3) : null,
|
|
191
|
+
// 竖屏全屏期望 ≈ 底部工具条高度(canvas 底边 = 工具条顶边);
|
|
192
|
+
// 普通竖屏期望 ≈ -HIT_PADDING_Y(沉到播放器底边以下)
|
|
193
|
+
canvas底距播底: canvas && player ? +(player.bottom - canvas.bottom).toFixed(1) : null,
|
|
194
|
+
// > 0 说明 canvas 顶部露在 dock 外被裁,那部分收不到 touch
|
|
195
|
+
canvas顶溢出: dock && canvas ? +(dock.top - canvas.top).toFixed(1) : null
|
|
165
196
|
}));
|
|
166
197
|
});
|
|
167
198
|
}, 300);
|
|
@@ -213,7 +244,8 @@ export const ZoomTurntableDock = props => {
|
|
|
213
244
|
lastVibrateValueRef.current = value;
|
|
214
245
|
beginDrag(value);
|
|
215
246
|
|
|
216
|
-
// 跨过主刻度(各镜头光学倍数 /
|
|
247
|
+
// 跨过主刻度(各镜头光学倍数 / 上限)时给更明显的反馈,形成挡位感。
|
|
248
|
+
// 这一下不受节流约束 —— 挡位感是转盘手感的骨架,丢一次就没了
|
|
217
249
|
const crossedMark = marks.some(mark => prev < mark.value && value >= mark.value || prev > mark.value && value <= mark.value);
|
|
218
250
|
const now = Date.now();
|
|
219
251
|
if (crossedMark) {
|
|
@@ -221,10 +253,17 @@ export const ZoomTurntableDock = props => {
|
|
|
221
253
|
vibrate('medium');
|
|
222
254
|
return;
|
|
223
255
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
256
|
+
|
|
257
|
+
// 距上一次震动太近就跳过。也顺带给 medium 留出静默期:
|
|
258
|
+
// 刚过主刻度又紧跟一下 light,会把挡位那一下的辨识度糊掉
|
|
259
|
+
if (now - lastVibrateAtRef.current < VIBRATE_THROTTLE) return;
|
|
260
|
+
|
|
261
|
+
// 一次 onChange 跨了好几格 = 正在快速甩动,逐格震分辨不出来,只会连成一片。
|
|
262
|
+
// 这时不震普通刻度,把反馈让给主刻度的挡位感
|
|
263
|
+
const movedSteps = step > 0 ? Math.abs(value - prev) / step : 1;
|
|
264
|
+
if (movedSteps > VIBRATE_FAST_STEPS) return;
|
|
265
|
+
lastVibrateAtRef.current = now;
|
|
266
|
+
vibrate('light');
|
|
228
267
|
});
|
|
229
268
|
const handleChangeEnd = useMemoizedFn(value => {
|
|
230
269
|
applyZoom(value);
|
|
@@ -241,6 +280,18 @@ export const ZoomTurntableDock = props => {
|
|
|
241
280
|
setNativeDisabled(false);
|
|
242
281
|
});
|
|
243
282
|
|
|
283
|
+
/**
|
|
284
|
+
* 点在转盘空白处(未命中刻度带)= 点画面 → 收起转盘。
|
|
285
|
+
*
|
|
286
|
+
* canvas 覆盖了播放器下部很大一片,且 touch 事件被它 preventDefault + stopPropagation,
|
|
287
|
+
* 播放器收不到这一下 tap,「点画面收起」在转盘范围内整个失效 ——
|
|
288
|
+
* 表现就是第一次点没反应、第二次点(落到画面上部)才收起。
|
|
289
|
+
*/
|
|
290
|
+
const handleBlankTap = useMemoizedFn(() => {
|
|
291
|
+
setNativeDisabled(false);
|
|
292
|
+
requestClose();
|
|
293
|
+
});
|
|
294
|
+
|
|
244
295
|
// 定位由承载的 CoverView 负责(absolutePosition),这里只做布局容器。
|
|
245
296
|
const turntableStyle = useMemo(() => ({
|
|
246
297
|
width: isRight ? 'auto' : '100%',
|
|
@@ -252,18 +303,19 @@ export const ZoomTurntableDock = props => {
|
|
|
252
303
|
}), [isRight]);
|
|
253
304
|
|
|
254
305
|
/**
|
|
255
|
-
* canvas
|
|
256
|
-
*
|
|
257
|
-
*
|
|
306
|
+
* canvas 的承载层:只负责居中,不再用负 margin 把 canvas 往下推 ——
|
|
307
|
+
* 实测负 margin 在 CoverView 里不生效,探出去的那截会原地显示出来,
|
|
308
|
+
* 转盘整体被顶高一截、底边还留一条透明缝。
|
|
309
|
+
* 现在椭圆截面精确等于可见高度(CROP_RATIO_BOTTOM),整体下沉交给 dock 的
|
|
310
|
+
* dock 的 bottom(竖屏全屏为正、普通竖屏为负),canvas 尺寸与命中区保持原样,手势不受影响。
|
|
258
311
|
*/
|
|
259
|
-
const canvasHolderStyle = useMemo(() =>
|
|
312
|
+
const canvasHolderStyle = useMemo(() => ({
|
|
260
313
|
display: 'flex',
|
|
261
314
|
justifyContent: 'center'
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}, [isRight]);
|
|
315
|
+
}), []);
|
|
316
|
+
|
|
317
|
+
/** 竖屏几何按容器 16:9 推导(转盘占播放器高的 1/3),横屏沿用设计稿比例 */
|
|
318
|
+
const styleOptions = useMemo(() => isRight ? STYLE_OPTIONS_RIGHT : getStyleOptionsBottom(isVerticalFull), [isRight, isVerticalFull]);
|
|
267
319
|
if (!multiLensReady || playState !== PlayState.PLAYING) {
|
|
268
320
|
return null;
|
|
269
321
|
}
|
|
@@ -280,7 +332,12 @@ export const ZoomTurntableDock = props => {
|
|
|
280
332
|
style: canvasHolderStyle
|
|
281
333
|
}, /*#__PURE__*/React.createElement(ZoomTurntable, {
|
|
282
334
|
key: `${isRight ? 'right' : 'bottom'}-${remountKey}`,
|
|
283
|
-
defaultValue: initialValue
|
|
335
|
+
defaultValue: initialValue
|
|
336
|
+
// 受控回写:手势缩放画面时读数要跟着走。
|
|
337
|
+
// rjs 的 setValue 自带保护 —— 拖动中、以及松手后 300ms 冷却期内一律忽略回写,
|
|
338
|
+
// 所以不会出现「拖动被回写拽走」(那正是当初只传 defaultValue 的顾虑)。
|
|
339
|
+
,
|
|
340
|
+
value: curM,
|
|
284
341
|
min: min,
|
|
285
342
|
max: max,
|
|
286
343
|
step: step,
|
|
@@ -291,10 +348,11 @@ export const ZoomTurntableDock = props => {
|
|
|
291
348
|
orientation: isRight ? 'right' : 'bottom',
|
|
292
349
|
cropRatio: isRight ? CROP_RATIO_RIGHT : CROP_RATIO_BOTTOM,
|
|
293
350
|
activeColor: "#FFFFFF",
|
|
294
|
-
styleOptions:
|
|
351
|
+
styleOptions: styleOptions,
|
|
295
352
|
onChange: handleChange,
|
|
296
353
|
onChangeEnd: handleChangeEnd,
|
|
297
354
|
onTouchStart: handleTouchStart,
|
|
298
|
-
onTouchEnd: handleTouchEnd
|
|
355
|
+
onTouchEnd: handleTouchEnd,
|
|
356
|
+
onBlankTap: handleBlankTap
|
|
299
357
|
})));
|
|
300
358
|
};
|
|
@@ -2,7 +2,7 @@ import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
|
|
|
2
2
|
import "core-js/modules/esnext.iterator.constructor.js";
|
|
3
3
|
import "core-js/modules/esnext.iterator.map.js";
|
|
4
4
|
import React, { useContext, useEffect, useRef, useState } from 'react';
|
|
5
|
-
import { View, Text } from '@ray-js/ray';
|
|
5
|
+
import { View, Text, usePageEvent } from '@ray-js/ray';
|
|
6
6
|
import clsx from 'clsx';
|
|
7
7
|
import { useMemoizedFn } from 'ahooks';
|
|
8
8
|
import { useStore } from '../../ctx/store';
|
|
@@ -13,9 +13,9 @@ import { Storage } from '../../utils/storage';
|
|
|
13
13
|
import { useLensZoom } from './useLensZoom';
|
|
14
14
|
import { ZoomTurntableDock } from './zoomTurntableDock';
|
|
15
15
|
import { ZoomTurntableTip } from './zoomTurntableTip';
|
|
16
|
-
import { measureEntryVisible } from './measureVisible';
|
|
16
|
+
import { measureBottomBarHeight, measureEntryVisible } from './measureVisible';
|
|
17
17
|
import { ZOOM_ENTRY_ANCHOR_CLS } from './entryAnchor';
|
|
18
|
-
import { LONG_PRESS_MS, MOVE_TOLERANCE,
|
|
18
|
+
import { FORCE_SHOW_ZOOM_TIP, LONG_PRESS_MS, MOVE_TOLERANCE, TIP_RESHOW_DELAY, getZoomDockBottom, getZoomDockHeight, getZoomDockRight, ZOOM_DOCK_Z_INDEX, zoomTurntableClose, zoomTurntableDockId, zoomTurntableOpen, zoomTurntableTipId } from './constants';
|
|
19
19
|
import './zoomTurntableEntry.less';
|
|
20
20
|
/**
|
|
21
21
|
* 焦距转盘入口:左下角工具条中的图标,长按弹出转盘。
|
|
@@ -39,12 +39,22 @@ export const ZoomTurntableEntry = props => {
|
|
|
39
39
|
const {
|
|
40
40
|
playState,
|
|
41
41
|
screenType,
|
|
42
|
-
|
|
42
|
+
isVerticalFullLayout
|
|
43
43
|
} = useStore({
|
|
44
44
|
playState: props.playState,
|
|
45
45
|
screenType: props.screenType,
|
|
46
|
-
|
|
46
|
+
isVerticalFullLayout: props.isVerticalFullLayout
|
|
47
47
|
});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 竖屏两种布局,转盘的落位与是否让路完全不同:
|
|
51
|
+
* - 竖屏全屏(isVerticalFullLayout):底部工具条常驻在屏幕底部,转盘停在工具条**之上**,
|
|
52
|
+
* 工具条保持可见(能看到当前镜头、能直接点右下角那排按钮);
|
|
53
|
+
* - 普通竖屏:播放器只是页面顶部的一块,长按 tab 时把底部元素藏掉,
|
|
54
|
+
* 转盘**贴播放器底边**显示。
|
|
55
|
+
* 两种方式下转盘都落在播放器的底部区域。
|
|
56
|
+
*/
|
|
57
|
+
const isVerticalFull = screenType === 'vertical' && isVerticalFullLayout;
|
|
48
58
|
const {
|
|
49
59
|
multiLensReady,
|
|
50
60
|
lenses,
|
|
@@ -82,22 +92,39 @@ export const ZoomTurntableEntry = props => {
|
|
|
82
92
|
dockVisibleRef.current = false;
|
|
83
93
|
setDockVisible(false);
|
|
84
94
|
deleteContent('absolute', zoomTurntableDockId);
|
|
95
|
+
// 竖屏没藏过,这一发是幂等的(横屏才真正需要它恢复右侧控件)
|
|
85
96
|
event.emit(showAllComponent);
|
|
86
97
|
event.emit(startTimeToHideAllComponent);
|
|
87
98
|
});
|
|
88
|
-
const openDock = useMemoizedFn(() => {
|
|
99
|
+
const openDock = useMemoizedFn(async () => {
|
|
89
100
|
if (dockVisibleRef.current) {
|
|
90
101
|
closeDock();
|
|
91
102
|
return;
|
|
92
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* 竖屏全屏要让出底部工具条,高度按**实测**取:ui.tsx 给它写的是 48px,
|
|
106
|
+
* 而 `-full` 变体在 less 里还叠了 `padding-bottom: env(safe-area-inset-bottom)`,
|
|
107
|
+
* 实际占位跟机型 safe-area 有关,硬编码必然对不上。
|
|
108
|
+
* 长按本身已经等了 500ms,这里再等一帧测量,体感无差别;测不到就退回 48。
|
|
109
|
+
*/
|
|
110
|
+
const barHeight = isVerticalFull ? await measureBottomBarHeight() : null;
|
|
93
111
|
event.emit(widgetClick, {
|
|
94
112
|
widgetId: widgetLabs.ZOOM_TURNTABLE
|
|
95
113
|
});
|
|
96
114
|
// PTZ 与转盘都占底部 / 右侧,互斥
|
|
97
115
|
event.emit('ptzControlHide');
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
116
|
+
/**
|
|
117
|
+
* 只有竖屏全屏**不隐藏**其余元素:底部工具条常驻,转盘停在它上方 ——
|
|
118
|
+
* 这样能看到当前在哪个镜头,也能直接点右下角那排按钮,收起不必先点画面。
|
|
119
|
+
*
|
|
120
|
+
* 普通竖屏与横屏仍旧先藏:前者转盘要贴播放器底边、正压在工具条位置上,
|
|
121
|
+
* 后者转盘占右侧、与右侧控件正面重叠。
|
|
122
|
+
*
|
|
123
|
+
* 不要 emit('disablePlayerTap', true):点播放器收起转盘依赖 ui.tsx 正常派发 playerTap。
|
|
124
|
+
*/
|
|
125
|
+
if (!isVerticalFull) {
|
|
126
|
+
event.emit(hideAllComponent);
|
|
127
|
+
}
|
|
101
128
|
event.emit(pauseTimeToHideAllComponent);
|
|
102
129
|
if (!hasContent('absolute', zoomTurntableDockId)) {
|
|
103
130
|
// 必须用 CoverView 承载(不能设 absoluteNoCoverView):播放器是原生组件、层级更高,
|
|
@@ -107,14 +134,17 @@ export const ZoomTurntableEntry = props => {
|
|
|
107
134
|
content: dockProps => /*#__PURE__*/React.createElement(ZoomTurntableDock, dockProps),
|
|
108
135
|
absolutePosition: screenType === 'full' ? {
|
|
109
136
|
top: 0,
|
|
110
|
-
right:
|
|
137
|
+
right: getZoomDockRight(),
|
|
111
138
|
bottom: 0,
|
|
112
139
|
zIndex: ZOOM_DOCK_Z_INDEX
|
|
113
140
|
} : {
|
|
114
141
|
left: 0,
|
|
115
142
|
right: 0,
|
|
116
|
-
|
|
117
|
-
|
|
143
|
+
// 一律是带单位的 px 字符串 —— 裸数字会被当 rpx,见 constants 里 px() 的说明。
|
|
144
|
+
// 竖屏全屏让出底部工具条(实测高度,避免与 ui.tsx 的写死值不同步);
|
|
145
|
+
// 普通竖屏工具条已藏,直接下沉一份底部触摸余量贴住播放器底边。
|
|
146
|
+
bottom: getZoomDockBottom(isVerticalFull, barHeight),
|
|
147
|
+
height: getZoomDockHeight(isVerticalFull),
|
|
118
148
|
zIndex: ZOOM_DOCK_Z_INDEX
|
|
119
149
|
},
|
|
120
150
|
initProps: _objectSpread(_objectSpread({}, props), {}, {
|
|
@@ -138,6 +168,17 @@ export const ZoomTurntableEntry = props => {
|
|
|
138
168
|
};
|
|
139
169
|
}, [event, closeDock]);
|
|
140
170
|
|
|
171
|
+
/**
|
|
172
|
+
* 横竖屏切换、竖屏布局切换(展开/收起竖屏全屏)时都先收起转盘。
|
|
173
|
+
*
|
|
174
|
+
* dock 的 absolutePosition 只在 addContent 那一刻定死,三套落位差别很大
|
|
175
|
+
* (横屏右侧 / 竖屏全屏的工具条之上 / 普通竖屏贴播放器底边),
|
|
176
|
+
* 不收起的话浮层会留在旧位置上 —— 横竖屏之间尤其明显,连朝向都不对。
|
|
177
|
+
*/
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
if (dockVisibleRef.current) closeDock();
|
|
180
|
+
}, [screenType, isVerticalFull, closeDock]);
|
|
181
|
+
|
|
141
182
|
// 卸载 / 停流时兜底收起
|
|
142
183
|
useEffect(() => {
|
|
143
184
|
if (playState !== PlayState.PLAYING && dockVisibleRef.current) {
|
|
@@ -160,19 +201,58 @@ export const ZoomTurntableEntry = props => {
|
|
|
160
201
|
}, [multiLensReady, lenses.length, event]);
|
|
161
202
|
|
|
162
203
|
// ---------------- 圈选引导 ----------------
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* 把引导收起,并允许之后重新判定一次。
|
|
207
|
+
*
|
|
208
|
+
* 引导是挂在 absolute 槽位的浮层,停流 / 进入后台时它还留在最上层 ——
|
|
209
|
+
* 回到前台会先看见它盖在画面上,紧接着重新拉流把 playState 打断,它又消失,
|
|
210
|
+
* 观感就是「闪一下」。所以离开时主动收起,等重新出流稳定后再按可见性重新判定。
|
|
211
|
+
* 注意不写已读标记:用户还没点「知道了」,下次仍应提示。
|
|
212
|
+
*/
|
|
213
|
+
const dismissTipForRestart = useMemoizedFn(() => {
|
|
214
|
+
if (!hasContent('absolute', zoomTurntableTipId)) return;
|
|
215
|
+
deleteContent('absolute', zoomTurntableTipId);
|
|
216
|
+
tipCheckedRef.current = false;
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// 进入后台:先收起,避免回前台的第一帧就看到它压在重连画面上
|
|
220
|
+
usePageEvent('onHide', dismissTipForRestart);
|
|
221
|
+
|
|
222
|
+
// 停流 / 重连:同样先收起,等回到 PLAYING 再重新判定
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
if (playState !== PlayState.PLAYING) {
|
|
225
|
+
dismissTipForRestart();
|
|
226
|
+
}
|
|
227
|
+
}, [playState, dismissTipForRestart]);
|
|
228
|
+
|
|
229
|
+
// 横竖屏切换:入口位置整个变了,旧引导的圈选坐标已失效,必须先收起再按新布局重测。
|
|
230
|
+
// 不收的话横屏、竖屏两套引导会同时留在屏幕上(absolute 槽位不会自动清理)。
|
|
231
|
+
useEffect(() => {
|
|
232
|
+
dismissTipForRestart();
|
|
233
|
+
}, [screenType, dismissTipForRestart]);
|
|
234
|
+
|
|
163
235
|
// 入口在横向 ScrollView 内可能被裁掉,只有真正可见时才提示;能力异步就绪,故判定在 multiLensReady 之后
|
|
164
236
|
useEffect(() => {
|
|
165
237
|
if (disableTip || !multiLensReady || playState !== PlayState.PLAYING) return undefined;
|
|
166
238
|
if (tipCheckedRef.current) return undefined;
|
|
167
239
|
let cancelled = false;
|
|
168
240
|
const storage = new Storage(devId);
|
|
169
|
-
|
|
241
|
+
// 刚出流时布局还在沉降(左下角元素陆续渲染、宽度还在长),等一小会儿再测再展示,
|
|
242
|
+
// 免得引导刚画好就因为入口位置变化而歪掉,也避免出流瞬间弹出来
|
|
243
|
+
const timer = setTimeout(async () => {
|
|
170
244
|
// key 不存在时 getStorage 会 fail,这里不能让它把整条链路带崩
|
|
171
245
|
const read = await storage.get(zoomTurntableTipId).catch(() => null);
|
|
172
|
-
if (cancelled
|
|
246
|
+
if (cancelled) return;
|
|
247
|
+
// 调试开关打开时忽略已读记录,每次都展示
|
|
248
|
+
if (read && !FORCE_SHOW_ZOOM_TIP) return;
|
|
173
249
|
const rect = await measureEntryVisible();
|
|
174
250
|
if (cancelled || !rect) return;
|
|
175
251
|
tipCheckedRef.current = true;
|
|
252
|
+
// ctx.addContent 不按 id 去重,重复添加会在屏幕上留下两份引导,这里先清一次
|
|
253
|
+
if (hasContent('absolute', zoomTurntableTipId)) {
|
|
254
|
+
deleteContent('absolute', zoomTurntableTipId);
|
|
255
|
+
}
|
|
176
256
|
addContent('absolute', {
|
|
177
257
|
id: zoomTurntableTipId,
|
|
178
258
|
content: tipProps => /*#__PURE__*/React.createElement(ZoomTurntableTip, tipProps),
|
|
@@ -181,9 +261,10 @@ export const ZoomTurntableEntry = props => {
|
|
|
181
261
|
anchorRect: rect
|
|
182
262
|
})
|
|
183
263
|
});
|
|
184
|
-
})
|
|
264
|
+
}, TIP_RESHOW_DELAY);
|
|
185
265
|
return () => {
|
|
186
266
|
cancelled = true;
|
|
267
|
+
clearTimeout(timer);
|
|
187
268
|
};
|
|
188
269
|
}, [disableTip, multiLensReady, playState, screenType, devId]);
|
|
189
270
|
|
|
@@ -254,9 +335,6 @@ export const ZoomTurntableEntry = props => {
|
|
|
254
335
|
'ipc-player-plugin-zoom-turntable-tab-full': screenType === 'full',
|
|
255
336
|
'ipc-player-plugin-zoom-turntable-tab-active': active
|
|
256
337
|
}),
|
|
257
|
-
style: active && dockVisible ? {
|
|
258
|
-
backgroundColor: brandColor
|
|
259
|
-
} : undefined,
|
|
260
338
|
onTouchStart: onTouchStart,
|
|
261
339
|
onTouchMove: onTouchMove,
|
|
262
340
|
onTouchEnd: () => onTouchEnd(index),
|
|
@@ -7,7 +7,7 @@ import { widgetClick, widgetLabs } from '../../ui/constant';
|
|
|
7
7
|
import { Storage } from '../../utils/storage';
|
|
8
8
|
import Strings from '../../i18n';
|
|
9
9
|
import { measurePlayerRect } from './measureVisible';
|
|
10
|
-
import {
|
|
10
|
+
import { FORCE_SHOW_ZOOM_TIP, TIP_LINE_TOTAL, TIP_PANEL_EDGE_GAP, TIP_PANEL_LINE_GAP, TIP_PANEL_WIDTH, TIP_RING_PADDING, zoomTurntableTipId } from './constants';
|
|
11
11
|
import './zoomTurntableTip.less';
|
|
12
12
|
/**
|
|
13
13
|
* 焦距入口圈选引导(一次性)。
|
|
@@ -26,51 +26,63 @@ export const ZoomTurntableTip = props => {
|
|
|
26
26
|
} = useStore({
|
|
27
27
|
playState: props.playState
|
|
28
28
|
});
|
|
29
|
-
const [
|
|
29
|
+
const [layout, setLayout] = useState(null);
|
|
30
30
|
|
|
31
|
-
//
|
|
31
|
+
// 把入口的视口坐标换算成播放器容器内坐标。
|
|
32
|
+
// 圈选框按入口实测 rect 自适应:入口是横向倍率胶囊,宽度随镜头数(2 个约 96px、
|
|
33
|
+
// 3 个约 132px)变化,用固定尺寸框不住。
|
|
32
34
|
useEffect(() => {
|
|
33
35
|
if (!anchorRect) return undefined;
|
|
34
36
|
let cancelled = false;
|
|
35
37
|
measurePlayerRect().then(player => {
|
|
36
38
|
if (cancelled || !player) return;
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
const ringWidth = anchorRect.width + TIP_RING_PADDING * 2;
|
|
40
|
+
const ringHeight = anchorRect.height + TIP_RING_PADDING * 2;
|
|
41
|
+
const ringLeft = anchorRect.left - player.left - TIP_RING_PADDING;
|
|
42
|
+
const ringBottom = player.bottom - anchorRect.bottom - TIP_RING_PADDING;
|
|
43
|
+
// 文案面板以圈选中心对齐,但不越出播放器左右边缘
|
|
44
|
+
const maxLeft = Math.max(player.width - TIP_PANEL_WIDTH - TIP_PANEL_EDGE_GAP, TIP_PANEL_EDGE_GAP);
|
|
45
|
+
const panelLeft = Math.min(Math.max(ringLeft + ringWidth / 2 - TIP_PANEL_WIDTH / 2, TIP_PANEL_EDGE_GAP), maxLeft);
|
|
46
|
+
setLayout({
|
|
47
|
+
ring: {
|
|
48
|
+
left: `${ringLeft}px`,
|
|
49
|
+
bottom: `${ringBottom}px`,
|
|
50
|
+
width: `${ringWidth}px`,
|
|
51
|
+
height: `${ringHeight}px`
|
|
52
|
+
},
|
|
53
|
+
panel: {
|
|
54
|
+
left: `${panelLeft}px`,
|
|
55
|
+
bottom: `${ringBottom + ringHeight + TIP_LINE_TOTAL + TIP_PANEL_LINE_GAP}px`,
|
|
56
|
+
width: `${TIP_PANEL_WIDTH}px`
|
|
57
|
+
}
|
|
43
58
|
});
|
|
44
59
|
});
|
|
45
60
|
return () => {
|
|
46
61
|
cancelled = true;
|
|
47
62
|
};
|
|
48
63
|
}, [anchorRect]);
|
|
49
|
-
if (playState !== PlayState.PLAYING || !
|
|
64
|
+
if (playState !== PlayState.PLAYING || !layout) {
|
|
50
65
|
return null;
|
|
51
66
|
}
|
|
52
67
|
return /*#__PURE__*/React.createElement(CoverView, null, /*#__PURE__*/React.createElement(View, {
|
|
53
68
|
className: clsx('ipc-player-plugin-zoom-turntable-tip-ring'),
|
|
54
|
-
style:
|
|
55
|
-
}), /*#__PURE__*/React.createElement(
|
|
56
|
-
className: clsx('ipc-player-plugin-zoom-turntable-tip-
|
|
57
|
-
style:
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
69
|
+
style: layout.ring
|
|
70
|
+
}), /*#__PURE__*/React.createElement(View, {
|
|
71
|
+
className: clsx('ipc-player-plugin-zoom-turntable-tip-panel'),
|
|
72
|
+
style: layout.panel
|
|
73
|
+
}, /*#__PURE__*/React.createElement(Text, {
|
|
74
|
+
className: clsx('ipc-player-plugin-zoom-turntable-tip-text')
|
|
61
75
|
}, Strings.getLang('ipc_player_zoom_turntable_tip_text')), /*#__PURE__*/React.createElement(View, {
|
|
62
76
|
className: clsx('ipc-player-plugin-zoom-turntable-tip-button'),
|
|
63
|
-
style: {
|
|
64
|
-
left: ringStyle.left,
|
|
65
|
-
bottom: `calc(${ringStyle.bottom} + 56px)`
|
|
66
|
-
},
|
|
67
77
|
onClick: () => {
|
|
68
78
|
props.event.emit(widgetClick, {
|
|
69
79
|
widgetId: widgetLabs.ZOOM_TURNTABLE_TIP
|
|
70
80
|
});
|
|
71
81
|
deleteContent('absolute', zoomTurntableTipId);
|
|
82
|
+
// 调试开关打开时不落已读标记,免得自测一次就再也看不到
|
|
83
|
+
if (FORCE_SHOW_ZOOM_TIP) return;
|
|
72
84
|
const storage = new Storage(devId);
|
|
73
85
|
storage.set(zoomTurntableTipId, true);
|
|
74
86
|
}
|
|
75
|
-
}, Strings.getLang('ipc_player_tip_confirm')));
|
|
87
|
+
}, Strings.getLang('ipc_player_tip_confirm'))));
|
|
76
88
|
};
|
|
@@ -10,10 +10,13 @@
|
|
|
10
10
|
font-size: 14px;
|
|
11
11
|
color: #fff;
|
|
12
12
|
|
|
13
|
+
// 圈选框:尺寸与位置按入口实测 rect 由 JS 内联注入。
|
|
14
|
+
// 入口是横向倍率胶囊,用圆角矩形贴合,不用正圆(正圆框不住宽度、上下还会超出)。
|
|
13
15
|
.ipc-player-plugin-zoom-turntable-tip-ring {
|
|
14
16
|
position: absolute;
|
|
17
|
+
box-sizing: border-box;
|
|
15
18
|
border: 1px dashed rgba(255, 255, 255, 0.5);
|
|
16
|
-
border-radius:
|
|
19
|
+
border-radius: 12px;
|
|
17
20
|
// 超大扩散阴影把全屏压暗,只露出被圈选的入口
|
|
18
21
|
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.7);
|
|
19
22
|
|
|
@@ -23,13 +26,13 @@
|
|
|
23
26
|
position: absolute;
|
|
24
27
|
left: 50%;
|
|
25
28
|
width: 1px;
|
|
26
|
-
height:
|
|
29
|
+
height: 17px;
|
|
27
30
|
box-sizing: border-box;
|
|
28
31
|
border-left: 1px dashed rgba(255, 255, 255, 0.5);
|
|
29
32
|
transform: translateX(-50%) translateY(-100%);
|
|
30
33
|
}
|
|
31
34
|
|
|
32
|
-
//
|
|
35
|
+
// 引线末端的小圆点:17 虚线 + 4 圆点
|
|
33
36
|
&::before {
|
|
34
37
|
content: '';
|
|
35
38
|
position: absolute;
|
|
@@ -38,18 +41,31 @@
|
|
|
38
41
|
height: 4px;
|
|
39
42
|
border-radius: 50%;
|
|
40
43
|
background-color: rgba(255, 255, 255, 0.5);
|
|
41
|
-
transform: translateX(-50%) translateY(-
|
|
44
|
+
transform: translateX(-50%) translateY(-21px);
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
47
|
|
|
45
|
-
|
|
48
|
+
// 文案 + 按钮面板:整体定位一次并对齐圈选框中心,内部居中堆叠,
|
|
49
|
+
// 间距交给布局,不再靠魔法数各自偏移
|
|
50
|
+
.ipc-player-plugin-zoom-turntable-tip-panel {
|
|
46
51
|
position: absolute;
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-direction: column;
|
|
54
|
+
align-items: center;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.ipc-player-plugin-zoom-turntable-tip-text {
|
|
47
58
|
max-width: 200px;
|
|
59
|
+
line-height: 1.4;
|
|
60
|
+
text-align: center;
|
|
48
61
|
}
|
|
49
62
|
|
|
50
63
|
.ipc-player-plugin-zoom-turntable-tip-button {
|
|
51
|
-
|
|
52
|
-
|
|
64
|
+
margin-top: 12px;
|
|
65
|
+
// 比 multiCamera 的 tile-tip(12/32)收紧一圈:引导只是一句短提示,按钮不必那么重
|
|
66
|
+
padding: 7px 20px;
|
|
67
|
+
font-size: 12px;
|
|
68
|
+
line-height: 1.4;
|
|
53
69
|
border: 1px solid rgba(255, 255, 255, 0.5);
|
|
54
70
|
border-radius: 250px;
|
|
55
71
|
}
|