@ray-js/ipc-player-integration 0.0.49 → 0.0.50-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/lib/ctx/ctx.composition.js +6 -1
  2. package/lib/ctx/ctx.js +11 -1
  3. package/lib/features/initPlayerWidgets/index.d.ts +8 -0
  4. package/lib/features/initPlayerWidgets/index.js +13 -0
  5. package/lib/i18n/index.d.ts +8 -0
  6. package/lib/i18n/strings.d.ts +4 -0
  7. package/lib/i18n/strings.js +4 -0
  8. package/lib/interface.d.ts +9 -1
  9. package/lib/ui/bottomLeftContent.js +23 -8
  10. package/lib/ui/constant.d.ts +10 -0
  11. package/lib/ui/constant.js +12 -1
  12. package/lib/ui/ui.js +78 -4
  13. package/lib/ui/ui.less +15 -0
  14. package/lib/utils/content/dpCode.d.ts +1 -0
  15. package/lib/utils/content/dpCode.js +5 -1
  16. package/lib/utils/storage/index.d.ts +2 -2
  17. package/lib/widgets/index.d.ts +1 -0
  18. package/lib/widgets/index.js +2 -1
  19. package/lib/widgets/realTimeMagnification/realTimeMagnification.js +18 -1
  20. package/lib/widgets/toggleVerticalFull/toggleVerticalFull.less +1 -1
  21. package/lib/widgets/zoomTurntable/constants.d.ts +92 -0
  22. package/lib/widgets/zoomTurntable/constants.js +148 -0
  23. package/lib/widgets/zoomTurntable/entryAnchor.d.ts +7 -0
  24. package/lib/widgets/zoomTurntable/entryAnchor.js +7 -0
  25. package/lib/widgets/zoomTurntable/index.d.ts +8 -0
  26. package/lib/widgets/zoomTurntable/index.js +8 -0
  27. package/lib/widgets/zoomTurntable/lensModel.d.ts +53 -0
  28. package/lib/widgets/zoomTurntable/lensModel.js +137 -0
  29. package/lib/widgets/zoomTurntable/lensModel.test.d.ts +1 -0
  30. package/lib/widgets/zoomTurntable/lensModel.test.js +336 -0
  31. package/lib/widgets/zoomTurntable/lensSkill.d.ts +89 -0
  32. package/lib/widgets/zoomTurntable/lensSkill.js +332 -0
  33. package/lib/widgets/zoomTurntable/measureVisible.d.ts +22 -0
  34. package/lib/widgets/zoomTurntable/measureVisible.js +76 -0
  35. package/lib/widgets/zoomTurntable/turntable/index.d.ts +10 -0
  36. package/lib/widgets/zoomTurntable/turntable/index.js +76 -0
  37. package/lib/widgets/zoomTurntable/turntable/index.less +11 -0
  38. package/lib/widgets/zoomTurntable/turntable/rjs/index.js +242 -0
  39. package/lib/widgets/zoomTurntable/turntable/rjs/index.json +3 -0
  40. package/lib/widgets/zoomTurntable/turntable/rjs/index.rjs +691 -0
  41. package/lib/widgets/zoomTurntable/turntable/rjs/index.tyml +7 -0
  42. package/lib/widgets/zoomTurntable/turntable/type.d.ts +95 -0
  43. package/lib/widgets/zoomTurntable/turntable/type.js +1 -0
  44. package/lib/widgets/zoomTurntable/useLensZoom.d.ts +37 -0
  45. package/lib/widgets/zoomTurntable/useLensZoom.js +418 -0
  46. package/lib/widgets/zoomTurntable/zoomTurntableDock.d.ts +17 -0
  47. package/lib/widgets/zoomTurntable/zoomTurntableDock.js +228 -0
  48. package/lib/widgets/zoomTurntable/zoomTurntableDock.less +11 -0
  49. package/lib/widgets/zoomTurntable/zoomTurntableEntry.d.ts +21 -0
  50. package/lib/widgets/zoomTurntable/zoomTurntableEntry.js +253 -0
  51. package/lib/widgets/zoomTurntable/zoomTurntableEntry.less +51 -0
  52. package/lib/widgets/zoomTurntable/zoomTurntableTip.d.ts +16 -0
  53. package/lib/widgets/zoomTurntable/zoomTurntableTip.js +76 -0
  54. package/lib/widgets/zoomTurntable/zoomTurntableTip.less +56 -0
  55. package/package.json +3 -3
@@ -0,0 +1,332 @@
1
+ import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
2
+ import "core-js/modules/esnext.iterator.constructor.js";
3
+ import "core-js/modules/esnext.iterator.for-each.js";
4
+ import "core-js/modules/esnext.iterator.map.js";
5
+ /* eslint-disable camelcase -- P2P 回包字段名由设备端定义,不做转换 */
6
+ /**
7
+ * 镜头能力解析与镜头切换下发。协议相关的不确定性都收敛在本文件。
8
+ *
9
+ * 协议来源:《面板小程序 V1.6.0_零散优化+多目变焦》§4
10
+ *
11
+ * 能力取自 ipc_multi_zoom DP 的 string 值,cnt <= 1 不出现转盘入口:
12
+ * {"cnt":2,"thresholds":[1,3,6],"lenses":[{"id":0,"type":0,"step":0.1,"basicRatio":1},…]}
13
+ * 切镜头走 ty.ipc.switchChannel({ deviceId, channel }),channel 即上报的子镜头 id。
14
+ * 出流后用 ty.ipc.queryCurrentChannel 回读当前镜头;
15
+ * 设备侧 / 其他端切镜头由 ty.ipc.onChannelChange 事件通知。
16
+ */
17
+
18
+ import { QUERY_LENS_TIMEOUT } from './constants';
19
+
20
+ /** channelChange 事件载荷 */
21
+
22
+ /** 能力 DP:其 string 值即镜头能力描述,解析出的 cnt > 1 才显示入口 */
23
+ export { MULTI_ZOOM as MULTI_ZOOM_DP_CODE } from '../../utils/content/dpCode';
24
+
25
+ /** MOCK 数据:与协议参考上报值一致的双目定焦(短焦 1.0x / 临界 3.0x / 上限 6.0x) */
26
+ export const MOCK_ZOOM_SKILL = {
27
+ cnt: 2,
28
+ thresholds: [1.0, 3.0, 6.0],
29
+ lenses: [{
30
+ id: 0,
31
+ type: 0,
32
+ step: 0.1,
33
+ basicRatio: 1.0
34
+ }, {
35
+ id: 1,
36
+ type: 1,
37
+ step: 0.1,
38
+ basicRatio: 3.0
39
+ }]
40
+ };
41
+
42
+ /**
43
+ * 镜头数量。以 cnt 为判定字段;
44
+ * 固件漏填时回落到 lenses.length,两者不一致时告警(协议保证一致)。
45
+ */
46
+ export const lensCount = skill => {
47
+ var _skill$lenses, _skill$cnt;
48
+ if (!(skill !== null && skill !== void 0 && (_skill$lenses = skill.lenses) !== null && _skill$lenses !== void 0 && _skill$lenses.length)) return 0;
49
+ if (skill.cnt != null && skill.cnt !== skill.lenses.length) {
50
+ console.warn('[zoomTurntable] cnt 与 lenses.length 不一致', JSON.stringify(skill));
51
+ }
52
+ return (_skill$cnt = skill.cnt) !== null && _skill$cnt !== void 0 ? _skill$cnt : skill.lenses.length;
53
+ };
54
+
55
+ /** 校验数据自洽性。半套数据画出的刻度是错的,比不显示更糟,故不做部分降级 */
56
+ export const isZoomSkillValid = skill => {
57
+ const count = lensCount(skill);
58
+ if (!skill || count <= 0) return false;
59
+ const {
60
+ thresholds,
61
+ lenses
62
+ } = skill;
63
+ if (!Array.isArray(thresholds) || thresholds.length !== count + 1) {
64
+ console.error('[zoomTurntable] thresholds 长度应为 cnt + 1', JSON.stringify(skill));
65
+ return false;
66
+ }
67
+ for (let i = 1; i < thresholds.length; i++) {
68
+ if (!(thresholds[i] > thresholds[i - 1])) {
69
+ console.error('[zoomTurntable] thresholds 必须严格升序', JSON.stringify(thresholds));
70
+ return false;
71
+ }
72
+ }
73
+ if (lenses.length < count) {
74
+ console.error('[zoomTurntable] lenses 长度小于 cnt', JSON.stringify(skill));
75
+ return false;
76
+ }
77
+ for (let i = 0; i < count; i++) {
78
+ const lens = lenses[i];
79
+ if (!lens || typeof lens.basicRatio !== 'number' || lens.id == null) {
80
+ console.error('[zoomTurntable] lens 字段缺失', JSON.stringify(lens));
81
+ return false;
82
+ }
83
+ if (i > 0 && !(lens.basicRatio >= lenses[i - 1].basicRatio)) {
84
+ console.error('[zoomTurntable] lenses 必须按 basicRatio 升序', JSON.stringify(lenses));
85
+ return false;
86
+ }
87
+ }
88
+ return true;
89
+ };
90
+
91
+ /**
92
+ * 解析 ipc_multi_zoom DP 上报的 string 值为镜头能力。
93
+ * @param value DP 值;调试注入的对象(mockZoomSkill)直接透传,两条来源共用同一校验链路
94
+ */
95
+ export const parseZoomSkill = value => {
96
+ if (value == null || value === '') return null;
97
+ if (typeof value === 'object') return value;
98
+ if (typeof value !== 'string') {
99
+ console.warn('[zoomTurntable] ipc_multi_zoom 非 string 值', value);
100
+ return null;
101
+ }
102
+ try {
103
+ const parsed = JSON.parse(value);
104
+ if (!parsed || typeof parsed !== 'object') return null;
105
+ return parsed;
106
+ } catch (err) {
107
+ console.error('[zoomTurntable] ipc_multi_zoom 解析失败', value, err);
108
+ return null;
109
+ }
110
+ };
111
+
112
+ /** ty.ipc.switchChannel 的最小签名:ty 类型包尚未收录,本地声明避免调用点退化成 any */
113
+
114
+ /**
115
+ * 切换物理镜头。channel 取上报的子镜头 id 而非数组下标 —— 固件可上报非连续 id。
116
+ * 返回 false(不支持 / 失败)时由 useLensZoom 回滚读数,无回调另有 SWITCH_LENS_TIMEOUT 兜底。
117
+ */
118
+ export const switchLens = (devId, lens) => {
119
+ var _ty;
120
+ const switchChannel = (_ty = ty) === null || _ty === void 0 || (_ty = _ty.ipc) === null || _ty === void 0 ? void 0 : _ty.switchChannel;
121
+ // 入参与回调结果都打出来:真机上「切没切成」只能靠这条链路判断
122
+ const params = {
123
+ deviceId: devId,
124
+ channel: lens.id
125
+ };
126
+ console.log('[zoomTurntable] switchChannel 调用', JSON.stringify(params), `lens={id:${lens.id},type:${lens.type},basicRatio:${lens.basicRatio}}`);
127
+ if (typeof switchChannel !== 'function') {
128
+ console.warn('[zoomTurntable] 宿主不支持 ty.ipc.switchChannel,无法切换镜头');
129
+ return Promise.resolve(false);
130
+ }
131
+ const startAt = Date.now();
132
+ return new Promise(resolve => {
133
+ try {
134
+ switchChannel(_objectSpread(_objectSpread({}, params), {}, {
135
+ success: res => {
136
+ console.log('[zoomTurntable] switchChannel success', `channel=${lens.id}`, `${Date.now() - startAt}ms`, JSON.stringify(res !== null && res !== void 0 ? res : null));
137
+ resolve(true);
138
+ },
139
+ fail: err => {
140
+ console.warn('[zoomTurntable] switchChannel fail', `channel=${lens.id}`, `${Date.now() - startAt}ms`, JSON.stringify(err !== null && err !== void 0 ? err : null));
141
+ resolve(false);
142
+ }
143
+ }));
144
+ } catch (err) {
145
+ console.error('[zoomTurntable] switchChannel 调用异常', JSON.stringify(params), err);
146
+ resolve(false);
147
+ }
148
+ });
149
+ };
150
+
151
+ /**
152
+ * 顺原型链收集对象上的属性名并标出类型;方法常挂在原型上,只看自有属性会漏。
153
+ * @param keyword 只保留名字含该关键词的(小写匹配),空串则全列
154
+ */
155
+ const describeApis = function (obj) {
156
+ let keyword = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
157
+ const keys = new Set();
158
+ for (let cur = obj; cur && cur !== Object.prototype; cur = Object.getPrototypeOf(cur)) {
159
+ Object.getOwnPropertyNames(cur).forEach(k => keys.add(k));
160
+ }
161
+ return [...keys].filter(k => !keyword || k.toLowerCase().includes(keyword.toLowerCase())).sort().map(k => {
162
+ try {
163
+ return `${k}:${typeof obj[k]}`;
164
+ } catch (err) {
165
+ return `${k}:<throw:${err === null || err === void 0 ? void 0 : err.message}>`;
166
+ }
167
+ });
168
+ };
169
+
170
+ /** 打印播放器实例上的方法名;方法名对不上时用来确认实例究竟提供了什么 */
171
+ export const dumpPlayerApis = function (IPCPlayerInstance) {
172
+ let keyword = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
173
+ if (!IPCPlayerInstance) {
174
+ console.warn('[zoomTurntable] 播放器实例不存在');
175
+ return;
176
+ }
177
+ const matched = describeApis(IPCPlayerInstance, keyword);
178
+ console.log(`[zoomTurntable] 播放器实例方法${keyword ? `(含 ${keyword})` : ''}(${matched.length})`, matched.join(', '));
179
+ };
180
+
181
+ /**
182
+ * IPCPlayerInstance.showSmallMonitor 的最小签名(与 startRecord 同级的播放器方法)。
183
+ * IpcContext 是 Record<string, any>,本地声明一份免得调用点退化成 any。
184
+ */
185
+
186
+ /**
187
+ * 开关全景小窗(小窗画面为当前镜头的全景)。
188
+ *
189
+ * 画面停在镜头光学正倍数上时不需要小窗,在镜头区间内数字放大后才需要。
190
+ * 调用方(useLensZoom)已按 devId 做过去重,这里只管下发,不判重。
191
+ */
192
+ export const showSmallMonitor = (IPCPlayerInstance, show) => {
193
+ console.log(IPCPlayerInstance, 'IPCPlayerInstance=====');
194
+ const api = IPCPlayerInstance === null || IPCPlayerInstance === void 0 ? void 0 : IPCPlayerInstance.showSmallMonitor;
195
+ if (typeof api !== 'function') {
196
+ // 方法名没对上时,把实例上现有的方法名打出来,省得来回猜
197
+ console.warn('[zoomTurntable] 播放器实例上没有 showSmallMonitor,小窗未切换', show);
198
+ dumpPlayerApis(IPCPlayerInstance, 'monitor');
199
+ return;
200
+ }
201
+ console.log('[zoomTurntable] showSmallMonitor 调用', show);
202
+ try {
203
+ api({
204
+ show,
205
+ success: res => console.log('[zoomTurntable] showSmallMonitor success', show, JSON.stringify(res !== null && res !== void 0 ? res : null)),
206
+ fail: err => console.warn('[zoomTurntable] showSmallMonitor fail', show, JSON.stringify(err !== null && err !== void 0 ? err : null))
207
+ });
208
+ } catch (err) {
209
+ console.error('[zoomTurntable] showSmallMonitor 调用异常', show, err);
210
+ }
211
+ };
212
+
213
+ /** 回包 / 事件载荷里的镜头通道,非数字视为无效 */
214
+ const pickChannel = payload => {
215
+ const num = Number(payload === null || payload === void 0 ? void 0 : payload.channel);
216
+ return Number.isFinite(num) ? num : null;
217
+ };
218
+
219
+ /** ty.ipc.queryCurrentChannel 的最小签名,同 switchChannel 一样本地声明 */
220
+
221
+ /**
222
+ * 查询当前镜头与本地缩放,用于出流后 / 重连后同步。
223
+ * 返回 null(不支持 / 失败 / 超时)时调用方保持现状,不重置镜头。
224
+ */
225
+ export const queryCurrentLens = devId => {
226
+ var _ty2;
227
+ const queryCurrentChannel = (_ty2 = ty) === null || _ty2 === void 0 || (_ty2 = _ty2.ipc) === null || _ty2 === void 0 ? void 0 : _ty2.queryCurrentChannel;
228
+ if (typeof queryCurrentChannel !== 'function') {
229
+ console.warn('[zoomTurntable] 宿主不支持 ty.ipc.queryCurrentChannel');
230
+ return Promise.resolve(null);
231
+ }
232
+ return new Promise(resolve => {
233
+ // 宿主始终不回调时不能把同步流程一直挂着;Promise 只结算一次,后到的忽略
234
+ const timer = setTimeout(() => {
235
+ console.warn('[zoomTurntable] queryCurrentChannel 超时', `${QUERY_LENS_TIMEOUT}ms`);
236
+ resolve(null);
237
+ }, QUERY_LENS_TIMEOUT);
238
+ const finish = value => {
239
+ clearTimeout(timer);
240
+ resolve(value);
241
+ };
242
+ try {
243
+ queryCurrentChannel({
244
+ deviceId: devId,
245
+ success: res => {
246
+ const channel = pickChannel(res);
247
+ if (channel == null) {
248
+ console.warn('[zoomTurntable] 回包里取不到 channel');
249
+ finish(null);
250
+ return;
251
+ }
252
+ // multiple 缺失 / 脏值按 1.0 兜底,否则会带进换算算出离谱倍率
253
+ const multiple = Number(res === null || res === void 0 ? void 0 : res.multiple);
254
+ finish({
255
+ lens_id: channel,
256
+ multiple: multiple > 0 ? multiple : 1.0
257
+ });
258
+ },
259
+ fail: err => {
260
+ console.warn('[zoomTurntable] queryCurrentChannel fail', JSON.stringify(err !== null && err !== void 0 ? err : null));
261
+ finish(null);
262
+ }
263
+ });
264
+ } catch (err) {
265
+ console.error('[zoomTurntable] queryCurrentChannel 调用异常', err);
266
+ finish(null);
267
+ }
268
+ });
269
+ };
270
+
271
+ /**
272
+ * 宿主是否支持镜头切换事件。
273
+ * 切镜头的收尾(画面归 1 倍 + 刷读数)以该事件为准,不支持时只能退回「switchChannel 成功即收尾」。
274
+ */
275
+ export const isChannelChangeSupported = () => {
276
+ var _ty3;
277
+ return typeof ((_ty3 = ty) === null || _ty3 === void 0 || (_ty3 = _ty3.ipc) === null || _ty3 === void 0 ? void 0 : _ty3.onChannelChange) === 'function';
278
+ };
279
+
280
+ /**
281
+ * 打印 ty.ipc 上与 channel 相关的方法名(顺原型链收集,方法可能挂在原型上)。
282
+ * 真机上「事件为什么不来」第一步就是确认宿主到底提供了哪个名字。
283
+ */
284
+ export const dumpChannelApis = () => {
285
+ var _ty4;
286
+ const ipc = (_ty4 = ty) === null || _ty4 === void 0 ? void 0 : _ty4.ipc;
287
+ if (!ipc) {
288
+ console.warn('[zoomTurntable] ty.ipc 不存在', `typeof ty=${typeof ty}`);
289
+ return;
290
+ }
291
+ const matched = describeApis(ipc, 'channel');
292
+ console.log(`[zoomTurntable] ty.ipc 上 channel 相关方法(${matched.length})`, matched.join(', '));
293
+ };
294
+
295
+ /** 订阅镜头切换事件(自己下发 / 设备侧 / 其他端切镜头都会上报),返回取消订阅函数 */
296
+ export const subscribeChannelChange = (devId, handler) => {
297
+ if (!isChannelChangeSupported()) {
298
+ console.warn('[zoomTurntable] 宿主不支持 ty.ipc.onChannelChange,切镜头只能按下发结果收尾');
299
+ dumpChannelApis();
300
+ return () => undefined;
301
+ }
302
+ const listener = payload => {
303
+ console.log('[zoomTurntable] channelChange 事件到达', JSON.stringify(payload !== null && payload !== void 0 ? payload : null));
304
+ const channel = pickChannel(payload);
305
+ if (channel == null) {
306
+ console.warn('[zoomTurntable] channelChange 缺少 channel');
307
+ return;
308
+ }
309
+ // 一页多设备时只认自己的;宿主没带 deviceId 就不过滤
310
+ const deviceId = payload === null || payload === void 0 ? void 0 : payload.deviceId;
311
+ if (deviceId && deviceId !== devId) {
312
+ // 不打日志的话,事件被这里吞掉与「事件根本没来」在日志上长得一样
313
+ console.warn('[zoomTurntable] channelChange 非本设备,已忽略', `${deviceId} != ${devId}`);
314
+ return;
315
+ }
316
+ handler({
317
+ deviceId,
318
+ channel
319
+ });
320
+ };
321
+ console.log('[zoomTurntable] 已订阅 ty.ipc.onChannelChange', devId);
322
+ ty.ipc.onChannelChange(listener);
323
+ return () => {
324
+ var _ty5;
325
+ console.log('[zoomTurntable] 解绑 channelChange 监听', devId);
326
+ if (typeof ((_ty5 = ty) === null || _ty5 === void 0 || (_ty5 = _ty5.ipc) === null || _ty5 === void 0 ? void 0 : _ty5.offChannelChange) === 'function') {
327
+ ty.ipc.offChannelChange(listener);
328
+ } else {
329
+ console.warn('[zoomTurntable] 宿主无 offChannelChange,监听无法解绑');
330
+ }
331
+ };
332
+ };
@@ -0,0 +1,22 @@
1
+ export type AnchorRect = {
2
+ left: number;
3
+ top: number;
4
+ width: number;
5
+ height: number;
6
+ right: number;
7
+ bottom: number;
8
+ };
9
+ /**
10
+ * 判断入口图标是否真正落在左下角滚动容器的可视范围内。
11
+ *
12
+ * 左下角是横向 ScrollView,元素过多时后面的入口会被裁掉,或被右边缘淡出遮罩盖住 ——
13
+ * 这种情况下画圈没有意义(圈到的是看不见的位置),所以返回 null。
14
+ *
15
+ * 测量策略与 ui/bottomLeftContent.tsx 同源:首测 60ms,随后 400ms 轮询兜底
16
+ * (左下角子元素异步加载后宽度会「长大」,一次测不准)。
17
+ *
18
+ * @returns 可见时返回入口相对视口的 rect,不可见 / 测不到返回 null
19
+ */
20
+ export declare const measureEntryVisible: () => Promise<AnchorRect | null>;
21
+ /** 播放器容器 rect,用于把入口的视口坐标换算成容器内坐标 */
22
+ export declare const measurePlayerRect: () => Promise<AnchorRect | null>;
@@ -0,0 +1,76 @@
1
+ import { FADE_SAFE_PADDING } from './constants';
2
+ import { ZOOM_ENTRY_ANCHOR_CLS } from './entryAnchor';
3
+ /** 首次测量延时:等布局沉降 */
4
+ const FIRST_DELAY = 60;
5
+ /** 轮询间隔 */
6
+ const POLL_INTERVAL = 400;
7
+ /** 最多轮询次数(约 6s),兜底异步内容撑开导致的尺寸变化 */
8
+ const MAX_ATTEMPTS = 15;
9
+ const queryRect = selector => new Promise(resolve => {
10
+ try {
11
+ const query = ty.createSelectorQuery();
12
+ query.select(selector).boundingClientRect();
13
+ query.exec(res => {
14
+ var _res$;
15
+ return resolve((_res$ = res === null || res === void 0 ? void 0 : res[0]) !== null && _res$ !== void 0 ? _res$ : null);
16
+ });
17
+ } catch (err) {
18
+ console.warn('[zoomTurntable] measure 失败', err);
19
+ resolve(null);
20
+ }
21
+ });
22
+ const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
23
+
24
+ /**
25
+ * 判断入口图标是否真正落在左下角滚动容器的可视范围内。
26
+ *
27
+ * 左下角是横向 ScrollView,元素过多时后面的入口会被裁掉,或被右边缘淡出遮罩盖住 ——
28
+ * 这种情况下画圈没有意义(圈到的是看不见的位置),所以返回 null。
29
+ *
30
+ * 测量策略与 ui/bottomLeftContent.tsx 同源:首测 60ms,随后 400ms 轮询兜底
31
+ * (左下角子元素异步加载后宽度会「长大」,一次测不准)。
32
+ *
33
+ * @returns 可见时返回入口相对视口的 rect,不可见 / 测不到返回 null
34
+ */
35
+ export const measureEntryVisible = async () => {
36
+ await wait(FIRST_DELAY);
37
+ for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
38
+ // eslint-disable-next-line no-await-in-loop
39
+ const entry = await queryRect(`.${ZOOM_ENTRY_ANCHOR_CLS}`);
40
+ // eslint-disable-next-line no-await-in-loop
41
+ const scroll = await queryRect('.ipc-player-bottom-left-content-wrap');
42
+ if ((entry === null || entry === void 0 ? void 0 : entry.width) > 0 && (scroll === null || scroll === void 0 ? void 0 : scroll.width) > 0) {
43
+ const visible = entry.left >= scroll.left - 1 && entry.left + entry.width <= scroll.right - FADE_SAFE_PADDING;
44
+ if (visible) {
45
+ var _entry$right, _entry$bottom;
46
+ return {
47
+ left: entry.left,
48
+ top: entry.top,
49
+ width: entry.width,
50
+ height: entry.height,
51
+ right: (_entry$right = entry.right) !== null && _entry$right !== void 0 ? _entry$right : entry.left + entry.width,
52
+ bottom: (_entry$bottom = entry.bottom) !== null && _entry$bottom !== void 0 ? _entry$bottom : entry.top + entry.height
53
+ };
54
+ }
55
+ // 测到了但不可见:不再重试,也不写「已读」标记,留待下次(例如用户横滑把入口滑出来)
56
+ return null;
57
+ }
58
+
59
+ // eslint-disable-next-line no-await-in-loop
60
+ await wait(POLL_INTERVAL);
61
+ }
62
+ return null;
63
+ };
64
+
65
+ /** 播放器容器 rect,用于把入口的视口坐标换算成容器内坐标 */
66
+ export const measurePlayerRect = () => queryRect('.ipc-player-content').then(rect => {
67
+ var _rect$right, _rect$bottom;
68
+ return (rect === null || rect === void 0 ? void 0 : rect.width) > 0 ? {
69
+ left: rect.left,
70
+ top: rect.top,
71
+ width: rect.width,
72
+ height: rect.height,
73
+ right: (_rect$right = rect.right) !== null && _rect$right !== void 0 ? _rect$right : rect.left + rect.width,
74
+ bottom: (_rect$bottom = rect.bottom) !== null && _rect$bottom !== void 0 ? _rect$bottom : rect.top + rect.height
75
+ } : null;
76
+ });
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import type { ZoomTurntableProps } from './type';
3
+ import './index.less';
4
+ /**
5
+ * 焦距转盘渲染层。
6
+ * 内嵌自 ray-zoom-turntable,实际绘制与手势在 rjs(render 线程)内完成,
7
+ * 拖动不过桥,保证跟手。
8
+ */
9
+ declare const ZoomTurntable: React.FC<ZoomTurntableProps>;
10
+ export default ZoomTurntable;
@@ -0,0 +1,76 @@
1
+ import React, { useRef } from 'react';
2
+ // @ts-ignore rjs 原生组件,无类型声明
3
+ import Component from './rjs/index.js';
4
+ import './index.less';
5
+ let canvasIdCounter = 0;
6
+
7
+ /**
8
+ * 焦距转盘渲染层。
9
+ * 内嵌自 ray-zoom-turntable,实际绘制与手势在 rjs(render 线程)内完成,
10
+ * 拖动不过桥,保证跟手。
11
+ */
12
+ const ZoomTurntable = props => {
13
+ const {
14
+ min = 1,
15
+ max = 6,
16
+ step = 0.1,
17
+ defaultValue = 1.0,
18
+ value: controlledValue,
19
+ scale = 1,
20
+ orientation = 'bottom',
21
+ cropRatio = 0.2,
22
+ onChange,
23
+ onChangeEnd,
24
+ onTouchStart,
25
+ onTouchEnd,
26
+ marks,
27
+ ticks,
28
+ curve = 1,
29
+ activeColor = '#FFFFFF',
30
+ styleOptions = {}
31
+ } = props;
32
+ const canvasId = useRef(`ipc-zoom-turntable-${++canvasIdCounter}`).current;
33
+ console.log('[zoomTurntable/logic] render', JSON.stringify({
34
+ canvasId,
35
+ min,
36
+ max,
37
+ step,
38
+ orientation,
39
+ cropRatio,
40
+ scale,
41
+ value: controlledValue
42
+ }));
43
+ return /*#__PURE__*/React.createElement(Component, {
44
+ canvasId: canvasId,
45
+ min: min,
46
+ max: max,
47
+ step: step,
48
+ defaultValue: defaultValue,
49
+ value: controlledValue,
50
+ scale: scale,
51
+ orientation: orientation,
52
+ cropRatio: cropRatio,
53
+ marks: marks,
54
+ ticks: ticks,
55
+ curve: curve,
56
+ activeColor: activeColor,
57
+ styleOptions: styleOptions,
58
+ bindchange: e => {
59
+ console.log('[zoomTurntable/logic] change', JSON.stringify(e === null || e === void 0 ? void 0 : e.detail));
60
+ onChange === null || onChange === void 0 || onChange(e.detail);
61
+ },
62
+ bindchangeEnd: e => {
63
+ console.log('[zoomTurntable/logic] changeEnd', JSON.stringify(e === null || e === void 0 ? void 0 : e.detail));
64
+ onChangeEnd === null || onChangeEnd === void 0 || onChangeEnd(e.detail);
65
+ },
66
+ bindturntableTouchStart: () => {
67
+ console.log('[zoomTurntable/logic] turntableTouchStart');
68
+ onTouchStart === null || onTouchStart === void 0 || onTouchStart();
69
+ },
70
+ bindturntableTouchEnd: () => {
71
+ console.log('[zoomTurntable/logic] turntableTouchEnd');
72
+ onTouchEnd === null || onTouchEnd === void 0 || onTouchEnd();
73
+ }
74
+ });
75
+ };
76
+ export default ZoomTurntable;
@@ -0,0 +1,11 @@
1
+ .ipc-player-plugin-zoom-turntable {
2
+ position: relative;
3
+ display: flex;
4
+ width: 100%;
5
+ flex-direction: column;
6
+ align-items: center;
7
+ justify-content: flex-end;
8
+ background-color: transparent;
9
+ user-select: none;
10
+ -webkit-user-select: none;
11
+ }