@ray-js/ipc-player-integration 0.0.50-beta.4 → 0.0.50-beta.6
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/features/initPlayerWidgets/index.d.ts +2 -1
- package/lib/features/initPlayerWidgets/index.js +2 -1
- package/lib/ui/bottomLeftContent.js +69 -26
- package/lib/ui/bottomRightContent.js +3 -1
- package/lib/ui/ui.js +11 -4
- package/lib/ui/ui.less +25 -1
- package/lib/widgets/zoomTurntable/lensSkill.d.ts +8 -0
- package/lib/widgets/zoomTurntable/lensSkill.js +31 -10
- package/lib/widgets/zoomTurntable/useLensZoom.d.ts +1 -1
- package/lib/widgets/zoomTurntable/useLensZoom.js +19 -6
- package/lib/widgets/zoomTurntable/zoomTurntableEntry.d.ts +3 -2
- package/lib/widgets/zoomTurntable/zoomTurntableEntry.js +2 -1
- package/package.json +1 -1
|
@@ -14,7 +14,8 @@ export type InitPlayerWidgetsOptions = {
|
|
|
14
14
|
showRealTimeMagnification?: boolean;
|
|
15
15
|
/**
|
|
16
16
|
* 允许展示焦距转盘入口(长按图标弹出转盘)。
|
|
17
|
-
*
|
|
17
|
+
* 仅为功能开关;实际渲染还要宿主提供 ty.ipc.switchChannel、且 ipc_multi_zoom 解析出 cnt > 1,
|
|
18
|
+
* 单镜头设备与老宿主上传 true 也不出现。
|
|
18
19
|
*/
|
|
19
20
|
showZoomTurntable?: boolean;
|
|
20
21
|
directionControlProps?: Partial<React.ComponentProps<typeof FullScreen>['directionControlProps']>;
|
|
@@ -53,7 +53,8 @@ export async function initPlayerWidgets(ctx, options) {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
// 焦距转盘入口:这里只放开占位,真正是否渲染由 widget 内部按
|
|
56
|
-
// ipc_multi_zoom
|
|
56
|
+
// 「宿主有 ty.ipc.switchChannel」+「ipc_multi_zoom 解析出 cnt > 1」决定
|
|
57
|
+
// (单镜头设备、不支持切镜头的老宿主都不出现)
|
|
57
58
|
if (options.showZoomTurntable) {
|
|
58
59
|
const zoomTurntableIndex = newDefaultBottomLeftContent.findIndex(item => item.id === 'ZoomTurntableEntry');
|
|
59
60
|
if (zoomTurntableIndex !== -1) {
|
|
@@ -27,6 +27,12 @@ const VERTICAL_PEEK_WIDTH = 34;
|
|
|
27
27
|
// 平铺档内容与右下角按钮之间的最小安全间距
|
|
28
28
|
const VERTICAL_FLAT_SAFE_GAP = 8;
|
|
29
29
|
|
|
30
|
+
// 收紧态(.ipc-player-bottom-left-content-container-tight)下每项省下的宽度:
|
|
31
|
+
// 竖屏 item 左右 padding 由 12px 收到 10px,中间项左右各省 2px;
|
|
32
|
+
// 首项只省右侧 2px —— 左侧 16px 是内容起始留白,要和其余三个角落对齐,不能动。
|
|
33
|
+
const TIGHT_SAVE_FIRST = 2;
|
|
34
|
+
const TIGHT_SAVE_OTHER = 4;
|
|
35
|
+
|
|
30
36
|
// 左下角子元素统一类名(由 ui.tsx 渲染时挂上),用于 selectAll 逐项测量
|
|
31
37
|
const ITEM_CLASS = 'bottom-left-item-container';
|
|
32
38
|
const BottomLeftContent = _ref => {
|
|
@@ -113,13 +119,16 @@ const BottomLeftContent = _ref => {
|
|
|
113
119
|
// 这类变化 bottomLeftContent.length 捕获不到,由 widget 主动 emit。
|
|
114
120
|
const [resizeSignal, setResizeSignal] = useState(0);
|
|
115
121
|
|
|
122
|
+
// 是否收紧 item 间距(窄屏专用,判定见下方 useEffect)。
|
|
123
|
+
// 只升不降:收紧后各项变窄 → 重测 → 可能又判成“放得下”→ 取消收紧 → 又放不下,
|
|
124
|
+
// 那就是一个自激振荡。锁存住,只在横竖屏切换时随其余测量值一起清零。
|
|
125
|
+
const [tightened, setTightened] = useState(false);
|
|
126
|
+
|
|
116
127
|
// 滑动期间暂停整体的自动隐藏,滑动停止后再恢复(无原生 scrollend,用防抖判定结束)
|
|
117
128
|
const scrollingRef = useRef(false);
|
|
118
129
|
const scrollEndTimerRef = useRef();
|
|
119
130
|
// 测量时需要叠加当前滚动量,用 ref 避免闭包拿到过期值
|
|
120
131
|
const scrollLeftRef = useRef(0);
|
|
121
|
-
// 仅用于在可见项数变化时打一条日志,便于真机核对分档结果
|
|
122
|
-
const lastCountRef = useRef(-1);
|
|
123
132
|
useEffect(() => {
|
|
124
133
|
setScrollResetKey(k => k + 1);
|
|
125
134
|
setScrollLeft(0);
|
|
@@ -130,6 +139,7 @@ const BottomLeftContent = _ref => {
|
|
|
130
139
|
setContentWidth(0);
|
|
131
140
|
setWrapWidth(0);
|
|
132
141
|
setItemWidths([]);
|
|
142
|
+
setTightened(false);
|
|
133
143
|
}, [screenType]);
|
|
134
144
|
|
|
135
145
|
// 程序化滚动目标只在动画期间短暂受控,之后释放为非受控,避免回弹影响手动拖动
|
|
@@ -168,11 +178,13 @@ const BottomLeftContent = _ref => {
|
|
|
168
178
|
// 避免估算失准把本来放得下的元素裁掉。
|
|
169
179
|
const isFlat = isVertical && (measuredCount > 0 ? measuredCount <= VERTICAL_FLAT_MAX && contentFits : contentFits);
|
|
170
180
|
const isCompact = isVertical && !isFlat && measuredCount > 0 && measuredCount <= VERTICAL_FLAT_MAX;
|
|
181
|
+
const isScrollMode = isVertical && !isFlat && !isCompact;
|
|
182
|
+
|
|
171
183
|
// 前 4 项实际累计宽(元素按自身内容撑开,不做等分)
|
|
172
184
|
const firstFourWidth = itemWidths.slice(0, VERTICAL_FLAT_MAX).reduce((sum, w) => sum + w, 0);
|
|
173
185
|
// 滚动档可视宽 = 前 4 项宽 + 第 5 项的露出区(箭头就压在这段上)
|
|
174
186
|
const verticalScrollWidth = (() => {
|
|
175
|
-
if (!
|
|
187
|
+
if (!isScrollMode || measuredCount === 0) return 0;
|
|
176
188
|
const want = firstFourWidth + VERTICAL_PEEK_WIDTH;
|
|
177
189
|
if (!(availableWidth > 0)) return want;
|
|
178
190
|
// 前 4 项 + 露出区塞不下 —— 前 4 项里有宽 widget(焦距转盘的倍率胶囊、清晰度文案)时很常见。
|
|
@@ -180,7 +192,48 @@ const BottomLeftContent = _ref => {
|
|
|
180
192
|
if (want > availableWidth) return 0;
|
|
181
193
|
return want;
|
|
182
194
|
})();
|
|
183
|
-
|
|
195
|
+
/**
|
|
196
|
+
* 收紧后前 4 项能省下多少(估算:按每项的 padding 差值累加)。
|
|
197
|
+
* 逐项宽是实测值,收紧后的真实宽度未必精确等于这个差值,作为“够不够”的预判足够。
|
|
198
|
+
*/
|
|
199
|
+
const tightenSaving = (() => {
|
|
200
|
+
const n = Math.min(measuredCount, VERTICAL_FLAT_MAX);
|
|
201
|
+
if (n <= 0) return 0;
|
|
202
|
+
return TIGHT_SAVE_FIRST + TIGHT_SAVE_OTHER * (n - 1);
|
|
203
|
+
})();
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* 是否要把 item 间距从 12px 收到 10px。
|
|
207
|
+
*
|
|
208
|
+
* 两头都不收:
|
|
209
|
+
* - **本来就放得下**(宽屏):不动,保持 12px 的宽松间距 —— 不为个别窄屏机型
|
|
210
|
+
* 让全量机型的图标挤在一起;
|
|
211
|
+
* - **收紧了也放不下**(更窄的屏 / 3 镜头的转盘胶囊):白挤一遍换不回箭头的
|
|
212
|
+
* 专属落位区,不如保住观感,让箭头压在第 4 项右缘上(见下方 hasArrowRoom)。
|
|
213
|
+
*
|
|
214
|
+
* 只有“差一点、收紧刚好够”这一段才收 —— 这正是真机上遇到的那一档。
|
|
215
|
+
*/
|
|
216
|
+
useEffect(() => {
|
|
217
|
+
if (tightened) return;
|
|
218
|
+
if (!isScrollMode || measuredCount === 0 || !(availableWidth > 0)) return;
|
|
219
|
+
const want = firstFourWidth + VERTICAL_PEEK_WIDTH;
|
|
220
|
+
if (want <= availableWidth) return;
|
|
221
|
+
if (want - tightenSaving > availableWidth) return;
|
|
222
|
+
setTightened(true);
|
|
223
|
+
}, [tightened, isScrollMode, measuredCount, availableWidth, firstFourWidth, tightenSaving]);
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* 竖屏 scroll 档一律出箭头。
|
|
227
|
+
*
|
|
228
|
+
* 曾要求「锁宽成功(verticalScrollWidth > 0)才出箭头」,理由是没锁宽时箭头会盖到前 4 项上。
|
|
229
|
+
* 真机上这条判断反而更糟:前 4 项里有宽 widget(焦距转盘胶囊 2 镜头约 100px、
|
|
230
|
+
* 3 镜头约 138px)时,`前 4 项 + 34` 很容易超过可用宽,于是箭头连同窄渐变一起消失 ——
|
|
231
|
+
* 右边还藏着两三个 widget,却没有任何可滑提示(6 个 widget、可用宽 256、内容宽 359 的实测 case)。
|
|
232
|
+
*
|
|
233
|
+
* 宁可让箭头压在第 4 项右缘那 26px 上:那块本来就落在淡出渐变里,
|
|
234
|
+
* 观感是「内容淡出 + 箭头」,正好是「右边还有」的暗示。
|
|
235
|
+
* 想既保住前 4 项完整、又给箭头留出专属位,在窄屏上物理上做不到。
|
|
236
|
+
*/
|
|
184
237
|
|
|
185
238
|
// 滚动末端的安全距离:滑到最右时最后一个元素与右边缘留更宽松的间距(横屏更宽)。
|
|
186
239
|
// 只有真正锁宽滚动的那一档需要;平铺 / 紧凑 / 塞不下而没锁宽的情况,
|
|
@@ -230,10 +283,6 @@ const BottomLeftContent = _ref => {
|
|
|
230
283
|
}
|
|
231
284
|
if (Array.isArray(itemRects)) {
|
|
232
285
|
const widths = itemRects.map(r => (r === null || r === void 0 ? void 0 : r.width) || 0).filter(w => w > 0);
|
|
233
|
-
if (widths.length !== lastCountRef.current) {
|
|
234
|
-
lastCountRef.current = widths.length;
|
|
235
|
-
console.log('[ipc-player] 左下角可见 widget 数 =', widths.length, widths);
|
|
236
|
-
}
|
|
237
286
|
// 值没变时保持同一引用,避免轮询期间无谓重渲染
|
|
238
287
|
setItemWidths(prev => prev.length === widths.length && prev.every((w, i) => w === widths[i]) ? prev : widths);
|
|
239
288
|
}
|
|
@@ -271,7 +320,9 @@ const BottomLeftContent = _ref => {
|
|
|
271
320
|
// reservedRight 决定 ScrollView 可视宽度(右下角内容异步加载后会从 0 变为实际宽度)
|
|
272
321
|
reservedRight,
|
|
273
322
|
// playState:视频就绪后 overlay 可能重新布局,需重新测量
|
|
274
|
-
playState, bottomLeftContent === null || bottomLeftContent === void 0 ? void 0 : bottomLeftContent.length, shouldHide, showBadge, wrapCls, scrollCls, contentCls, scrollResetKey, resizeSignal
|
|
323
|
+
playState, bottomLeftContent === null || bottomLeftContent === void 0 ? void 0 : bottomLeftContent.length, shouldHide, showBadge, wrapCls, scrollCls, contentCls, scrollResetKey, resizeSignal,
|
|
324
|
+
// 收紧后各项 padding 变小,必须重测一遍逐项宽,否则分档还在用收紧前的旧值
|
|
325
|
+
tightened]);
|
|
275
326
|
|
|
276
327
|
// 子元素主动上报宽度变化 → 重跑测量,及时更新横滑箭头与右边缘淡出
|
|
277
328
|
useEffect(() => {
|
|
@@ -337,19 +388,6 @@ const BottomLeftContent = _ref => {
|
|
|
337
388
|
if (!(availableWidth > 0) || !(scrollViewWidth > 0)) return 4;
|
|
338
389
|
return Math.max(availableWidth - scrollViewWidth + 4, 4);
|
|
339
390
|
})();
|
|
340
|
-
|
|
341
|
-
// 真机核对分档结果用,确认无误后可摘掉
|
|
342
|
-
useEffect(() => {
|
|
343
|
-
if (!isVertical) return;
|
|
344
|
-
console.log('[ipc-player] 左下角分档', {
|
|
345
|
-
个数: measuredCount,
|
|
346
|
-
档位: isFlat ? 'flat 平铺' : isCompact ? 'compact 紧凑' : 'scroll 滚动',
|
|
347
|
-
可用宽: availableWidth,
|
|
348
|
-
内容宽: naturalWidth,
|
|
349
|
-
可视宽: verticalScrollWidth || availableWidth,
|
|
350
|
-
逐项宽: itemWidths
|
|
351
|
-
});
|
|
352
|
-
}, [isVertical, measuredCount, isFlat, isCompact, availableWidth, naturalWidth, verticalScrollWidth, itemWidths]);
|
|
353
391
|
return /*#__PURE__*/React.createElement(CoverView, {
|
|
354
392
|
className: clsx('ipc-player-bottom-left-content-wrap', wrapCls),
|
|
355
393
|
style: {
|
|
@@ -408,8 +446,11 @@ const BottomLeftContent = _ref => {
|
|
|
408
446
|
},
|
|
409
447
|
className: clsx(scrollCls, {
|
|
410
448
|
'ipc-player-bottom-left-scroll-fademask': fadeRightEdge,
|
|
411
|
-
//
|
|
412
|
-
|
|
449
|
+
// 竖屏 scroll 档一律用窄渐变(34px,只覆盖箭头那块)。
|
|
450
|
+
// 原条件是「锁宽成功」,可没锁宽时可视区右缘同样压着第 4 项,
|
|
451
|
+
// 72px 的宽渐变会把宽 widget(转盘胶囊约 100px)淡掉一大半。
|
|
452
|
+
// 横屏 isScrollMode 恒为 false,仍走 72px 宽渐变,行为不变。
|
|
453
|
+
'ipc-player-bottom-left-scroll-fademask--narrow': fadeRightEdge && isScrollMode
|
|
413
454
|
}),
|
|
414
455
|
style: {
|
|
415
456
|
paddingBottom: `${paddingBottomPx}px`,
|
|
@@ -421,7 +462,9 @@ const BottomLeftContent = _ref => {
|
|
|
421
462
|
whiteSpace: 'nowrap'
|
|
422
463
|
}
|
|
423
464
|
}, /*#__PURE__*/React.createElement(View, {
|
|
424
|
-
className: clsx('ipc-player-bottom-left-content-container', contentCls
|
|
465
|
+
className: clsx('ipc-player-bottom-left-content-container', contentCls, {
|
|
466
|
+
'ipc-player-bottom-left-content-container-tight': tightened
|
|
467
|
+
}),
|
|
425
468
|
style: {
|
|
426
469
|
display: 'inline-flex',
|
|
427
470
|
flexDirection: 'row',
|
|
@@ -436,7 +479,7 @@ const BottomLeftContent = _ref => {
|
|
|
436
479
|
height: '1px',
|
|
437
480
|
flexShrink: 0
|
|
438
481
|
}
|
|
439
|
-
}))), isOverflow && !atEnd && !isCompact && (!isVertical ||
|
|
482
|
+
}))), isOverflow && !atEnd && !isCompact && (!isVertical || isScrollMode) && (isButtonHint || !isDismissed) && /*#__PURE__*/React.createElement(CoverView, {
|
|
440
483
|
className: clsx('ipc-player-bottom-left-scroll-hint', {
|
|
441
484
|
'ipc-player-bottom-left-scroll-hint--button': isButtonHint
|
|
442
485
|
}),
|
|
@@ -69,7 +69,9 @@ const BottomRightContent = _ref => {
|
|
|
69
69
|
return /*#__PURE__*/React.createElement(CoverView, {
|
|
70
70
|
className: clsx('ipc-player-bottom-right-content-wrap', wrapCls),
|
|
71
71
|
style: _objectSpread({
|
|
72
|
-
|
|
72
|
+
// 竖屏归 0:这 10px 是纯空白,且计入实测宽后又被 reservedRight 带给左下角,
|
|
73
|
+
// 等于左下角白让 10px。与右下角按钮的间距统一由 BOTTOM_RIGHT_SAFE_GAP 负责。
|
|
74
|
+
paddingLeft: '0',
|
|
73
75
|
height: screenType === 'vertical' ? shouldHide ? '49px' : '48px' : shouldHide ? '59px' : '58px'
|
|
74
76
|
}, widthStyle)
|
|
75
77
|
}, /*#__PURE__*/React.createElement(View, {
|
package/lib/ui/ui.js
CHANGED
|
@@ -59,7 +59,10 @@ const SET_SCALE_ALIGN_TIMEOUT = 1500;
|
|
|
59
59
|
const SCALE_MATCH_EPS = 0.01;
|
|
60
60
|
|
|
61
61
|
/** 竖屏左下角内容与右下角按钮之间的安全间距(px) */
|
|
62
|
-
|
|
62
|
+
// 左下角内容右缘与右下角按钮之间的安全间距。
|
|
63
|
+
// 原为 12px:右下角 wrap 当时还带 10px paddingLeft,两段叠起来是 22px 空白,窄屏上太奢侈。
|
|
64
|
+
// paddingLeft 已归 0,这里收到 4px —— 右下角按钮自身还有 16px 的右留白撑着,不会显挤。
|
|
65
|
+
const BOTTOM_RIGHT_SAFE_GAP = 4;
|
|
63
66
|
export const IPCPlayerIntegration = /*#__PURE__*/React.memo(props => {
|
|
64
67
|
const {
|
|
65
68
|
className,
|
|
@@ -803,13 +806,17 @@ export const IPCPlayerIntegration = /*#__PURE__*/React.memo(props => {
|
|
|
803
806
|
if (screenType === 'full') {
|
|
804
807
|
return bottomRightWidth;
|
|
805
808
|
}
|
|
809
|
+
// 安卓上 CoverView 的 boundingClientRect 可能一直测不出宽度,轮询跑完仍是 0
|
|
810
|
+
if (!(bottomRightMeasuredWidth > 0)) {
|
|
811
|
+
return bottomRightWidth;
|
|
812
|
+
}
|
|
806
813
|
// 封顶保护:个别宿主里不设宽度的 CoverView 可能撑满整行,实测值会异常放大,
|
|
807
814
|
// 那样左下角会被挤没。超过屏宽一半就当测量不可信,退回常量估算。
|
|
808
815
|
const maxReserved = windowWidthRef.current ? windowWidthRef.current / 2 : 0;
|
|
809
|
-
if (
|
|
810
|
-
return
|
|
816
|
+
if (maxReserved && bottomRightMeasuredWidth > maxReserved) {
|
|
817
|
+
return bottomRightWidth;
|
|
811
818
|
}
|
|
812
|
-
return
|
|
819
|
+
return Math.ceil(bottomRightMeasuredWidth) + BOTTOM_RIGHT_SAFE_GAP;
|
|
813
820
|
}, [screenType, bottomRightWidth, bottomRightMeasuredWidth]);
|
|
814
821
|
|
|
815
822
|
/**
|
package/lib/ui/ui.less
CHANGED
|
@@ -311,7 +311,7 @@
|
|
|
311
311
|
padding: 0 16px 0 0;
|
|
312
312
|
}
|
|
313
313
|
|
|
314
|
-
//
|
|
314
|
+
// 左下角子元素容器(竖屏;横屏另叠 .bottom-left-item-full-container 且带 !important)
|
|
315
315
|
.bottom-left-item-container {
|
|
316
316
|
padding: 0 12px;
|
|
317
317
|
display: flex;
|
|
@@ -334,6 +334,30 @@
|
|
|
334
334
|
padding: 0 0 0 16px;
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
+
// 收紧态:左右各 10px(默认 12px)。
|
|
338
|
+
//
|
|
339
|
+
// 只有窄屏上「前 4 项 + 34 露出区」放不下、且收紧后刚好放得下时才挂这个类
|
|
340
|
+
// (判定见 bottomLeftContent.tsx 的 tightened)。宽屏本来就放得下,
|
|
341
|
+
// 保持 12px 的宽松间距,不为个别机型让全量机型变紧。
|
|
342
|
+
//
|
|
343
|
+
// 中间项左右各省 2px = 4px;首项只省右侧 2px —— 左侧 16px 是内容起始留白,
|
|
344
|
+
// 要和其余三个角落对齐,不能动。10px 是不显挤的下限。
|
|
345
|
+
//
|
|
346
|
+
// 横屏叠的 .bottom-left-item-full-container 带 !important,优先级更高,不受影响。
|
|
347
|
+
.ipc-player-bottom-left-content-container-tight {
|
|
348
|
+
.bottom-left-item-container {
|
|
349
|
+
padding: 0 10px;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.bottom-left-item-container:first-of-type {
|
|
353
|
+
padding: 0 10px 0 16px;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.bottom-left-item-container:last-of-type {
|
|
357
|
+
padding: 0 0 0 10px;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
337
361
|
// 左下角全屏子元素容器
|
|
338
362
|
.bottom-left-item-full-container {
|
|
339
363
|
padding: 0 16px !important;
|
|
@@ -56,6 +56,14 @@ export declare const isZoomSkillValid: (skill?: ZoomSkill | null) => boolean;
|
|
|
56
56
|
* @param value DP 值;内部 mock 常量是对象、DP 上报是 string,两条来源共用同一校验链路
|
|
57
57
|
*/
|
|
58
58
|
export declare const parseZoomSkill: (value?: unknown) => ZoomSkill | null;
|
|
59
|
+
/**
|
|
60
|
+
* 宿主是否支持切镜头下发。
|
|
61
|
+
*
|
|
62
|
+
* 转盘的每一种交互(点 tab 切镜头、拖转盘跨界、手势触底下切)最终都落到 switchChannel 上,
|
|
63
|
+
* 方法不存在时转盘画得出来却切不动,只会让用户以为坏了 —— 故整个入口不渲染
|
|
64
|
+
* (门控在 useLensZoom 的 multiLensReady 里,与 cnt > 1 同级)。
|
|
65
|
+
*/
|
|
66
|
+
export declare const isSwitchChannelSupported: () => boolean;
|
|
59
67
|
/**
|
|
60
68
|
* 切换物理镜头。channel 取上报的子镜头 id 而非数组下标 —— 固件可上报非连续 id。
|
|
61
69
|
* 返回 false(不支持 / 失败)时由 useLensZoom 回滚读数,无回调另有 SWITCH_LENS_TIMEOUT 兜底。
|
|
@@ -111,13 +111,34 @@ export const parseZoomSkill = value => {
|
|
|
111
111
|
|
|
112
112
|
/** ty.ipc.switchChannel 的最小签名:ty 类型包尚未收录,本地声明避免调用点退化成 any */
|
|
113
113
|
|
|
114
|
+
/** 「宿主不支持 switchChannel」只提示一次:判定会被 render 反复调用,不去重会刷屏 */
|
|
115
|
+
let switchChannelMissingLogged = false;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 宿主是否支持切镜头下发。
|
|
119
|
+
*
|
|
120
|
+
* 转盘的每一种交互(点 tab 切镜头、拖转盘跨界、手势触底下切)最终都落到 switchChannel 上,
|
|
121
|
+
* 方法不存在时转盘画得出来却切不动,只会让用户以为坏了 —— 故整个入口不渲染
|
|
122
|
+
* (门控在 useLensZoom 的 multiLensReady 里,与 cnt > 1 同级)。
|
|
123
|
+
*/
|
|
124
|
+
export const isSwitchChannelSupported = () => {
|
|
125
|
+
var _ty;
|
|
126
|
+
const supported = typeof ((_ty = ty) === null || _ty === void 0 || (_ty = _ty.ipc) === null || _ty === void 0 ? void 0 : _ty.switchChannel) === 'function';
|
|
127
|
+
if (!supported && !switchChannelMissingLogged) {
|
|
128
|
+
switchChannelMissingLogged = true;
|
|
129
|
+
console.warn('[zoomTurntable] 宿主不支持 ty.ipc.switchChannel,焦距转盘入口不展示');
|
|
130
|
+
dumpChannelApis();
|
|
131
|
+
}
|
|
132
|
+
return supported;
|
|
133
|
+
};
|
|
134
|
+
|
|
114
135
|
/**
|
|
115
136
|
* 切换物理镜头。channel 取上报的子镜头 id 而非数组下标 —— 固件可上报非连续 id。
|
|
116
137
|
* 返回 false(不支持 / 失败)时由 useLensZoom 回滚读数,无回调另有 SWITCH_LENS_TIMEOUT 兜底。
|
|
117
138
|
*/
|
|
118
139
|
export const switchLens = (devId, lens) => {
|
|
119
|
-
var
|
|
120
|
-
const switchChannel = (
|
|
140
|
+
var _ty2;
|
|
141
|
+
const switchChannel = (_ty2 = ty) === null || _ty2 === void 0 || (_ty2 = _ty2.ipc) === null || _ty2 === void 0 ? void 0 : _ty2.switchChannel;
|
|
121
142
|
// 入参与回调结果都打出来:真机上「切没切成」只能靠这条链路判断
|
|
122
143
|
const params = {
|
|
123
144
|
deviceId: devId,
|
|
@@ -223,8 +244,8 @@ const pickChannel = payload => {
|
|
|
223
244
|
* 返回 null(不支持 / 失败 / 超时)时调用方保持现状,不重置镜头。
|
|
224
245
|
*/
|
|
225
246
|
export const queryCurrentLens = devId => {
|
|
226
|
-
var
|
|
227
|
-
const queryCurrentChannel = (
|
|
247
|
+
var _ty3;
|
|
248
|
+
const queryCurrentChannel = (_ty3 = ty) === null || _ty3 === void 0 || (_ty3 = _ty3.ipc) === null || _ty3 === void 0 ? void 0 : _ty3.queryCurrentChannel;
|
|
228
249
|
if (typeof queryCurrentChannel !== 'function') {
|
|
229
250
|
console.warn('[zoomTurntable] 宿主不支持 ty.ipc.queryCurrentChannel');
|
|
230
251
|
return Promise.resolve(null);
|
|
@@ -273,8 +294,8 @@ export const queryCurrentLens = devId => {
|
|
|
273
294
|
* 切镜头的收尾(画面归 1 倍 + 刷读数)以该事件为准,不支持时只能退回「switchChannel 成功即收尾」。
|
|
274
295
|
*/
|
|
275
296
|
export const isChannelChangeSupported = () => {
|
|
276
|
-
var
|
|
277
|
-
return typeof ((
|
|
297
|
+
var _ty4;
|
|
298
|
+
return typeof ((_ty4 = ty) === null || _ty4 === void 0 || (_ty4 = _ty4.ipc) === null || _ty4 === void 0 ? void 0 : _ty4.onChannelChange) === 'function';
|
|
278
299
|
};
|
|
279
300
|
|
|
280
301
|
/**
|
|
@@ -282,8 +303,8 @@ export const isChannelChangeSupported = () => {
|
|
|
282
303
|
* 真机上「事件为什么不来」第一步就是确认宿主到底提供了哪个名字。
|
|
283
304
|
*/
|
|
284
305
|
export const dumpChannelApis = () => {
|
|
285
|
-
var
|
|
286
|
-
const ipc = (
|
|
306
|
+
var _ty5;
|
|
307
|
+
const ipc = (_ty5 = ty) === null || _ty5 === void 0 ? void 0 : _ty5.ipc;
|
|
287
308
|
if (!ipc) {
|
|
288
309
|
console.warn('[zoomTurntable] ty.ipc 不存在', `typeof ty=${typeof ty}`);
|
|
289
310
|
return;
|
|
@@ -321,9 +342,9 @@ export const subscribeChannelChange = (devId, handler) => {
|
|
|
321
342
|
console.log('[zoomTurntable] 已订阅 ty.ipc.onChannelChange', devId);
|
|
322
343
|
ty.ipc.onChannelChange(listener);
|
|
323
344
|
return () => {
|
|
324
|
-
var
|
|
345
|
+
var _ty6;
|
|
325
346
|
console.log('[zoomTurntable] 解绑 channelChange 监听', devId);
|
|
326
|
-
if (typeof ((
|
|
347
|
+
if (typeof ((_ty6 = ty) === null || _ty6 === void 0 || (_ty6 = _ty6.ipc) === null || _ty6 === void 0 ? void 0 : _ty6.offChannelChange) === 'function') {
|
|
327
348
|
ty.ipc.offChannelChange(listener);
|
|
328
349
|
} else {
|
|
329
350
|
console.warn('[zoomTurntable] 宿主无 offChannelChange,监听无法解绑');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ComponentConfigProps } from '../../interface';
|
|
2
2
|
type Options = ComponentConfigProps & {
|
|
3
|
-
/**
|
|
3
|
+
/** 业务侧的功能开关;实际是否渲染仍由宿主 switchChannel 能力 + ipc_multi_zoom 的 cnt 决定 */
|
|
4
4
|
showZoomTurntable?: boolean;
|
|
5
5
|
/** 跳过能力加载:Entry 常驻负责加载,Dock 作为消费方传 true 避免重复解析 */
|
|
6
6
|
skipLoad?: boolean;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from 'react';
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { useMemoizedFn } from 'ahooks';
|
|
3
3
|
import { useStore } from '../../ctx/store';
|
|
4
4
|
import { PlayState } from '../../interface';
|
|
5
5
|
import { useDpState } from '../../hooks/useDpState';
|
|
6
6
|
import { setScaleMultipleEvent, zoomLevelChange } from '../../ui/constant';
|
|
7
|
-
import { MOCK_ZOOM_SKILL, MULTI_ZOOM_DP_CODE, dumpChannelApis, isChannelChangeSupported, isZoomSkillValid, lensCount, parseZoomSkill, queryCurrentLens, showSmallMonitor, subscribeChannelChange, switchLens } from './lensSkill';
|
|
7
|
+
import { MOCK_ZOOM_SKILL, MULTI_ZOOM_DP_CODE, dumpChannelApis, isChannelChangeSupported, isSwitchChannelSupported, 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
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
|
/**
|
|
@@ -210,9 +210,18 @@ export const useLensZoom = options => {
|
|
|
210
210
|
};
|
|
211
211
|
}, [event]);
|
|
212
212
|
|
|
213
|
+
// ---------------- 宿主能力 ----------------
|
|
214
|
+
/**
|
|
215
|
+
* 宿主是否支持 ty.ipc.switchChannel。宿主能力在运行期不会变,求一次即可 ——
|
|
216
|
+
* 放 useMemo 而不是 render 期直接调,免得 typeof 判定与首次告警日志跟着每次渲染跑。
|
|
217
|
+
*
|
|
218
|
+
* 转盘的每一种交互最终都落到这个方法上,它不存在时转盘切不动,故整个入口不展示。
|
|
219
|
+
*/
|
|
220
|
+
const switchChannelSupported = useMemo(() => isSwitchChannelSupported(), []);
|
|
221
|
+
|
|
213
222
|
// ---------------- 能力加载 ----------------
|
|
214
223
|
useEffect(() => {
|
|
215
|
-
if (skipLoad || !devId || !showZoomTurntable) return undefined;
|
|
224
|
+
if (skipLoad || !devId || !showZoomTurntable || !switchChannelSupported) return undefined;
|
|
216
225
|
const raw = multiZoomDpValue;
|
|
217
226
|
console.log('[zoomTurntable] ipc_multi_zoom 上报值', JSON.stringify(raw !== null && raw !== void 0 ? raw : null));
|
|
218
227
|
const skill = parseZoomSkill(raw);
|
|
@@ -223,10 +232,14 @@ export const useLensZoom = options => {
|
|
|
223
232
|
}
|
|
224
233
|
setZoomSkill(skill);
|
|
225
234
|
return undefined;
|
|
226
|
-
}, [devId, showZoomTurntable, multiZoomDpValue, skipLoad]);
|
|
235
|
+
}, [devId, showZoomTurntable, switchChannelSupported, multiZoomDpValue, skipLoad]);
|
|
227
236
|
|
|
228
|
-
/**
|
|
229
|
-
|
|
237
|
+
/**
|
|
238
|
+
* 多镜头能力就绪:宿主提供 ty.ipc.switchChannel,且 ipc_multi_zoom 解析出的 cnt > 1。
|
|
239
|
+
* 两条缺一个入口都不出现 —— 单镜头设备没有可切的镜头,
|
|
240
|
+
* 老宿主则是切不动(见 isSwitchChannelSupported)。
|
|
241
|
+
*/
|
|
242
|
+
const multiLensReady = showZoomTurntable && switchChannelSupported && lensCount(zoomSkill) > 1;
|
|
230
243
|
|
|
231
244
|
// 手势缩放上限按当前镜头换算后下发给播放器:maxScaleMultiple 是 d 口径,
|
|
232
245
|
// 直接用画面绝对上限(如 6.0)会让画面放到该镜头区间之外。切镜头后跟着更新。
|
|
@@ -3,7 +3,7 @@ import { ComponentConfigProps } from '../../interface';
|
|
|
3
3
|
import './zoomTurntableEntry.less';
|
|
4
4
|
type Props = ComponentConfigProps & {
|
|
5
5
|
className?: string;
|
|
6
|
-
/**
|
|
6
|
+
/** 业务侧的功能开关;实际是否渲染仍由宿主 switchChannel 能力 + ipc_multi_zoom 的 cnt 决定 */
|
|
7
7
|
showZoomTurntable?: boolean;
|
|
8
8
|
/** 长按判定时长,默认 500ms */
|
|
9
9
|
longPressDuration?: number;
|
|
@@ -12,7 +12,8 @@ type Props = ComponentConfigProps & {
|
|
|
12
12
|
};
|
|
13
13
|
/**
|
|
14
14
|
* 焦距转盘入口:左下角工具条中的图标,长按弹出转盘。
|
|
15
|
-
*
|
|
15
|
+
* 仅「宿主支持 ty.ipc.switchChannel」且 cnt > 1 的多镜头设备渲染;
|
|
16
|
+
* 单镜头设备倍率交互仍由 RealTimeMagnification 承担。
|
|
16
17
|
*/
|
|
17
18
|
export declare const ZoomTurntableEntry: (props: Props) => React.JSX.Element | null;
|
|
18
19
|
export {};
|
|
@@ -19,7 +19,8 @@ import { FORCE_SHOW_ZOOM_TIP, LONG_PRESS_MS, MOVE_TOLERANCE, TIP_RESHOW_DELAY, g
|
|
|
19
19
|
import './zoomTurntableEntry.less';
|
|
20
20
|
/**
|
|
21
21
|
* 焦距转盘入口:左下角工具条中的图标,长按弹出转盘。
|
|
22
|
-
*
|
|
22
|
+
* 仅「宿主支持 ty.ipc.switchChannel」且 cnt > 1 的多镜头设备渲染;
|
|
23
|
+
* 单镜头设备倍率交互仍由 RealTimeMagnification 承担。
|
|
23
24
|
*/
|
|
24
25
|
export const ZoomTurntableEntry = props => {
|
|
25
26
|
var _props$event;
|