@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,228 @@
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
+ import "core-js/modules/esnext.iterator.some.js";
6
+ import React, { useEffect, useMemo, useRef, useState } from 'react';
7
+ import { View, getCurrentPages, usePageEvent } from '@ray-js/ray';
8
+ import clsx from 'clsx';
9
+ import { useMemoizedFn } from 'ahooks';
10
+ import { useStore } from '../../ctx/store';
11
+ import { PlayState } from '../../interface';
12
+ import { pauseTimeToHideAllComponent, playerTap } from '../../ui/constant';
13
+ import { useLensZoom } from './useLensZoom';
14
+ import ZoomTurntable from './turntable';
15
+ import { CROP_RATIO_BOTTOM, CROP_RATIO_RIGHT, RELAYOUT_DELAYS, STYLE_OPTIONS_BOTTOM, STYLE_OPTIONS_RIGHT, VIBRATE_THROTTLE, zoomTurntableClose } from './constants';
16
+ import './zoomTurntableDock.less';
17
+ /**
18
+ * 当前页面 id,ty.nativeDisabled 需要它定位要禁用手势分发的页面。
19
+ * 各宿主暴露的字段名不统一,逐个兜底取;取不到时传空串,由宿主按当前页处理。
20
+ */
21
+ const getPageId = () => {
22
+ try {
23
+ var _ref, _ref2, _ref3, _page$__wxWebviewId__;
24
+ const pages = getCurrentPages();
25
+ const page = pages === null || pages === void 0 ? void 0 : pages[pages.length - 1];
26
+ const pageId = (_ref = (_ref2 = (_ref3 = (_page$__wxWebviewId__ = page === null || page === void 0 ? void 0 : page.__wxWebviewId__) !== null && _page$__wxWebviewId__ !== void 0 ? _page$__wxWebviewId__ : page === null || page === void 0 ? void 0 : page.__webviewId__) !== null && _ref3 !== void 0 ? _ref3 : page === null || page === void 0 ? void 0 : page.__id__) !== null && _ref2 !== void 0 ? _ref2 : page === null || page === void 0 ? void 0 : page.pageId) !== null && _ref !== void 0 ? _ref : '';
27
+ return String(pageId);
28
+ } catch (err) {
29
+ console.warn('[zoomTurntable] 获取 pageId 失败', err);
30
+ return '';
31
+ }
32
+ };
33
+
34
+ /**
35
+ * 焦距转盘浮层。
36
+ *
37
+ * 必须由 CoverView 承载(Entry 里 addContent 不设 absoluteNoCoverView):
38
+ * 播放器是原生组件、层级高于普通 View,放在普通 View 里的 canvas 会被它盖住,
39
+ * touchstart 到不了 canvas,转盘完全滑不动(实测 setupEventListeners 成功但无任何 touch 日志)。
40
+ * 定位与层级由承载的 CoverView 通过 absolutePosition 提供。
41
+ */
42
+ export const ZoomTurntableDock = props => {
43
+ const {
44
+ event,
45
+ className
46
+ } = props;
47
+ const {
48
+ screenType,
49
+ playState
50
+ } = useStore({
51
+ screenType: props.screenType,
52
+ playState: props.playState
53
+ });
54
+ const {
55
+ multiLensReady,
56
+ curM,
57
+ min,
58
+ max,
59
+ step,
60
+ marks,
61
+ ticks,
62
+ beginDrag,
63
+ applyZoom
64
+ } = useLensZoom(_objectSpread(_objectSpread({}, props), {}, {
65
+ skipLoad: true
66
+ }));
67
+ const isRight = screenType !== 'vertical';
68
+ /** 横竖屏切换要重建 canvas 尺寸:换 key 强制 rjs 组件重新 attached/ready */
69
+ const [remountKey, setRemountKey] = useState(0);
70
+ const relayoutTimersRef = useRef([]);
71
+ /** 是否已禁用原生手势,避免重复调用与漏恢复 */
72
+ const nativeDisabledRef = useRef(false);
73
+ /** 上次震动时间与上次震动对应的倍率,用于节流与跨刻度判定 */
74
+ const lastVibrateAtRef = useRef(0);
75
+ const lastVibrateValueRef = useRef(curM);
76
+ const requestClose = useMemoizedFn(() => {
77
+ event.emit(zoomTurntableClose);
78
+ });
79
+
80
+ /**
81
+ * 转盘操作期间禁用异层渲染的手势分发,否则播放器原生层会抢走 touch 序列。
82
+ * 成对调用,且在 touchcancel、卸载路径上都要恢复,否则播放器手势会永久失效。
83
+ */
84
+ const setNativeDisabled = useMemoizedFn(disabled => {
85
+ var _ty;
86
+ if (nativeDisabledRef.current === disabled) return;
87
+ if (typeof ((_ty = ty) === null || _ty === void 0 ? void 0 : _ty.nativeDisabled) !== 'function') return;
88
+ nativeDisabledRef.current = disabled;
89
+ try {
90
+ ty.nativeDisabled({
91
+ nativeDisabled: disabled,
92
+ pageId: getPageId(),
93
+ fail: err => {
94
+ console.warn('[zoomTurntable] nativeDisabled fail', JSON.stringify(err));
95
+ }
96
+ });
97
+ } catch (err) {
98
+ console.warn('[zoomTurntable] nativeDisabled 调用异常', err);
99
+ }
100
+ });
101
+
102
+ // 展开期间转盘常驻,不做空闲自动收起;
103
+ // 自动隐藏倒计时已由 Entry 在 openDock 时 emit(pauseTimeToHideAllComponent) 暂停。
104
+ useEffect(() => {
105
+ event.emit(pauseTimeToHideAllComponent);
106
+ return () => {
107
+ relayoutTimersRef.current.forEach(timer => clearTimeout(timer));
108
+ relayoutTimersRef.current = [];
109
+ // 卸载兜底:手指还按着就被收起时,必须恢复原生手势
110
+ setNativeDisabled(false);
111
+ };
112
+ }, []);
113
+
114
+ // 点击播放器 → 收起转盘。
115
+ // ui.tsx 的 handPlayerTap 会在此时把其余控件重新显示出来(componentHideState → false)
116
+ // 并 emit(playerTap),所以这里只需负责关掉转盘。
117
+ useEffect(() => {
118
+ event.on(playerTap, requestClose);
119
+ return () => {
120
+ event.off(playerTap, requestClose);
121
+ };
122
+ }, [event, requestClose]);
123
+
124
+ // 横竖屏切换:重建画布
125
+ useEffect(() => {
126
+ setRemountKey(key => key + 1);
127
+ }, [screenType]);
128
+
129
+ /** 原生层布局沉降滞后,onShow / onResize 后延时重建画布兜底 */
130
+ const scheduleRelayout = useMemoizedFn(() => {
131
+ relayoutTimersRef.current.forEach(timer => clearTimeout(timer));
132
+ relayoutTimersRef.current = RELAYOUT_DELAYS.map(delay => setTimeout(() => setRemountKey(key => key + 1), delay));
133
+ });
134
+ usePageEvent('onShow', scheduleRelayout);
135
+ usePageEvent('onResize', scheduleRelayout);
136
+ usePageEvent('onHide', () => {
137
+ relayoutTimersRef.current.forEach(timer => clearTimeout(timer));
138
+ relayoutTimersRef.current = [];
139
+ requestClose();
140
+ });
141
+
142
+ /** 震动反馈:跨主刻度用 medium,普通刻度用 light 且节流 */
143
+ const vibrate = useMemoizedFn(type => {
144
+ var _ty2;
145
+ if (typeof ((_ty2 = ty) === null || _ty2 === void 0 ? void 0 : _ty2.vibrateShort) !== 'function') return;
146
+ try {
147
+ ty.vibrateShort({
148
+ type
149
+ });
150
+ } catch (err) {
151
+ console.warn('[zoomTurntable] vibrateShort 调用失败', err);
152
+ }
153
+ });
154
+ const handleChange = useMemoizedFn(value => {
155
+ const prev = lastVibrateValueRef.current;
156
+ lastVibrateValueRef.current = value;
157
+ beginDrag(value);
158
+
159
+ // 跨过主刻度(各镜头光学倍数 / 上限)时给更明显的反馈,形成挡位感
160
+ const crossedMark = marks.some(mark => prev < mark.value && value >= mark.value || prev > mark.value && value <= mark.value);
161
+ const now = Date.now();
162
+ if (crossedMark) {
163
+ lastVibrateAtRef.current = now;
164
+ vibrate('medium');
165
+ return;
166
+ }
167
+ if (now - lastVibrateAtRef.current >= VIBRATE_THROTTLE) {
168
+ lastVibrateAtRef.current = now;
169
+ vibrate('light');
170
+ }
171
+ });
172
+ const handleChangeEnd = useMemoizedFn(value => {
173
+ applyZoom(value);
174
+ });
175
+ const handleTouchStart = useMemoizedFn(() => {
176
+ setNativeDisabled(true);
177
+ // 每次按下重置震动基准,避免用上一次拖动结束时的值误判跨刻度
178
+ lastVibrateValueRef.current = curM;
179
+ lastVibrateAtRef.current = 0;
180
+ // 触摸期间同样保持暂停,避免其他控件在此期间重启倒计时
181
+ event.emit(pauseTimeToHideAllComponent);
182
+ });
183
+ const handleTouchEnd = useMemoizedFn(() => {
184
+ setNativeDisabled(false);
185
+ });
186
+
187
+ // 定位由承载的 CoverView 负责(absolutePosition),这里只做布局容器。
188
+ const turntableStyle = useMemo(() => ({
189
+ width: isRight ? 'auto' : '100%',
190
+ height: '100%',
191
+ display: 'flex',
192
+ flexDirection: isRight ? 'row' : 'column',
193
+ alignItems: 'center',
194
+ justifyContent: 'flex-end'
195
+ }), [isRight]);
196
+ if (!multiLensReady || playState !== PlayState.PLAYING) {
197
+ return null;
198
+ }
199
+
200
+ // 不要在外层套「铺满 + onClick」的遮罩层:父级 tap 手势识别器会 cancel 掉
201
+ // canvas 在 render 线程里的 touch 序列,转盘就滑不动了。
202
+ // 「点播放器收起」改为监听 ui.tsx 的 playerTap 事件(见上面的 effect)。
203
+ return /*#__PURE__*/React.createElement(View, {
204
+ className: clsx(className, 'ipc-player-plugin-zoom-turntable-dock', {
205
+ 'ipc-player-plugin-zoom-turntable-dock-right': isRight
206
+ }),
207
+ style: turntableStyle
208
+ }, /*#__PURE__*/React.createElement(ZoomTurntable, {
209
+ key: `${isRight ? 'right' : 'bottom'}-${remountKey}`,
210
+ value: curM,
211
+ defaultValue: curM,
212
+ min: min,
213
+ max: max,
214
+ step: step,
215
+ marks: marks,
216
+ ticks: ticks,
217
+ curve: 1,
218
+ scale: 1,
219
+ orientation: isRight ? 'right' : 'bottom',
220
+ cropRatio: isRight ? CROP_RATIO_RIGHT : CROP_RATIO_BOTTOM,
221
+ activeColor: "#FFFFFF",
222
+ styleOptions: isRight ? STYLE_OPTIONS_RIGHT : STYLE_OPTIONS_BOTTOM,
223
+ onChange: handleChange,
224
+ onChangeEnd: handleChangeEnd,
225
+ onTouchStart: handleTouchStart,
226
+ onTouchEnd: handleTouchEnd
227
+ }));
228
+ };
@@ -0,0 +1,11 @@
1
+ // 转盘浮层。
2
+ // 定位与层级由承载它的 CoverView 提供(absolutePosition,见 Entry 的 addContent),
3
+ // 这里只负责把 canvas 在容器内对齐,不再自己 position: absolute。
4
+ .ipc-player-plugin-zoom-turntable-dock {
5
+ overflow: visible;
6
+ }
7
+
8
+ // 横屏:贴右侧竖向展开
9
+ .ipc-player-plugin-zoom-turntable-dock-right {
10
+ height: 100%;
11
+ }
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import { ComponentConfigProps } from '../../interface';
3
+ import { ZoomSkill } from './lensSkill';
4
+ import './zoomTurntableEntry.less';
5
+ type Props = ComponentConfigProps & {
6
+ className?: string;
7
+ /** 业务侧的功能开关;实际是否渲染仍由 ipc_multi_zoom 解析出的 cnt 决定 */
8
+ showZoomTurntable?: boolean;
9
+ /** 调试用:直接注入镜头能力,跳过 ipc_multi_zoom DP 解析 */
10
+ mockZoomSkill?: ZoomSkill | boolean;
11
+ /** 长按判定时长,默认 500ms */
12
+ longPressDuration?: number;
13
+ /** 关闭圈选引导 */
14
+ disableTip?: boolean;
15
+ };
16
+ /**
17
+ * 焦距转盘入口:左下角工具条中的图标,长按弹出转盘。
18
+ * 仅 cnt > 1 的多镜头设备渲染;单镜头设备倍率交互仍由 RealTimeMagnification 承担。
19
+ */
20
+ export declare const ZoomTurntableEntry: (props: Props) => React.JSX.Element | null;
21
+ export {};
@@ -0,0 +1,253 @@
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.map.js";
4
+ import React, { useContext, useEffect, useRef, useState } from 'react';
5
+ import { View, Text } from '@ray-js/ray';
6
+ import clsx from 'clsx';
7
+ import { useMemoizedFn } from 'ahooks';
8
+ import { useStore } from '../../ctx/store';
9
+ import { UIEventContext } from '../../ui/context';
10
+ import { PlayState } from '../../interface';
11
+ import { bottomLeftContentResize, hideAllComponent, pauseTimeToHideAllComponent, showAllComponent, startTimeToHideAllComponent, widgetClick, widgetLabs } from '../../ui/constant';
12
+ import { Storage } from '../../utils/storage';
13
+ import { useLensZoom } from './useLensZoom';
14
+ import { ZoomTurntableDock } from './zoomTurntableDock';
15
+ import { ZoomTurntableTip } from './zoomTurntableTip';
16
+ import { measureEntryVisible } from './measureVisible';
17
+ import { ZOOM_ENTRY_ANCHOR_CLS } from './entryAnchor';
18
+ import { LONG_PRESS_MS, MOVE_TOLERANCE, ZOOM_DOCK_BOTTOM, ZOOM_DOCK_HEIGHT, ZOOM_DOCK_RIGHT, ZOOM_DOCK_Z_INDEX, zoomTurntableClose, zoomTurntableDockId, zoomTurntableOpen, zoomTurntableTipId } from './constants';
19
+ import './zoomTurntableEntry.less';
20
+ /**
21
+ * 焦距转盘入口:左下角工具条中的图标,长按弹出转盘。
22
+ * 仅 cnt > 1 的多镜头设备渲染;单镜头设备倍率交互仍由 RealTimeMagnification 承担。
23
+ */
24
+ export const ZoomTurntableEntry = props => {
25
+ var _props$event;
26
+ const {
27
+ devId,
28
+ className,
29
+ addContent,
30
+ deleteContent,
31
+ hasContent,
32
+ longPressDuration = LONG_PRESS_MS,
33
+ disableTip = false
34
+ } = props;
35
+ const {
36
+ event: ctxEvent
37
+ } = useContext(UIEventContext);
38
+ const event = (_props$event = props.event) !== null && _props$event !== void 0 ? _props$event : ctxEvent;
39
+ const {
40
+ playState,
41
+ screenType,
42
+ brandColor
43
+ } = useStore({
44
+ playState: props.playState,
45
+ screenType: props.screenType,
46
+ brandColor: props.brandColor
47
+ });
48
+ const {
49
+ multiLensReady,
50
+ lenses,
51
+ lensIndex,
52
+ curM,
53
+ switchToLens
54
+ } = useLensZoom(props);
55
+ const [dockVisible, setDockVisible] = useState(false);
56
+ const timerRef = useRef();
57
+ const startRef = useRef();
58
+ /** 长按已触发:用于抑制紧跟其后的 tap(部分宿主 longpress 后仍派发 tap) */
59
+ const firedRef = useRef(false);
60
+ /** 本次触摸已被判为横滑:既不出转盘也不切镜头 */
61
+ const cancelledRef = useRef(false);
62
+ const dockVisibleRef = useRef(false);
63
+ const tipCheckedRef = useRef(false);
64
+ const clearTimer = () => {
65
+ if (timerRef.current) {
66
+ clearTimeout(timerRef.current);
67
+ timerRef.current = undefined;
68
+ }
69
+ };
70
+ const closeDock = useMemoizedFn(() => {
71
+ if (!dockVisibleRef.current) return;
72
+ dockVisibleRef.current = false;
73
+ setDockVisible(false);
74
+ deleteContent('absolute', zoomTurntableDockId);
75
+ event.emit(showAllComponent);
76
+ event.emit(startTimeToHideAllComponent);
77
+ });
78
+ const openDock = useMemoizedFn(() => {
79
+ if (dockVisibleRef.current) {
80
+ closeDock();
81
+ return;
82
+ }
83
+ event.emit(widgetClick, {
84
+ widgetId: widgetLabs.ZOOM_TURNTABLE
85
+ });
86
+ // PTZ 与转盘都占底部 / 右侧,互斥
87
+ event.emit('ptzControlHide');
88
+ // 其余元素隐藏并暂停自动隐藏倒计时。
89
+ // 不要 emit('disablePlayerTap', true):点播放器收起转盘依赖 ui.tsx 正常派发 playerTap。
90
+ event.emit(hideAllComponent);
91
+ event.emit(pauseTimeToHideAllComponent);
92
+ if (!hasContent('absolute', zoomTurntableDockId)) {
93
+ // 必须用 CoverView 承载(不能设 absoluteNoCoverView):播放器是原生组件、层级更高,
94
+ // 普通 View 里的 canvas 会被它盖住,touchstart 到不了 canvas,转盘滑不动。
95
+ addContent('absolute', {
96
+ id: zoomTurntableDockId,
97
+ content: dockProps => /*#__PURE__*/React.createElement(ZoomTurntableDock, dockProps),
98
+ absolutePosition: screenType === 'full' ? {
99
+ top: 0,
100
+ right: ZOOM_DOCK_RIGHT,
101
+ bottom: 0,
102
+ zIndex: ZOOM_DOCK_Z_INDEX
103
+ } : {
104
+ left: 0,
105
+ right: 0,
106
+ bottom: ZOOM_DOCK_BOTTOM,
107
+ height: ZOOM_DOCK_HEIGHT,
108
+ zIndex: ZOOM_DOCK_Z_INDEX
109
+ },
110
+ initProps: _objectSpread(_objectSpread({}, props), {}, {
111
+ skipLoad: true
112
+ })
113
+ });
114
+ }
115
+ dockVisibleRef.current = true;
116
+ setDockVisible(true);
117
+ event.emit(zoomTurntableOpen);
118
+ });
119
+
120
+ // 转盘展开时 PTZ 被唤起 → 收起转盘
121
+ useEffect(() => {
122
+ const onPtzShow = () => closeDock();
123
+ event.on(zoomTurntableClose, closeDock);
124
+ event.on('ptzControlShow', onPtzShow);
125
+ return () => {
126
+ event.off(zoomTurntableClose, closeDock);
127
+ event.off('ptzControlShow', onPtzShow);
128
+ };
129
+ }, [event, closeDock]);
130
+
131
+ // 卸载 / 停流时兜底收起
132
+ useEffect(() => {
133
+ if (playState !== PlayState.PLAYING && dockVisibleRef.current) {
134
+ closeDock();
135
+ }
136
+ }, [playState, closeDock]);
137
+ useEffect(() => {
138
+ return () => {
139
+ clearTimer();
140
+ if (dockVisibleRef.current) {
141
+ deleteContent('absolute', zoomTurntableDockId);
142
+ }
143
+ };
144
+ }, []);
145
+
146
+ // 入口延迟出现:DP 异步到达才翻 multiLensReady,而 bottomLeftContent.length 不变,
147
+ // 左下角测不到宽度变化,横滑箭头与右边缘淡出会算错,需主动通知重测。tab 数量变化同理。
148
+ useEffect(() => {
149
+ event.emit(bottomLeftContentResize);
150
+ }, [multiLensReady, lenses.length, event]);
151
+
152
+ // ---------------- 圈选引导 ----------------
153
+ // 入口在横向 ScrollView 内可能被裁掉,只有真正可见时才提示;能力异步就绪,故判定在 multiLensReady 之后
154
+ useEffect(() => {
155
+ if (disableTip || !multiLensReady || playState !== PlayState.PLAYING) return undefined;
156
+ if (tipCheckedRef.current) return undefined;
157
+ let cancelled = false;
158
+ const storage = new Storage(devId);
159
+ (async () => {
160
+ // key 不存在时 getStorage 会 fail,这里不能让它把整条链路带崩
161
+ const read = await storage.get(zoomTurntableTipId).catch(() => null);
162
+ if (cancelled || read) return;
163
+ const rect = await measureEntryVisible();
164
+ if (cancelled || !rect) return;
165
+ tipCheckedRef.current = true;
166
+ addContent('absolute', {
167
+ id: zoomTurntableTipId,
168
+ content: tipProps => /*#__PURE__*/React.createElement(ZoomTurntableTip, tipProps),
169
+ absoluteContentClassName: 'ipc-player-plugin-zoom-turntable-tip-wrap',
170
+ initProps: _objectSpread(_objectSpread({}, props), {}, {
171
+ anchorRect: rect
172
+ })
173
+ });
174
+ })();
175
+ return () => {
176
+ cancelled = true;
177
+ };
178
+ }, [disableTip, multiLensReady, playState, screenType, devId]);
179
+
180
+ // ---------------- 长按 / 短按识别 ----------------
181
+ // 长按任意 tab → 弹转盘;短按 → 切到该镜头。
182
+ // 不用 onLongPress:入口在横向 ScrollView 内,原生 longpress 在部分宿主上不稳。
183
+ const onTouchStart = useMemoizedFn(e => {
184
+ var _e$touches$, _e$touches, _e$changedTouches, _touch$clientX, _touch$clientY;
185
+ const touch = (_e$touches$ = e === null || e === void 0 || (_e$touches = e.touches) === null || _e$touches === void 0 ? void 0 : _e$touches[0]) !== null && _e$touches$ !== void 0 ? _e$touches$ : e === null || e === void 0 || (_e$changedTouches = e.changedTouches) === null || _e$changedTouches === void 0 ? void 0 : _e$changedTouches[0];
186
+ startRef.current = {
187
+ x: (_touch$clientX = touch === null || touch === void 0 ? void 0 : touch.clientX) !== null && _touch$clientX !== void 0 ? _touch$clientX : 0,
188
+ y: (_touch$clientY = touch === null || touch === void 0 ? void 0 : touch.clientY) !== null && _touch$clientY !== void 0 ? _touch$clientY : 0
189
+ };
190
+ firedRef.current = false;
191
+ cancelledRef.current = false;
192
+ event.emit(pauseTimeToHideAllComponent);
193
+ clearTimer();
194
+ timerRef.current = setTimeout(() => {
195
+ firedRef.current = true;
196
+ openDock();
197
+ }, longPressDuration);
198
+ });
199
+ const onTouchMove = useMemoizedFn(e => {
200
+ var _e$touches2;
201
+ const touch = e === null || e === void 0 || (_e$touches2 = e.touches) === null || _e$touches2 === void 0 ? void 0 : _e$touches2[0];
202
+ if (!touch || !startRef.current) return;
203
+ const moved = Math.abs(touch.clientX - startRef.current.x) > MOVE_TOLERANCE || Math.abs(touch.clientY - startRef.current.y) > MOVE_TOLERANCE;
204
+ if (moved) {
205
+ // 判定为工具条横滑:既不出转盘,也不切镜头
206
+ cancelledRef.current = true;
207
+ clearTimer();
208
+ }
209
+ });
210
+ const onTouchEnd = useMemoizedFn(index => {
211
+ clearTimer();
212
+ // firedRef:长按已出转盘,不再当作点击处理
213
+ if (!firedRef.current && !cancelledRef.current) {
214
+ if (index !== lensIndex) {
215
+ event.emit(widgetClick, {
216
+ widgetId: widgetLabs.ZOOM_TURNTABLE
217
+ });
218
+ switchToLens(index);
219
+ }
220
+ event.emit(startTimeToHideAllComponent);
221
+ }
222
+ });
223
+ if (playState !== PlayState.PLAYING || !multiLensReady) {
224
+ return null;
225
+ }
226
+ return /*#__PURE__*/React.createElement(View, {
227
+ className: clsx(className, ZOOM_ENTRY_ANCHOR_CLS)
228
+ }, /*#__PURE__*/React.createElement(View, {
229
+ className: clsx('ipc-player-plugin-zoom-turntable-tabs', {
230
+ 'ipc-player-plugin-zoom-turntable-tabs-full': screenType === 'full'
231
+ })
232
+ }, lenses.map((lens, index) => {
233
+ const active = index === lensIndex;
234
+ // 当前镜头实时显示画面绝对倍率(手势放大时 1.0X → 2.5X),其余显示各自光学倍数
235
+ const text = `${(active ? curM : lens.basicRatio).toFixed(1)}X`;
236
+ return /*#__PURE__*/React.createElement(View, {
237
+ key: lens.id,
238
+ className: clsx('ipc-player-plugin-zoom-turntable-tab', {
239
+ 'ipc-player-plugin-zoom-turntable-tab-full': screenType === 'full',
240
+ 'ipc-player-plugin-zoom-turntable-tab-active': active
241
+ }),
242
+ style: active && dockVisible ? {
243
+ backgroundColor: brandColor
244
+ } : undefined,
245
+ onTouchStart: onTouchStart,
246
+ onTouchMove: onTouchMove,
247
+ onTouchEnd: () => onTouchEnd(index),
248
+ onTouchCancel: () => onTouchEnd(index)
249
+ }, /*#__PURE__*/React.createElement(Text, {
250
+ className: clsx('ipc-player-plugin-zoom-turntable-tab-text')
251
+ }, text));
252
+ })));
253
+ };
@@ -0,0 +1,51 @@
1
+ // 倍率 tab 组:按协议 zoom_skill.lenses 渲染 N 个,
2
+ // 当前镜头高亮并实时显示画面绝对倍率,其余显示各自光学倍数。
3
+ //
4
+ // 视觉上是「一个整体胶囊」:圆角与阴影收在外层容器并 overflow 裁剪,
5
+ // 各 tab 自身不带圆角、不留间隔,靠底色深浅区分选中态。
6
+ // stretch 让各 tab 等高,避免选中/未选中因字重渲染差异出现半像素错位。
7
+ .ipc-player-plugin-zoom-turntable-tabs {
8
+ display: flex;
9
+ flex-direction: row;
10
+ align-items: stretch;
11
+ border-radius: calc(4px * var(--ipc-player-size-scale, 1));
12
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
13
+ overflow: hidden;
14
+ }
15
+
16
+ .ipc-player-plugin-zoom-turntable-tabs-full {
17
+ border-radius: calc(5px * var(--ipc-player-size-scale, 1));
18
+ }
19
+
20
+ // 与 RealTimeMagnification / Resolution 完全同构:
21
+ // 不声明 height / min-height,也不声明 line-height,
22
+ // 高度由 padding + 文字默认行盒撑开,这样才能和左下角其他控件逐像素齐平。
23
+ // (曾用 line-height: 1.2 + min-height 兜,反而比默认行高矮 2px)
24
+ .ipc-player-plugin-zoom-turntable-tab {
25
+ display: flex;
26
+ justify-content: center;
27
+ align-items: center;
28
+ padding: calc(4px * var(--ipc-player-size-scale, 1)) calc(5px * var(--ipc-player-size-scale, 1));
29
+ background-color: rgba(255, 255, 255, 0.3);
30
+
31
+ .ipc-player-plugin-zoom-turntable-tab-text {
32
+ color: var(--iconColor);
33
+ font-weight: 600;
34
+ font-size: calc(12px * var(--ipc-player-size-scale, 1));
35
+ }
36
+ }
37
+
38
+ // 横屏:左右 padding 放宽,同 RealTimeMagnification 的 full 态
39
+ .ipc-player-plugin-zoom-turntable-tab-full {
40
+ padding-left: calc(8px * var(--ipc-player-size-scale, 1));
41
+ padding-right: calc(8px * var(--ipc-player-size-scale, 1));
42
+ }
43
+
44
+ // 选中态:白底黑字(须在基础态之后定义,同优先级下后者生效)
45
+ .ipc-player-plugin-zoom-turntable-tab-active {
46
+ background-color: rgba(255, 255, 255, 0.92);
47
+
48
+ .ipc-player-plugin-zoom-turntable-tab-text {
49
+ color: #000;
50
+ }
51
+ }
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import { ComponentConfigProps } from '../../interface';
3
+ import { AnchorRect } from './measureVisible';
4
+ import './zoomTurntableTip.less';
5
+ type Props = ComponentConfigProps & {
6
+ /** 入口图标的视口 rect,由 measureEntryVisible 测得 */
7
+ anchorRect?: AnchorRect;
8
+ };
9
+ /**
10
+ * 焦距入口圈选引导(一次性)。
11
+ *
12
+ * 与 multiCamera 的 tileTip 同一套视觉(虚线圈 + 超大扩散阴影压暗 + 引线 + 文案 + 知道了),
13
+ * 差别在于入口位于横向 ScrollView 内、位置不固定,所以圈选框坐标由 JS 内联注入而非写死。
14
+ */
15
+ export declare const ZoomTurntableTip: (props: Props) => React.JSX.Element | null;
16
+ export {};
@@ -0,0 +1,76 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { CoverView, Text, View } from '@ray-js/ray';
3
+ import clsx from 'clsx';
4
+ import { useStore } from '../../ctx/store';
5
+ import { PlayState } from '../../interface';
6
+ import { widgetClick, widgetLabs } from '../../ui/constant';
7
+ import { Storage } from '../../utils/storage';
8
+ import Strings from '../../i18n';
9
+ import { measurePlayerRect } from './measureVisible';
10
+ import { TIP_RING_SIZE, zoomTurntableTipId } from './constants';
11
+ import './zoomTurntableTip.less';
12
+ /**
13
+ * 焦距入口圈选引导(一次性)。
14
+ *
15
+ * 与 multiCamera 的 tileTip 同一套视觉(虚线圈 + 超大扩散阴影压暗 + 引线 + 文案 + 知道了),
16
+ * 差别在于入口位于横向 ScrollView 内、位置不固定,所以圈选框坐标由 JS 内联注入而非写死。
17
+ */
18
+ export const ZoomTurntableTip = props => {
19
+ const {
20
+ devId,
21
+ deleteContent,
22
+ anchorRect
23
+ } = props;
24
+ const {
25
+ playState
26
+ } = useStore({
27
+ playState: props.playState
28
+ });
29
+ const [ringStyle, setRingStyle] = useState(null);
30
+
31
+ // 把入口的视口坐标换算成播放器容器内坐标
32
+ useEffect(() => {
33
+ if (!anchorRect) return undefined;
34
+ let cancelled = false;
35
+ measurePlayerRect().then(player => {
36
+ if (cancelled || !player) return;
37
+ const centerX = anchorRect.left - player.left + anchorRect.width / 2;
38
+ setRingStyle({
39
+ left: `${centerX - TIP_RING_SIZE / 2}px`,
40
+ bottom: `${player.bottom - anchorRect.bottom - (TIP_RING_SIZE - anchorRect.height) / 2}px`,
41
+ width: `${TIP_RING_SIZE}px`,
42
+ height: `${TIP_RING_SIZE}px`
43
+ });
44
+ });
45
+ return () => {
46
+ cancelled = true;
47
+ };
48
+ }, [anchorRect]);
49
+ if (playState !== PlayState.PLAYING || !ringStyle) {
50
+ return null;
51
+ }
52
+ return /*#__PURE__*/React.createElement(CoverView, null, /*#__PURE__*/React.createElement(View, {
53
+ className: clsx('ipc-player-plugin-zoom-turntable-tip-ring'),
54
+ style: ringStyle
55
+ }), /*#__PURE__*/React.createElement(Text, {
56
+ className: clsx('ipc-player-plugin-zoom-turntable-tip-text'),
57
+ style: {
58
+ left: ringStyle.left,
59
+ bottom: `calc(${ringStyle.bottom} + 116px)`
60
+ }
61
+ }, Strings.getLang('ipc_player_zoom_turntable_tip_text')), /*#__PURE__*/React.createElement(View, {
62
+ className: clsx('ipc-player-plugin-zoom-turntable-tip-button'),
63
+ style: {
64
+ left: ringStyle.left,
65
+ bottom: `calc(${ringStyle.bottom} + 56px)`
66
+ },
67
+ onClick: () => {
68
+ props.event.emit(widgetClick, {
69
+ widgetId: widgetLabs.ZOOM_TURNTABLE_TIP
70
+ });
71
+ deleteContent('absolute', zoomTurntableTipId);
72
+ const storage = new Storage(devId);
73
+ storage.set(zoomTurntableTipId, true);
74
+ }
75
+ }, Strings.getLang('ipc_player_tip_confirm')));
76
+ };