@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
|
@@ -6,7 +6,7 @@ import { useDpState } from '../../hooks/useDpState';
|
|
|
6
6
|
import { setScaleMultipleEvent, zoomLevelChange } from '../../ui/constant';
|
|
7
7
|
import { MOCK_ZOOM_SKILL, MULTI_ZOOM_DP_CODE, dumpChannelApis, isChannelChangeSupported, isZoomSkillValid, lensCount, parseZoomSkill, queryCurrentLens, showSmallMonitor, subscribeChannelChange, switchLens } from './lensSkill';
|
|
8
8
|
import { DEFAULT_STEP, D_MIN, buildMarks, buildTickValues, indexOfLensId, lensRange, matchLens, maxScaleMultiple, toDisplayZoom, toScaleMultiple, zoomBounds } from './lensModel';
|
|
9
|
-
import { CHANNEL_SETTLE_TIMEOUT, D_MATCH_EPS, GESTURE_SWITCH_DEBOUNCE, SWITCH_LENS_TIMEOUT, ZOOM_PREVIEW_THROTTLE, zoomTurntableLensChange, zoomTurntableSwitching, zoomTurntableZoomChange } from './constants';
|
|
9
|
+
import { CHANNEL_SETTLE_TIMEOUT, DOWN_SWITCH_COOLDOWN, DRAG_CROSS_SWITCH_DELAY, D_MATCH_EPS, FLOOR_HIT_COUNT, FLOOR_HIT_SETTLE_GUARD, FLOOR_HIT_WINDOW, GESTURE_SWITCH_DEBOUNCE, POST_SWITCH_APPLY_DELAY, SETTLE_APPLY_TIMEOUT, SWITCH_DEDUPE_WINDOW, SWITCH_LENS_TIMEOUT, ZOOM_PREVIEW_THROTTLE, zoomTurntableLensChange, zoomTurntableSwitching, zoomTurntableZoomChange } from './constants';
|
|
10
10
|
/**
|
|
11
11
|
* 内部调试开关:置 true 时用 MOCK_ZOOM_SKILL 顶替 ipc_multi_zoom 的上报值。
|
|
12
12
|
*
|
|
@@ -42,6 +42,37 @@ const pendingSwitchStore = new Map();
|
|
|
42
42
|
*/
|
|
43
43
|
const smallMonitorStore = new Map();
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* 切镜头收尾后正在补下发的目标 d(按 devId)。
|
|
47
|
+
*
|
|
48
|
+
* 收尾时读数 / tab 已经按目标值落地,画面却要等 setScaleMultiple 走完才到位;
|
|
49
|
+
* 中间原生层会先把画面复位到 1 倍并回读一帧。不挡掉的话读数会先闪回镜头正倍数再跳到目标值。
|
|
50
|
+
*/
|
|
51
|
+
const settleApplyStore = new Map();
|
|
52
|
+
|
|
53
|
+
/** 触底计数(按 devId):画面已夹在 d = 1、用户还在继续缩小的连续帧数 */
|
|
54
|
+
const floorHitStore = new Map();
|
|
55
|
+
|
|
56
|
+
/** 最近一次切镜头收尾的时间(按 devId):收尾自身的 d = 1 回读不算触底 */
|
|
57
|
+
const settledAtStore = new Map();
|
|
58
|
+
|
|
59
|
+
/** 最近一次「触底自动下切」的时间(按 devId):冷却用 */
|
|
60
|
+
const downSwitchAtStore = new Map();
|
|
61
|
+
|
|
62
|
+
/** 最近一次切镜头下发(按 devId):Entry / Dock 两份 hook 判出同一次切换时去重 */
|
|
63
|
+
const switchReqStore = new Map();
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 是否有转盘正在被拖动(按 devId)。
|
|
67
|
+
*
|
|
68
|
+
* 组件内的 draggingRef 只挡自己那份的回读,管不到别的实例:拖动中 previewZoom 会把
|
|
69
|
+
* 夹住的 d 连续下发,回读到 Entry 那份就成了「一直在推 d = 1」,正好被触底判定当成
|
|
70
|
+
* 「用户还在往下缩」,于是拖动落地和触底下切会同时切镜头。拖动路径自带落地逻辑,
|
|
71
|
+
* 手势那套自动切镜头必须整体让路,所以这个标记要跨实例共享。
|
|
72
|
+
*/
|
|
73
|
+
const draggingStore = new Map();
|
|
74
|
+
const isDraggingAnywhere = devId => draggingStore.get(devId) === true;
|
|
75
|
+
|
|
45
76
|
/** 自增序号:连着切到同一个镜头时,靠它认出「pending 还是不是我这次的」 */
|
|
46
77
|
let switchSeq = 0;
|
|
47
78
|
/** 是否正在切镜头(屏蔽回读用) */
|
|
@@ -55,6 +86,46 @@ const takePendingSwitch = devId => {
|
|
|
55
86
|
return pending;
|
|
56
87
|
};
|
|
57
88
|
|
|
89
|
+
/**
|
|
90
|
+
* 补差值下发期间的中间态回读要丢掉。
|
|
91
|
+
*
|
|
92
|
+
* 只丢「d ≈ 1」这一种 —— 那是原生层切完通道后画面自己的复位帧。ui.tsx 里
|
|
93
|
+
* 「先对齐 actual 再设 target」产生的那一帧已被它自己的 alignPending 抑制,不会到这儿。
|
|
94
|
+
* 收到任何非复位值就认为补差值已落地,解除屏蔽。
|
|
95
|
+
*/
|
|
96
|
+
const shouldDropSettleEcho = (devId, d) => {
|
|
97
|
+
const applying = settleApplyStore.get(devId);
|
|
98
|
+
if (!applying) return false;
|
|
99
|
+
if (Date.now() > applying.expireAt) {
|
|
100
|
+
// 原生层没按目标值回读(量化 / 下发失败),不能把读数永久冻住
|
|
101
|
+
console.warn('[zoomTurntable] 补差值回读超时,解除中间态屏蔽', applying.d);
|
|
102
|
+
settleApplyStore.delete(devId);
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
if (Math.abs(d - D_MIN) <= D_MATCH_EPS) return true;
|
|
106
|
+
settleApplyStore.delete(devId);
|
|
107
|
+
return false;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 抢一次切镜头的下发权。
|
|
112
|
+
* Entry / Dock 都订阅 zoomLevelChange,同一帧回读会让两份 hook 判出同一次切换;
|
|
113
|
+
* 不去重就是两发 switchChannel(第二发还会把第一发的 pending 作废,读数更乱)。
|
|
114
|
+
*/
|
|
115
|
+
const claimSwitch = (devId, index) => {
|
|
116
|
+
const last = switchReqStore.get(devId);
|
|
117
|
+
const now = Date.now();
|
|
118
|
+
if (last && last.index === index && now - last.at < SWITCH_DEDUPE_WINDOW) {
|
|
119
|
+
console.log('[zoomTurntable] 同一次切镜头已由另一实例下发,跳过', index);
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
switchReqStore.set(devId, {
|
|
123
|
+
index,
|
|
124
|
+
at: now
|
|
125
|
+
});
|
|
126
|
+
return true;
|
|
127
|
+
};
|
|
128
|
+
|
|
58
129
|
/**
|
|
59
130
|
* 焦距能力与双层缩放状态机。
|
|
60
131
|
* zoomSkill / zoomLensIndex 存在 ctx 共享 atom 上,Entry / Dock / RealTimeMagnification 读同一份。
|
|
@@ -101,6 +172,19 @@ export const useLensZoom = options => {
|
|
|
101
172
|
const gestureTimerRef = useRef();
|
|
102
173
|
/** 拖动中连续下发的节流时间戳 */
|
|
103
174
|
const previewAtRef = useRef(0);
|
|
175
|
+
/** 触底下切的 trailing 防抖(等手指停下再切,免得和正在进行的手势打架) */
|
|
176
|
+
const downTimerRef = useRef();
|
|
177
|
+
/** 拖动跨界后「手停住」的 trailing 防抖 */
|
|
178
|
+
const dragCrossTimerRef = useRef();
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* 自动切镜头(手势触底 / 跨临界)的驱动方:只由能力加载方(Entry)负责。
|
|
182
|
+
*
|
|
183
|
+
* Entry / Dock 各跑一份 hook,都订阅 zoomLevelChange。触底判定是**按帧计数**的,
|
|
184
|
+
* 两份都计数会让阈值形同减半;Entry 常驻(转盘展开时它只是被 CSS 藏起来,没卸载),
|
|
185
|
+
* 拿它当唯一驱动方最稳。拖动路径反过来只有 Dock 有,各管一段互不重叠。
|
|
186
|
+
*/
|
|
187
|
+
const isAutoSwitchDriver = !skipLoad;
|
|
104
188
|
const lensIndexRef = useRef(zoomLensIndex);
|
|
105
189
|
lensIndexRef.current = zoomLensIndex;
|
|
106
190
|
const skillRef = useRef(zoomSkill);
|
|
@@ -238,42 +322,79 @@ export const useLensZoom = options => {
|
|
|
238
322
|
});
|
|
239
323
|
const beginDrag = useMemoizedFn(value => {
|
|
240
324
|
draggingRef.current = true;
|
|
325
|
+
draggingStore.set(devId, true);
|
|
241
326
|
setCurM(value);
|
|
242
|
-
|
|
327
|
+
// 切镜头进行中(拖动跨界会在手指还按着时切):画面归收尾流程管,这里只更新读数
|
|
328
|
+
if (!isSwitching(devId)) previewZoom(value);
|
|
329
|
+
scheduleDragCross(value);
|
|
243
330
|
});
|
|
244
331
|
|
|
245
332
|
/**
|
|
246
|
-
* 镜头已切到 index
|
|
333
|
+
* 镜头已切到 index 后的收尾:把画面落到目标倍率上(**补差值**),读数与 tab 同步。
|
|
334
|
+
*
|
|
247
335
|
* 正常路径由 channelChange 触发(等设备真正切完再动画面,中间态更干净);
|
|
248
336
|
* 宿主不支持该事件时,退回 switchChannel 成功即收尾。
|
|
337
|
+
*
|
|
338
|
+
* @param targetM 目标画面绝对倍率。本端下发的切换都会带(从 pendingSwitch 取),
|
|
339
|
+
* 落位就是用户实际要的那个值:转盘拖到 2.5 就落 2.5、手势停在 3.5 就落 3.5、
|
|
340
|
+
* 触底下切落「上一镜头上限 − 1 step」。**不带**时才归该镜头的光学正倍数 ——
|
|
341
|
+
* 只有外部来源(设备侧 / 其他端切镜头)走这条,那时本端没有目标值可言。
|
|
342
|
+
*
|
|
343
|
+
* 早期设计是「切镜头一律归 1 倍、不补差值」,那会让下切必然掉到新镜头的 basicRatio:
|
|
344
|
+
* 从 3.0 往下拖到 2.5,切到短焦后画面直接变成 1.0,跨度大到像是坏了。
|
|
249
345
|
*/
|
|
250
|
-
const settleLensSwitched = useMemoizedFn(index => {
|
|
251
|
-
var
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
346
|
+
const settleLensSwitched = useMemoizedFn((index, targetM) => {
|
|
347
|
+
var _skill$lenses;
|
|
348
|
+
const skill = skillRef.current;
|
|
349
|
+
const lens = skill === null || skill === void 0 || (_skill$lenses = skill.lenses) === null || _skill$lenses === void 0 ? void 0 : _skill$lenses[index];
|
|
350
|
+
if (!skill || !lens) return;
|
|
351
|
+
// 目标值夹进新镜头区间:跨界落点可能略微越界(容差、浮点毛刺)
|
|
352
|
+
const [lo, hi] = lensRange(skill, index);
|
|
353
|
+
const finalM = targetM == null ? lens.basicRatio : Math.min(hi, Math.max(lo, targetM));
|
|
354
|
+
const d = toScaleMultiple(skill, index, finalM);
|
|
355
|
+
const isBase = d <= D_MIN + D_MATCH_EPS;
|
|
356
|
+
console.log('[scaleMultiple] zoomTurntable 切镜头收尾', `lensIndex=${index}`, `finalM=${finalM.toFixed(2)}`, `d=${d.toFixed(3)}`, isBase ? '(归正倍数)' : '(补差值)', `lastD=${getLastD(devId)}`);
|
|
357
|
+
|
|
358
|
+
// 状态先落地:读数 / tab 立刻对齐,不等画面下发走完 P2P
|
|
359
|
+
syncToLens(index, d);
|
|
257
360
|
markSwitching(-1);
|
|
258
|
-
|
|
259
|
-
|
|
361
|
+
settledAtStore.set(devId, Date.now());
|
|
362
|
+
floorHitStore.delete(devId);
|
|
363
|
+
// 小窗跟最终 d 走:补差值后 d > 1(画面不在正倍数上),小窗该开着 ——
|
|
364
|
+
// 旧实现无条件 applySmallMonitor(false),补差值后就与画面状态不符了
|
|
365
|
+
syncSmallMonitorByD(d);
|
|
260
366
|
event.emit(zoomTurntableLensChange, {
|
|
261
367
|
lensId: lens.id,
|
|
262
368
|
lensIndex: index,
|
|
263
|
-
value:
|
|
369
|
+
value: finalM
|
|
264
370
|
});
|
|
265
371
|
event.emit(zoomTurntableZoomChange, {
|
|
266
|
-
curM:
|
|
267
|
-
d
|
|
372
|
+
curM: finalM,
|
|
373
|
+
d,
|
|
268
374
|
lensIndex: index
|
|
269
375
|
});
|
|
376
|
+
if (isBase) {
|
|
377
|
+
// 归正倍数:与旧行为一致,立即下发
|
|
378
|
+
event.emit(setScaleMultipleEvent, D_MIN);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
// 补差值:新流刚起来,立刻打 scaleMultiple 有被吞的风险,延一拍;
|
|
382
|
+
// 这段窗口内的复位帧回读由 settleApplyStore 挡掉
|
|
383
|
+
settleApplyStore.set(devId, {
|
|
384
|
+
d,
|
|
385
|
+
expireAt: Date.now() + SETTLE_APPLY_TIMEOUT
|
|
386
|
+
});
|
|
387
|
+
setTimeout(() => {
|
|
388
|
+
console.log('[scaleMultiple] zoomTurntable 补差值下发', d, `lensIndex=${index}`);
|
|
389
|
+
event.emit(setScaleMultipleEvent, d);
|
|
390
|
+
}, POST_SWITCH_APPLY_DELAY);
|
|
270
391
|
});
|
|
271
392
|
|
|
272
393
|
/** 把目标倍率落到指定镜头上:同镜头内下发本地缩放,跨镜头则切物理镜头并归 1 倍 */
|
|
273
394
|
const applyToLens = useMemoizedFn(async (nextIndex, targetM) => {
|
|
274
|
-
var _skill$
|
|
395
|
+
var _skill$lenses2;
|
|
275
396
|
const skill = skillRef.current;
|
|
276
|
-
if (!(skill !== null && skill !== void 0 && (_skill$
|
|
397
|
+
if (!(skill !== null && skill !== void 0 && (_skill$lenses2 = skill.lenses) !== null && _skill$lenses2 !== void 0 && _skill$lenses2[nextIndex])) return;
|
|
277
398
|
setCurM(targetM);
|
|
278
399
|
const prevIndex = lensIndexRef.current;
|
|
279
400
|
if (nextIndex === prevIndex) {
|
|
@@ -290,6 +411,9 @@ export const useLensZoom = options => {
|
|
|
290
411
|
return;
|
|
291
412
|
}
|
|
292
413
|
|
|
414
|
+
// Entry / Dock 同一帧判出同一次切换时只放一次过(读数已在上面各自更新过)
|
|
415
|
+
if (!claimSwitch(devId, nextIndex)) return;
|
|
416
|
+
|
|
293
417
|
// 切物理镜头:从下发到 channelChange 回来这段屏蔽回读
|
|
294
418
|
console.log('[zoomTurntable] 切换镜头', `${prevIndex} -> ${nextIndex}`, `channel=${skill.lenses[nextIndex].id}`, `targetM=${targetM.toFixed(2)}`);
|
|
295
419
|
takePendingSwitch(devId); // 上一次没结算完的直接作废,以最新这次为准
|
|
@@ -305,6 +429,7 @@ export const useLensZoom = options => {
|
|
|
305
429
|
pendingSwitchStore.set(devId, {
|
|
306
430
|
seq,
|
|
307
431
|
index: nextIndex,
|
|
432
|
+
targetM,
|
|
308
433
|
timer: setTimeout(() => {
|
|
309
434
|
pendingSwitchStore.delete(devId);
|
|
310
435
|
markSwitching(-1);
|
|
@@ -328,7 +453,7 @@ export const useLensZoom = options => {
|
|
|
328
453
|
// 那时设备已经真的切过去了,画面不会先跳一下再稳定
|
|
329
454
|
if (!isChannelChangeSupported()) {
|
|
330
455
|
takePendingSwitch(devId);
|
|
331
|
-
settleLensSwitched(nextIndex);
|
|
456
|
+
settleLensSwitched(nextIndex, targetM);
|
|
332
457
|
return;
|
|
333
458
|
}
|
|
334
459
|
|
|
@@ -345,13 +470,75 @@ export const useLensZoom = options => {
|
|
|
345
470
|
// 有到达但没有「镜头切换到位」→ 被 deviceId 或 channel 校验挡掉了
|
|
346
471
|
console.warn('[zoomTurntable] 等 channelChange 超时,按下发结果收尾', `${prevIndex} -> ${nextIndex}`, `channel=${skill.lenses[nextIndex].id}`, `${CHANNEL_SETTLE_TIMEOUT}ms`, `onChannelChange 可用=${isChannelChangeSupported()}`);
|
|
347
472
|
dumpChannelApis();
|
|
348
|
-
settleLensSwitched(nextIndex);
|
|
473
|
+
settleLensSwitched(nextIndex, targetM);
|
|
349
474
|
}, CHANNEL_SETTLE_TIMEOUT);
|
|
350
475
|
});
|
|
351
476
|
|
|
477
|
+
/**
|
|
478
|
+
* 拖动跨镜头临界:过界后**手停住** DRAG_CROSS_SWITCH_DELAY 就地切镜头,画面跟着拖动走。
|
|
479
|
+
*
|
|
480
|
+
* previewZoom 把下发值夹在当前镜头区间内,所以拖过界之后画面是「冻」住的 ——
|
|
481
|
+
* 只等松手才切的话,从 3.0 往下拖到 2.5 的一路上画面纹丝不动,手感像是没响应。
|
|
482
|
+
* 逐帧切也不行:switchChannel 是 P2P、几百毫秒级,逐帧下发会把通道打爆。
|
|
483
|
+
* 折中成 trailing 防抖:值一直在变就一直不切,停住才切。
|
|
484
|
+
*
|
|
485
|
+
* 切换期间 draggingRef 保持 true(见 onChannelChange 里的说明),松手后 applyZoom
|
|
486
|
+
* 再按最终值落一次 —— 那时镜头通常已经对了,走的是同镜头内下发 d 的分支。
|
|
487
|
+
*/
|
|
488
|
+
const scheduleDragCross = useMemoizedFn(value => {
|
|
489
|
+
const clearPending = () => {
|
|
490
|
+
if (dragCrossTimerRef.current) {
|
|
491
|
+
clearTimeout(dragCrossTimerRef.current);
|
|
492
|
+
dragCrossTimerRef.current = undefined;
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
const skill = skillRef.current;
|
|
496
|
+
if (!skill) return;
|
|
497
|
+
// 回到当前镜头区间内 / 已在切换中:撤掉待切
|
|
498
|
+
if (matchLens(skill, value) === lensIndexRef.current || isSwitching(devId)) {
|
|
499
|
+
clearPending();
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
clearPending();
|
|
503
|
+
dragCrossTimerRef.current = setTimeout(() => {
|
|
504
|
+
dragCrossTimerRef.current = undefined;
|
|
505
|
+
// 已松手 → 交给 applyZoom 落地,不在这儿抢
|
|
506
|
+
if (!draggingRef.current || isSwitching(devId)) return;
|
|
507
|
+
const cur = skillRef.current;
|
|
508
|
+
if (!cur) return;
|
|
509
|
+
const nextIndex = matchLens(cur, value);
|
|
510
|
+
if (nextIndex === lensIndexRef.current) return;
|
|
511
|
+
console.log('[zoomTurntable] 拖动跨界停留,就地切镜头', `${lensIndexRef.current} -> ${nextIndex}`, `targetM=${value.toFixed(2)}`);
|
|
512
|
+
applyToLens(nextIndex, value);
|
|
513
|
+
}, DRAG_CROSS_SWITCH_DELAY);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* 拖动状态兜底复位。
|
|
518
|
+
*
|
|
519
|
+
* `beginDrag` 置上的 draggingRef 正常由 `applyZoom`(onChangeEnd)复位,但异常路径下
|
|
520
|
+
* onChangeEnd 可能永远不来 —— 拖动中转盘被收起(点画面 / 切横竖屏 / 停流)就是这种情况。
|
|
521
|
+
* draggingRef 只要还挂着 true,本实例后续所有 zoomLevelChange 都会被 early return 丢掉,
|
|
522
|
+
* 手势缩放的读数就再也不更新了。挂载方在收尾路径上调一次它即可。
|
|
523
|
+
*/
|
|
524
|
+
const endDrag = useMemoizedFn(() => {
|
|
525
|
+
draggingRef.current = false;
|
|
526
|
+
draggingStore.delete(devId);
|
|
527
|
+
if (dragCrossTimerRef.current) {
|
|
528
|
+
clearTimeout(dragCrossTimerRef.current);
|
|
529
|
+
dragCrossTimerRef.current = undefined;
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
|
|
352
533
|
/** 拖动结束:按倍率匹配镜头后落地 */
|
|
353
534
|
const applyZoom = useMemoizedFn(async targetM => {
|
|
354
535
|
draggingRef.current = false;
|
|
536
|
+
draggingStore.delete(devId);
|
|
537
|
+
// 松手即是最终值,待切的那次作废,直接按最终值走
|
|
538
|
+
if (dragCrossTimerRef.current) {
|
|
539
|
+
clearTimeout(dragCrossTimerRef.current);
|
|
540
|
+
dragCrossTimerRef.current = undefined;
|
|
541
|
+
}
|
|
355
542
|
const skill = skillRef.current;
|
|
356
543
|
if (!skill) return;
|
|
357
544
|
await applyToLens(matchLens(skill, targetM), targetM);
|
|
@@ -359,11 +546,12 @@ export const useLensZoom = options => {
|
|
|
359
546
|
|
|
360
547
|
/** 点击倍率 tab:直接切到该镜头并归位到其光学倍数;不走 matchLens,免得容差判到相邻镜头 */
|
|
361
548
|
const switchToLens = useMemoizedFn(async index => {
|
|
362
|
-
var _skill$
|
|
549
|
+
var _skill$lenses3;
|
|
363
550
|
const skill = skillRef.current;
|
|
364
|
-
const lens = skill === null || skill === void 0 || (_skill$
|
|
551
|
+
const lens = skill === null || skill === void 0 || (_skill$lenses3 = skill.lenses) === null || _skill$lenses3 === void 0 ? void 0 : _skill$lenses3[index];
|
|
365
552
|
if (!lens) return;
|
|
366
553
|
draggingRef.current = false;
|
|
554
|
+
draggingStore.delete(devId);
|
|
367
555
|
await applyToLens(index, lens.basicRatio);
|
|
368
556
|
});
|
|
369
557
|
|
|
@@ -394,9 +582,11 @@ export const useLensZoom = options => {
|
|
|
394
582
|
return; // 外部来源的重复事件
|
|
395
583
|
}
|
|
396
584
|
console.log('[zoomTurntable] 镜头切换到位', `${lensIndexRef.current} -> ${index}`, pending ? '(本端下发)' : '(外部来源)');
|
|
397
|
-
//
|
|
398
|
-
|
|
399
|
-
|
|
585
|
+
// 只有外部来源(设备侧 / 其他端)才作废进行中的拖动 —— 读数得以设备为准。
|
|
586
|
+
// 本端下发的那次不能动 draggingRef:拖动跨界会在**手指还按着**的时候切镜头,
|
|
587
|
+
// 置 false 会放开回读屏蔽,回读帧和拖动帧互相打架,指针会抖。
|
|
588
|
+
if (!pending) draggingRef.current = false;
|
|
589
|
+
settleLensSwitched(index, pending === null || pending === void 0 ? void 0 : pending.targetM);
|
|
400
590
|
});
|
|
401
591
|
useEffect(() => {
|
|
402
592
|
// 订没订上必须能从日志看出来:没订上时切镜头一定走超时回滚,两者表象一样
|
|
@@ -422,15 +612,94 @@ export const useLensZoom = options => {
|
|
|
422
612
|
gestureTimerRef.current = setTimeout(() => {
|
|
423
613
|
gestureTimerRef.current = undefined;
|
|
424
614
|
const skill = skillRef.current;
|
|
425
|
-
if (!skill || draggingRef.current || isSwitching(devId)) return;
|
|
615
|
+
if (!skill || draggingRef.current || isDraggingAnywhere(devId) || isSwitching(devId)) return;
|
|
426
616
|
const nextIndex = matchLens(skill, value);
|
|
427
617
|
if (nextIndex === lensIndexRef.current) return;
|
|
428
618
|
console.log('[zoomTurntable] 手势跨临界点,切换镜头', `curM=${value.toFixed(2)}`, `${lensIndexRef.current} -> ${nextIndex}`);
|
|
429
619
|
applyToLens(nextIndex, value);
|
|
430
620
|
}, GESTURE_SWITCH_DEBOUNCE);
|
|
431
621
|
});
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* 触底继续缩 → 往下切一个镜头(trailing 防抖,等手指停下再切)。
|
|
625
|
+
*
|
|
626
|
+
* 现象:在非首个镜头上把画面缩到该镜头的光学正倍数后(d = 1,协议下限),用户继续
|
|
627
|
+
* 双指往里收,播放器的 d 已经夹住动不了,画面纹丝不动,读数也停在 basicRatio。
|
|
628
|
+
* 此时 zoomLevelChange 仍然按帧推 d = 1 上来 —— 这就是唯一可用的信号
|
|
629
|
+
* (detail.scaleMode「自由缩放状态标识」实测不准,不能拿来判定)。
|
|
630
|
+
*
|
|
631
|
+
* 落位取「上一个镜头区间上限 − 1 step」(如 3.0 → 2.9):那正是画面能继续变小的第一格,
|
|
632
|
+
* 换算到短焦上是 d ≈ 3.85,用户接着往里收就能顺畅地一路缩到 1.0。
|
|
633
|
+
*/
|
|
634
|
+
const scheduleDownSwitch = useMemoizedFn(() => {
|
|
635
|
+
var _downSwitchAtStore$ge;
|
|
636
|
+
if (Date.now() - ((_downSwitchAtStore$ge = downSwitchAtStore.get(devId)) !== null && _downSwitchAtStore$ge !== void 0 ? _downSwitchAtStore$ge : 0) < DOWN_SWITCH_COOLDOWN) return;
|
|
637
|
+
if (downTimerRef.current) clearTimeout(downTimerRef.current);
|
|
638
|
+
downTimerRef.current = setTimeout(() => {
|
|
639
|
+
var _skill$lenses$target$, _skill$lenses4;
|
|
640
|
+
downTimerRef.current = undefined;
|
|
641
|
+
const skill = skillRef.current;
|
|
642
|
+
const index = lensIndexRef.current;
|
|
643
|
+
if (!skill || index <= 0) return;
|
|
644
|
+
if (draggingRef.current || isDraggingAnywhere(devId) || isSwitching(devId)) return;
|
|
645
|
+
// 防抖这 500ms 里可能又被放大了,切之前再确认画面确实还夹在正倍数上
|
|
646
|
+
if (getLastD(devId) > D_MIN + D_MATCH_EPS) {
|
|
647
|
+
floorHitStore.delete(devId);
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
const target = index - 1;
|
|
651
|
+
const [, upper] = lensRange(skill, target);
|
|
652
|
+
const stepOfTarget = (_skill$lenses$target$ = (_skill$lenses4 = skill.lenses) === null || _skill$lenses4 === void 0 || (_skill$lenses4 = _skill$lenses4[target]) === null || _skill$lenses4 === void 0 ? void 0 : _skill$lenses4.step) !== null && _skill$lenses$target$ !== void 0 ? _skill$lenses$target$ : DEFAULT_STEP;
|
|
653
|
+
const targetM = upper - stepOfTarget;
|
|
654
|
+
downSwitchAtStore.set(devId, Date.now());
|
|
655
|
+
floorHitStore.delete(devId);
|
|
656
|
+
console.log('[zoomTurntable] 触底继续缩,向下切镜头', `${index} -> ${target}`, `targetM=${targetM.toFixed(2)}`);
|
|
657
|
+
applyToLens(target, targetM);
|
|
658
|
+
}, GESTURE_SWITCH_DEBOUNCE);
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* 触底计数。返回 true 表示已排下切,本帧不再走上界 / 防抖那套判定。
|
|
663
|
+
* 只由驱动方计数 —— 两份 hook 都数会让阈值形同减半。
|
|
664
|
+
*/
|
|
665
|
+
const handleFloorHit = useMemoizedFn(d => {
|
|
666
|
+
var _settledAtStore$get;
|
|
667
|
+
if (!isAutoSwitchDriver) return false;
|
|
668
|
+
// 拖动中:落地由 applyZoom / 跨界停留负责,触底那套整体让路
|
|
669
|
+
if (isDraggingAnywhere(devId)) {
|
|
670
|
+
floorHitStore.delete(devId);
|
|
671
|
+
return false;
|
|
672
|
+
}
|
|
673
|
+
// 不在正倍数上:正常缩放,清计数
|
|
674
|
+
if (d > D_MIN + D_MATCH_EPS) {
|
|
675
|
+
floorHitStore.delete(devId);
|
|
676
|
+
return false;
|
|
677
|
+
}
|
|
678
|
+
// 首个镜头已经是倍率域下界,再缩没有镜头可切
|
|
679
|
+
if (lensIndexRef.current <= 0) return false;
|
|
680
|
+
const now = Date.now();
|
|
681
|
+
// 收尾自身会引发一到两帧 d = 1 回读,静默期内不计数,否则刚切上去又被切回来
|
|
682
|
+
if (now - ((_settledAtStore$get = settledAtStore.get(devId)) !== null && _settledAtStore$get !== void 0 ? _settledAtStore$get : 0) < FLOOR_HIT_SETTLE_GUARD) return false;
|
|
683
|
+
const prev = floorHitStore.get(devId);
|
|
684
|
+
const count = prev && now - prev.lastAt <= FLOOR_HIT_WINDOW ? prev.count + 1 : 1;
|
|
685
|
+
floorHitStore.set(devId, {
|
|
686
|
+
count,
|
|
687
|
+
lastAt: now
|
|
688
|
+
});
|
|
689
|
+
// 每一帧都打:真机上「触底还缩不缩得动」全靠这条日志判断 ——
|
|
690
|
+
// 如果原生只在 d 真正变化时才推事件,这里会永远停在 count=1、下切一次都不会触发,
|
|
691
|
+
// 那就说明这条信号不成立,得改由 APP 侧补「手势下界」参数(协议 §4.3 第 5 条)
|
|
692
|
+
console.log('[zoomTurntable] 触底回读', `count=${count}/${FLOOR_HIT_COUNT}`, `lensIndex=${lensIndexRef.current}`);
|
|
693
|
+
// 第一帧是「正常缩到正倍数」,从第二帧起才是「夹住了还在缩」
|
|
694
|
+
if (count < FLOOR_HIT_COUNT) return false;
|
|
695
|
+
console.log('[zoomTurntable] 画面已夹在正倍数仍有缩放回读,排下切', `count=${count}`);
|
|
696
|
+
scheduleDownSwitch();
|
|
697
|
+
return true;
|
|
698
|
+
});
|
|
432
699
|
const onZoomLevelChange = useMemoizedFn(d => {
|
|
433
700
|
if (!(d > 0)) return;
|
|
701
|
+
// 补差值下发期间原生画面会先复位到 1 倍,那一帧不能让它把读数拽回镜头正倍数
|
|
702
|
+
if (shouldDropSettleEcho(devId, d)) return;
|
|
434
703
|
setLastD(devId, d);
|
|
435
704
|
// 切镜头途中的中间态不算数(收尾时统一关掉),拖动中画面还没动
|
|
436
705
|
if (draggingRef.current || isSwitching(devId)) return;
|
|
@@ -446,6 +715,13 @@ export const useLensZoom = options => {
|
|
|
446
715
|
lensIndex: lensIndexRef.current
|
|
447
716
|
});
|
|
448
717
|
|
|
718
|
+
// 触底继续缩:往下切一个镜头(下界,与下面的上界对称)
|
|
719
|
+
if (handleFloorHit(d)) return;
|
|
720
|
+
if (!isAutoSwitchDriver) return;
|
|
721
|
+
// 拖动中:previewZoom 把下发值夹在当前镜头区间内,回读会一直贴着区间边界,
|
|
722
|
+
// 不让路的话「触到上界立即切」会和拖动跨界停留同时切镜头
|
|
723
|
+
if (isDraggingAnywhere(devId)) return;
|
|
724
|
+
|
|
449
725
|
// 画面已经顶到当前镜头区间的上界(也就是下一个镜头的光学倍数,如 1x 镜头放到 3.0):
|
|
450
726
|
// 这个边界值在归属上属于下一个镜头,必须立刻切过去。
|
|
451
727
|
// 走 trailing 防抖的话,那 500ms 里 tab 会挂着「下一镜头的值」显示在当前镜头上。
|
|
@@ -472,6 +748,8 @@ export const useLensZoom = options => {
|
|
|
472
748
|
useEffect(() => {
|
|
473
749
|
return () => {
|
|
474
750
|
if (gestureTimerRef.current) clearTimeout(gestureTimerRef.current);
|
|
751
|
+
if (downTimerRef.current) clearTimeout(downTimerRef.current);
|
|
752
|
+
if (dragCrossTimerRef.current) clearTimeout(dragCrossTimerRef.current);
|
|
475
753
|
};
|
|
476
754
|
}, []);
|
|
477
755
|
const [min, max] = zoomSkill ? zoomBounds(zoomSkill) : [D_MIN, D_MIN];
|
|
@@ -495,6 +773,7 @@ export const useLensZoom = options => {
|
|
|
495
773
|
ticks: zoomSkill ? buildTickValues(zoomSkill) : [],
|
|
496
774
|
beginDrag,
|
|
497
775
|
applyZoom,
|
|
776
|
+
endDrag,
|
|
498
777
|
switchToLens
|
|
499
778
|
};
|
|
500
779
|
};
|