@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.
@@ -1,8 +1,8 @@
1
1
  @font-face {
2
2
  font-family: "icon-panel"; /* Project id 4727767 */
3
- src: url('iconfont.woff2?t=1787047721746') format('woff2'),
4
- url('iconfont.woff?t=1787047721746') format('woff'),
5
- url('iconfont.ttf?t=1787047721746') format('truetype');
3
+ src: url('iconfont.woff2?t=1787276656877') format('woff2'),
4
+ url('iconfont.woff?t=1787276656877') format('woff'),
5
+ url('iconfont.ttf?t=1787276656877') format('truetype');
6
6
  }
7
7
 
8
8
  .icon-panel {
@@ -1463,7 +1463,7 @@
1463
1463
  },
1464
1464
  {
1465
1465
  "icon_id": "43222962",
1466
- "name": "one-way-intercom",
1466
+ "name": "yuyin",
1467
1467
  "font_class": "yuyin",
1468
1468
  "unicode": "e63c",
1469
1469
  "unicode_decimal": 58940
Binary file
Binary file
Binary file
package/lib/ui/ui.js CHANGED
@@ -586,7 +586,10 @@ export const IPCPlayerIntegration = /*#__PURE__*/React.memo(props => {
586
586
  pageShownRef.current = true;
587
587
  // 回到前台时刷新画质(试用)状态(方法内部会按 hideSmartImageQualityState 自行短路)
588
588
  refreshSmartImageQuality();
589
+ // 从后台回来延续离开前的状态:不动 componentHideState,也不重置「用户已点过画面」
590
+ // 的标记 —— 切后台只是暂时离开,回来强行展示或重新常驻都显得突兀。
589
591
  // 已经在出流就直接启动;否则等 playState 变为 PLAYING 的 effect 再启动
592
+ // (用户从没点过画面时,startHideTimer 仍会跳过,控件保持常驻)
590
593
  if (playStateRef.current === PlayState.PLAYING) {
591
594
  eventRef.current.emit(startTimeToHideAllComponent);
592
595
  }
@@ -854,21 +857,36 @@ export const IPCPlayerIntegration = /*#__PURE__*/React.memo(props => {
854
857
  instance.changeStreamStatus(code);
855
858
  });
856
859
  const isHidingByTapRef = useRef(false);
860
+ /**
861
+ * 用户是否手势点击过播放器画面。
862
+ * 刚进入播放器时控件常驻、不走自动隐藏,等用户第一次点画面之后才启用倒计时 ——
863
+ * 一进来就 5s 消失,用户还没看清有哪些功能。
864
+ */
865
+ const hasUserTappedPlayerRef = useRef(false);
857
866
  const hasInitializedLandscapeMultiRef = useRef(false);
858
867
  const multiTapTimerRef = useRef(null);
859
868
  const prevSelectedIndexRef = useRef(selectedLenInfo.index);
860
869
  /**
861
- * 展示所有组件,一段时间后自动关闭
870
+ * 启动自动隐藏倒计时的统一入口。
871
+ * 用户还没点过播放器画面时直接跳过 —— 控件常驻,直到用户第一次交互。
862
872
  */
863
- const showComponents = () => {
873
+ const startHideTimer = useMemoizedFn(onTimeout => {
864
874
  if (timer.current) {
865
875
  clearTimeout(timer.current);
866
876
  timer.current = null;
867
877
  }
878
+ if (!hasUserTappedPlayerRef.current) return;
879
+ timer.current = setTimeout(onTimeout, HIDE_COMPONENT_TIME);
880
+ });
881
+
882
+ /**
883
+ * 展示所有组件,一段时间后自动关闭
884
+ */
885
+ const showComponents = () => {
868
886
  setComponentHideState(false);
869
- timer.current = setTimeout(() => {
887
+ startHideTimer(() => {
870
888
  setComponentHideState(true);
871
- }, HIDE_COMPONENT_TIME);
889
+ });
872
890
  };
873
891
  const triggerEvent = type => {
874
892
  if (prevTriggerEvent.current === type) return;
@@ -877,26 +895,20 @@ export const IPCPlayerIntegration = /*#__PURE__*/React.memo(props => {
877
895
  timer.current = null;
878
896
  }
879
897
  if (type === showAllComponent) {
880
- // @ts-ignore
881
- timer.current = setTimeout(() => {
898
+ startHideTimer(() => {
882
899
  triggerEvent(hideAllComponent);
883
- }, HIDE_COMPONENT_TIME);
900
+ });
884
901
  }
885
902
  eventRef.current.emit(type);
886
903
  };
887
904
  const listenStart = useMemoizedFn(() => {
888
- if (timer.current) {
889
- clearTimeout(timer.current);
890
- timer.current = null;
891
- }
892
- // 重新开始计时
893
- // @ts-ignore
894
- timer.current = setTimeout(() => {
905
+ // 重新开始计时(用户还没点过画面时不启动,见 startHideTimer)
906
+ startHideTimer(() => {
895
907
  // 定时器触发时从 store 读最新状态,避免启动时闭包拿到过期的 isVerticalFullLayout
896
908
  const store = getDefaultStore();
897
909
  if (store.get(instance.isVerticalFullLayout) && store.get(instance.screenType) === 'vertical') return;
898
910
  triggerEvent(hideAllComponent);
899
- }, HIDE_COMPONENT_TIME);
911
+ });
900
912
  });
901
913
  const listenPause = useMemoizedFn(() => {
902
914
  timer.current && clearTimeout(timer.current);
@@ -1089,13 +1101,20 @@ export const IPCPlayerIntegration = /*#__PURE__*/React.memo(props => {
1089
1101
  onPlayerTap: data => {
1090
1102
  console.log('playerTap event');
1091
1103
  if (disablePlayerTap.current) {
1092
- console.log('playerTap 事件已禁止');
1104
+ // 「禁用」只针对「点画面切换控件显隐」这件事(竖屏全屏下控件要常驻),
1105
+ // 但用户确实点了画面这个事实仍要派发出去 —— 转盘、PTZ 这类浮层靠它收起。
1106
+ // 不派发的话,竖屏全屏下点播放器关不掉转盘(普通竖屏可以,因为那时没禁用)。
1107
+ console.log('playerTap 事件已禁止显隐切换,仅派发 tap 通知浮层');
1108
+ eventRef.current.emit(playerTap);
1093
1109
  return;
1094
1110
  }
1095
1111
  console.log('res===on onVideoTap1', data);
1112
+ // 这一下起,自动隐藏倒计时才开始生效
1113
+ hasUserTappedPlayerRef.current = true;
1096
1114
  if (instance.multiCameraCtx.isSupport) {
1097
1115
  var _instance$multiCamera, _instance$multiCamera2;
1098
1116
  const preTapIndex = (_instance$multiCamera = (_instance$multiCamera2 = instance.multiCameraCtx.selectedLenInfoRef.current) === null || _instance$multiCamera2 === void 0 ? void 0 : _instance$multiCamera2.index) !== null && _instance$multiCamera !== void 0 ? _instance$multiCamera : prevSelectedIndexRef.current;
1117
+ const wasHidden = componentHideState;
1099
1118
  instance.multiCameraCtx.selectVideoFiredRef.current = false;
1100
1119
  if (multiTapTimerRef.current) {
1101
1120
  clearTimeout(multiTapTimerRef.current);
@@ -1105,7 +1124,7 @@ export const IPCPlayerIntegration = /*#__PURE__*/React.memo(props => {
1105
1124
  const selectFired = !!instance.multiCameraCtx.selectVideoFiredRef.current;
1106
1125
  const currentIndex = (_instance$multiCamera3 = (_instance$multiCamera4 = instance.multiCameraCtx.selectedLenInfoRef.current) === null || _instance$multiCamera4 === void 0 ? void 0 : _instance$multiCamera4.index) !== null && _instance$multiCamera3 !== void 0 ? _instance$multiCamera3 : preTapIndex;
1107
1126
  if (selectFired && currentIndex === preTapIndex) {
1108
- if (componentHideState) {
1127
+ if (wasHidden) {
1109
1128
  showComponents();
1110
1129
  } else {
1111
1130
  if (timer.current) {
@@ -1118,6 +1137,13 @@ export const IPCPlayerIntegration = /*#__PURE__*/React.memo(props => {
1118
1137
  // 点击不同镜头或非镜头区域时,保持现有行为
1119
1138
  showComponents();
1120
1139
  }
1140
+ // 多镜头分支原先漏了这一发:转盘、PTZ、清晰度这些浮层都靠 playerTap 收起
1141
+ // (见本函数顶部 disablePlayerTap 分支的注释),不派发的话多镜头设备上
1142
+ // 点画面根本关不掉转盘。隐藏路径同样要加 isHidingByTapRef 保护,
1143
+ // 免得浮层的 showAllComponent 把这次隐藏又顶回去。
1144
+ isHidingByTapRef.current = !wasHidden;
1145
+ eventRef.current.emit(playerTap);
1146
+ isHidingByTapRef.current = false;
1121
1147
  prevSelectedIndexRef.current = currentIndex;
1122
1148
  multiTapTimerRef.current = null;
1123
1149
  }, 50);
@@ -226,7 +226,12 @@ export const VoiceIntercom = /*#__PURE__*/forwardRef((props, ref) => {
226
226
  }, icon);
227
227
  }
228
228
  return /*#__PURE__*/React.createElement(View, {
229
- className: clsx('icon-panel', 'icon-panel-one-way-intercom', mode === 'circle' && 'intercom-default-size', mode !== 'circle' && 'ipc-player-plugin-voice-small-intercom', iconClassName)
229
+ className: clsx('icon-panel',
230
+ // 类名按 iconfont 的 font_class 取,不是 name —— 图标库里这枚图标的
231
+ // name 是「one-way-intercom」(单向对讲)、font_class 是「yuyin」(unicode e63c),
232
+ // 而 CSS 类是按 font_class 生成的。曾经按 name 拼类名,字形永远找不到,
233
+ // 重新导出多少次都不会有那个类。别再"顺手改成语义化名字"。
234
+ 'icon-panel-yuyin', mode === 'circle' && 'intercom-default-size', mode !== 'circle' && 'ipc-player-plugin-voice-small-intercom', iconClassName)
230
235
  });
231
236
  })());
232
237
  });
@@ -27,14 +27,19 @@ export declare const zoomTurntableSwitching = "zoomTurntableSwitching";
27
27
  export declare const LONG_PRESS_MS = 500;
28
28
  /** 长按期间允许的位移容差(px),超出视为横滑并取消长按 */
29
29
  export declare const MOVE_TOLERANCE = 10;
30
- /** switchChannel 下发超时(ms):回调始终不来时不知道切没切成,超时回滚读数 */
31
- export declare const SWITCH_LENS_TIMEOUT = 3000;
30
+ /**
31
+ * switchChannel 下发超时(ms):回调始终不来时不知道切没切成,超时回滚读数。
32
+ *
33
+ * 取 10s:设备切镜头本身就慢,回调回来得晚是常态;给短了会在回调还在路上时
34
+ * 先把读数回滚掉,随后回调又落地,反而多一次跳变。
35
+ */
36
+ export declare const SWITCH_LENS_TIMEOUT = 10000;
32
37
  /**
33
38
  * switchChannel 已 success 后,等 channelChange 报「切到位」的时长(ms)。
34
39
  * 超时按下发结果自己收尾,**不回滚** —— 指令已被受理,物理镜头其实已经切了,
35
40
  * 回滚读数只会让读数和画面对不上。
36
41
  *
37
- * 取 10s,比 SWITCH_LENS_TIMEOUT 宽得多:设备真正切完镜头本身要一会儿,
42
+ * 取 10s,与 SWITCH_LENS_TIMEOUT 同档:设备真正切完镜头本身要一会儿,
38
43
  * 给短了会在事件晚到时提前自己收尾,等于白订阅。
39
44
  * 真机现状:设备侧暂未推 channelChange,切镜头基本都靠这条超时收尾。
40
45
  */
@@ -52,73 +57,233 @@ export declare const QUERY_LENS_TIMEOUT = 3000;
52
57
  * 待 APP 提供 zoomFree 的「手势是否结束」参数后可改为由该参数触发。
53
58
  */
54
59
  export declare const GESTURE_SWITCH_DEBOUNCE = 500;
60
+ /**
61
+ * 切镜头到位后,补下发目标倍率(d > 1)的延时(ms)。
62
+ *
63
+ * 新镜头的流刚起来,立刻打 scaleMultiple 有被原生层吞掉的风险;这段延时里的中间态回读
64
+ * 由 settleApplyStore 挡住,读数不会先闪回镜头正倍数。归正倍数(d = 1)不走延时,与旧行为一致。
65
+ */
66
+ export declare const POST_SWITCH_APPLY_DELAY = 200;
67
+ /**
68
+ * 补差值下发后,等回读追上目标 d 的最长时间(ms)。
69
+ * 超时解除中间态屏蔽 —— 原生层没按目标值回读时(量化、失败),读数不能被永久冻住。
70
+ */
71
+ export declare const SETTLE_APPLY_TIMEOUT = 3000;
72
+ /**
73
+ * 触底判定:连续多少帧 d ≈ 1 才认为「用户还在继续往下缩」。
74
+ *
75
+ * 取 2:第一帧是「正常缩到该镜头的光学正倍数」,从第二帧起 d 已经夹在协议下限动不了,
76
+ * 却仍有回读推上来,说明手指还在往里收。取 1 会把正常的「缩到正倍数」误判成越界。
77
+ */
78
+ export declare const FLOOR_HIT_COUNT = 2;
79
+ /** 触底计数的有效窗口(ms):两帧间隔超过它就不算连续,计数重置 */
80
+ export declare const FLOOR_HIT_WINDOW = 1200;
81
+ /**
82
+ * 切镜头收尾后的触底静默期(ms)。
83
+ * 收尾自身的 emit(setScaleMultipleEvent, 1.0) 会引发一到两帧 d = 1 回读,
84
+ * 不挡掉就会被算进触底计数,刚切上去又被切回来。
85
+ */
86
+ export declare const FLOOR_HIT_SETTLE_GUARD = 800;
87
+ /** 相邻两次「触底自动下切镜头」的冷却(ms):一次长手势不该连着切两级 */
88
+ export declare const DOWN_SWITCH_COOLDOWN = 800;
89
+ /**
90
+ * 同一次自动切镜头的跨实例去重窗口(ms)。
91
+ * Entry / Dock 各跑一份 useLensZoom,都订阅 zoomLevelChange,会同时判出同一次切换 ——
92
+ * 不去重就是两发 switchChannel。
93
+ */
94
+ export declare const SWITCH_DEDUPE_WINDOW = 300;
95
+ /**
96
+ * 转盘拖动跨镜头临界后,手停住多久(ms)就地切镜头。
97
+ *
98
+ * 逐帧切不行(switchChannel 是 P2P,几百毫秒级,会把通道打爆),只等松手也不行
99
+ * (拖过界的一路上画面是「冻」的)。折中:过界后手停住这么久就切,画面跟着拖动走。
100
+ */
101
+ export declare const DRAG_CROSS_SWITCH_DELAY = 600;
55
102
  /** dock 层级,需高于四角容器的 z-index: 100 */
56
103
  export declare const ZOOM_DOCK_Z_INDEX = 120;
57
104
  /**
58
- * 竖屏截取比例:从椭圆上弧顶往下截取多少。
59
- * 0.5 = 完整上半椭圆(截面底边落在最宽处);> 0.5 会带出两侧下弯段。
60
- * 取 0.5 让背景一路铺到播放器底边、不留空隙,多出来的部分靠 ZOOM_DOCK_BOTTOM 探出被裁掉。
105
+ * 竖屏播放器高度:容器按 16:9 推导。
106
+ * 不用固定值,pad / 折叠屏 / 大屏机型上转盘才能始终占到 1/3。
107
+ */
108
+ export declare const getVerticalPlayerHeight: () => number;
109
+ /** 竖屏遮罩椭圆横半径:跟着容器宽度缩放,各机型弧形一致 */
110
+ export declare const getEllipseRxBottom: () => number;
111
+ /** 竖屏背景弧顶到播放器底边的可见高度:播放器高的 1/3(375 宽机型约 70px) */
112
+ export declare const getZoomVisibleHeightBottom: () => number;
113
+ /** 竖屏遮罩椭圆纵半径:跟着可见高度按 ELLIPSE_RY_TO_HEIGHT 缩放,各机型弯度一致 */
114
+ export declare const getEllipseRyBottom: () => number;
115
+ /**
116
+ * 竖屏截取比例:从椭圆上弧顶往下截多深。
117
+ *
118
+ * = 可见高度 / 椭圆全高 = 1 / (2 × ELLIPSE_RY_TO_HEIGHT) ≈ 0.273,与机型无关
119
+ * (可见高与纵半径同比例缩放)。**关键**:截面高度就等于要显示的高度,
120
+ * 不再靠「椭圆探出播放器底边、多出部分被裁」——
121
+ * CoverView 里 canvas 的负 margin 并不可靠,一旦不生效,探出去的那截就会原地显示出来,
122
+ * 表现就是转盘整体被顶高一截(实测占到播放器高的 1/2)、底边还留一条透明缝。
123
+ * 现在 canvas 底边 = 内容底边 = 播放器底边,不依赖负 margin 与裁切。
61
124
  */
62
- export declare const CROP_RATIO_BOTTOM = 0.5;
125
+ export declare const CROP_RATIO_BOTTOM: number;
126
+ /**
127
+ * 轨道虚线弧与外圈遮罩弧之间的等距间距(px,scale=1)。
128
+ *
129
+ * rjs 侧按「横纵半径各减同一个绝对值」内收,所以这个值就是两条弧线处处的间距,
130
+ * 两条弧因此弧度一致、看着是一对平行弧(旧的按比例内收会让弧顶间距远小于两侧)。
131
+ *
132
+ * 用绝对值而不是跟着带高缩放:竖屏背景带本身就只有 70px 上下,间距按比例缩会失真。
133
+ *
134
+ * 竖屏取 32:它同时决定「文案下缘到虚线弧顶」的距离 —— 文案占掉
135
+ * LABEL_TOP_GAP_BOTTOM + 字高(11 + 12 = 23px),剩下 9px 就是两者之间的留白,
136
+ * 与设计稿一致(28 时只剩 5px,文字贴着刻度线)。
137
+ * 横屏取 40,与改动前的弧顶间距(39.25)保持一致,横屏观感不变。
138
+ */
139
+ export declare const TRACK_EDGE_INSET_BOTTOM = 32;
140
+ export declare const TRACK_EDGE_INSET_RIGHT = 40;
141
+ /**
142
+ * 竖屏刻度文案「顶边到背景弧」的留白(px,scale=1)。
143
+ *
144
+ * 文案位置以**遮罩弧**为基准、与它等距内收,而不是以轨道弧往外推:
145
+ * 这样调轨道间距(TRACK_EDGE_INSET_BOTTOM)文案不会跟着上下窜,
146
+ * 当前倍率(12px)与刻度数字(10px)两种字号的顶边也落在同一条线上。
147
+ *
148
+ * 取 11 = 最初的 6 再加 5:文字与外边缘贴太近显得局促。
149
+ */
150
+ export declare const LABEL_TOP_GAP_BOTTOM = 11;
151
+ /**
152
+ * 竖屏虚线弧「扎出播放器底边的位置」距左右边的距离(px)。
153
+ *
154
+ * 设计稿里刻度线两端是**插进播放器底部**的,而且插出去的位置离左右边还有一段距离 ——
155
+ * 不是贴着左右下角擦出去(那样看着就是「顶到最左最右」)。
156
+ */
157
+ export declare const TRACK_SIDE_GAP_BOTTOM = 28;
158
+ /**
159
+ * 竖屏轨道椭圆的横半径:由「虚线弧在距左右边 TRACK_SIDE_GAP_BOTTOM 处正好降到播放器底边」反推。
160
+ *
161
+ * 设 x = 屏幕半宽 − 留白、弧顶到底边的高度 h = 可见高 − 间距、纵半径 ry:
162
+ * ry × (1 − √(1 − (x / rx)²)) = h ⟹ rx = x / √(1 − (1 − h/ry)²)
163
+ *
164
+ * 算出来的 rx 比「遮罩横半径 − 间距」小一截,所以虚线弧比遮罩弧更弯 ——
165
+ * 两条弧只在弧顶附近保持 TRACK_EDGE_INSET_BOTTOM 的间距,往两侧虚线弧下坠得更快,
166
+ * 这正是设计稿的样子(背景横向铺满、刻度线扎进底部)。
167
+ */
168
+ export declare const getTrackRxBottom: () => number;
63
169
  /**
64
170
  * 触摸响应余量:canvas 是唯一能收 touch 的元素,把它向外扩一圈,
65
171
  * **整个转盘范围(椭圆截面 + 上方文案区 + 两侧空白)都能拖动**,而不是只有刻度线那一带。
66
172
  *
67
- * 绘制坐标由 renderer 按同样的余量补偿,视觉不变;下面的 ZOOM_DOCK_BOTTOM /
68
- * ZOOM_DOCK_RIGHT 也都从「内容边距」反推,改这两个值不会让转盘位置偏移。
173
+ * 绘制坐标由 renderer 按同样的余量补偿,视觉不变;下面的 dock 位置也都从「内容边距」反推,
174
+ * 改这两个值不会让转盘位置偏移。
69
175
  */
70
176
  export declare const HIT_PADDING_X = 48;
71
177
  export declare const HIT_PADDING_Y = 56;
72
178
  /**
73
- * 竖屏 dock 高度:必须 >= canvas 实际高度(椭圆截面 + 上下触摸余量),
74
- * 否则承载它的 CoverView 会把转盘裁掉一截。随 CROP_RATIO_BOTTOM / HIT_PADDING_Y 自动跟随,留 8px 余量。
179
+ * 竖屏底部工具条高度的兜底值(px):实测不到时用它。
180
+ * 对应 ui.tsx `.ipc-player-bottom-content` 竖屏分支的 `height: '48px'`;
181
+ * `-full` 变体还叠了 safe-area 的 padding,所以正常路径走实测(measureBottomBarHeight)。
75
182
  */
76
- export declare const ZOOM_DOCK_HEIGHT: number;
183
+ export declare const BOTTOM_BAR_HEIGHT_VERTICAL = 48;
77
184
  /**
78
- * 竖屏 dock 距播放器底边的距离,固定 0(贴底,不再探出)。
185
+ * 竖屏 dock 距播放器底边的距离(px 字符串)。
79
186
  *
80
- * 这里曾用负值让浮层整体下沉、把椭圆下半探出播放器底边。副作用是 CoverView 顶边
81
- * 跟着被压低,canvas 顶部(含刻度线那一带)露在 CoverView 之外被裁掉 —— 原生层裁掉的
82
- * 部分收不到 touch,表现就是「只有刻度尺下方能滑,上半部分直接拖到播放器」。
83
- * 实测:dock 可命中高度只有 155,而 canvas 287.5,顶部溢出 132.5。
84
- *
85
- * 现在改由 canvas 自己用负 margin 向下探出(见 ZOOM_CANVAS_OVERSHOOT_BOTTOM),
86
- * 浮层顶边因此上移,canvas 完整落在可命中区内,而内容视觉位置不变。
187
+ * - 竖屏全屏:**正值**,抬起一整条底部工具条的高度(优先用实测值,避免与 ui.tsx 写死的
188
+ * 48px 以及 `-full` 变体的 safe-area padding 不同步),于是 canvas 底边 = 工具条顶边;
189
+ * - 普通竖屏:**负值**,下沉一份底部触摸余量,让绘制内容底边正好落在播放器底边上。
190
+ * (负 bottom 是生效的;canvas 的负 marginBottom 才是无效的那个,别再往回改。)
87
191
  */
88
- export declare const ZOOM_DOCK_BOTTOM = 0;
192
+ export declare const getZoomDockBottom: (isVerticalFull: boolean, measuredBarHeight?: number | null) => string;
89
193
  /**
90
- * 竖屏 canvas 底边探出播放器底边的距离。
91
- * canvas 在 dock 内底部对齐,靠这个负 margin 下移,使绘制内容仍然铺到播放器底边:
92
- * 内容底探出 = ZOOM_CANVAS_OVERSHOOT_BOTTOM - HIT_PADDING_Y
93
- * 探出去的部分由 .ipc-player-content 的 overflow: hidden 裁掉,与改动前的观感一致。
194
+ * 竖屏 dock 高度(px 字符串):必须 >= canvas 高度,否则承载它的 CoverView 会把转盘裁掉一截,
195
+ * 被裁掉的部分收不到 touch(「只有刻度尺下方能滑」/「手势完全不生效」都是它)。
196
+ * 普通竖屏还要把下沉的那一份底部余量算进来,所以 canvas 高本身就带两份余量。
94
197
  */
95
- export declare const ZOOM_CANVAS_OVERSHOOT_BOTTOM: number;
198
+ export declare const getZoomDockHeight: (isVerticalFull: boolean) => string;
199
+ /** 竖屏 canvas / dock 的 px 尺寸,仅用于布局诊断日志与文档核对 */
200
+ export declare const getZoomLayoutDebugPx: (isVerticalFull: boolean) => {
201
+ 可见高: number;
202
+ canvas高: number;
203
+ dock高: number;
204
+ };
96
205
  /**
97
206
  * 拖动转盘时连续下发缩放的节流间隔(ms)。
98
207
  * 转盘每跨一个 step 就回调一次,不节流会把 setScaleMultiple 打爆;
99
208
  * 松手时 applyZoom 还会按最终值精确下发一次,所以这里丢掉尾值也没关系。
100
209
  */
101
210
  export declare const ZOOM_PREVIEW_THROTTLE = 80;
102
- /** 横屏 dock 距播放器右边的距离,负值时向右探出 */
103
- export declare const ZOOM_DOCK_RIGHT: number;
211
+ /**
212
+ * 横屏 dock 距播放器右边的距离(px 字符串,负值向右探出)。
213
+ *
214
+ * 之前写的是裸数字、被当 rpx 解释,而横屏的 rpx 基准是屏幕**长边**、机型间差 150px 以上,
215
+ * 换算下来内容内缩量在 3.8~12.3px 之间飘(窄屏留明显缝、宽屏几乎贴边,
216
+ * 就是「有些机型右边有 1px 空白」的来源)。写成 px 之后各机型恒为 14px。
217
+ *
218
+ * 内容右边内缩 = 本函数的 px 值 + HIT_PADDING_Y = ZOOM_CONTENT_RIGHT_INSET = 14px
219
+ */
220
+ export declare const getZoomDockRight: () => string;
104
221
  /** 横屏截取比例(右侧展示空间较窄,截得更深) */
105
222
  export declare const CROP_RATIO_RIGHT = 0.45;
223
+ /**
224
+ * 横屏刻度与文案的整体下移量(角度不变,仅平移绘制坐标系)。
225
+ * 竖屏不用它(截面已精确等于可见区,再平移会把底边裁掉一截),固定传 0。
226
+ */
227
+ export declare const CONTENT_OFFSET_Y = 10;
106
228
  /** 布局沉降兜底:onShow / onResize 后的重挂载延时 */
107
229
  export declare const RELAYOUT_DELAYS: number[];
108
- /** 刻度与文案整体下移量(角度不变,仅平移绘制坐标系) */
109
- export declare const CONTENT_OFFSET_Y = 10;
110
- /** 刻度与标记值向椭圆内侧收的距离,加大与遮罩边缘的间距 */
111
- export declare const TRACK_EDGE_INSET = 10;
112
- /** 竖屏样式微调 */
113
- export declare const STYLE_OPTIONS_BOTTOM: ZoomTurntableStyleOptions;
230
+ /**
231
+ * 竖屏样式微调。纵向几何跟着容器 16:9 推导,所以是函数而不是常量对象。
232
+ *
233
+ * `contentOffsetY: 0`:椭圆截面已经精确等于可见高度,再整体下移会把底边那一截裁掉。
234
+ *
235
+ * 底部触摸余量按布局给:竖屏全屏传 0(canvas 底边就是工具条顶边,再留余量会盖住按钮
236
+ * 并吃掉它们的点击);普通竖屏工具条已藏,保持上下对称、由 dock 的负 bottom 沉出去。
237
+ * (早先无条件设 0 时手势整体失灵,根因不在它,而是 rjs/index.js 漏读 ellipseRx/ellipseRy
238
+ * + absolutePosition 的 rpx 单位,两处都已修。)
239
+ */
240
+ export declare const getStyleOptionsBottom: (isVerticalFull: boolean) => ZoomTurntableStyleOptions;
114
241
  /** 横屏样式微调:压小刻度、文字与指针,避免与右侧控件重叠 */
115
242
  export declare const STYLE_OPTIONS_RIGHT: ZoomTurntableStyleOptions;
116
243
  /**
117
- * 普通刻度震动节流(ms)。step=0.1 时整个区间有几十级,
118
- * 不节流会震得过密,反而糊成一片没有颗粒感。
244
+ * 普通刻度震动的最小间隔(ms)。
245
+ *
246
+ * 60ms 太密:拖得稍快时 onChange 就是逐帧回调,等于每秒震十几下,马达来不及回位,
247
+ * 手上是一片持续嗡鸣而不是逐格咔哒。取 110ms 接近人手能分辨两次震动的下限,
248
+ * 也和连续下发的节流(80ms)拉开,不至于两个节奏互相打架。
249
+ */
250
+ export declare const VIBRATE_THROTTLE = 110;
251
+ /**
252
+ * 判定「拖得很快」的跨格数:一次 onChange 跨过的刻度数超过它,就不再逐格震。
253
+ *
254
+ * 快速甩动时用户根本分辨不出逐格反馈,继续震只会糊成一片;这时只保留跨主刻度的
255
+ * medium 挡位感,反而更清楚自己甩过了哪几档。
256
+ */
257
+ export declare const VIBRATE_FAST_STEPS = 3;
258
+ /**
259
+ * 内部调试开关:置 true 时忽略「只提示一次」的本地记录,每次出流都展示圈选引导,
260
+ * 点「知道了」也不写入已读标记 —— 便于反复核对引导的位置与样式。
261
+ *
262
+ * 只在自测引导样式时临时打开,**提交前必须回到 false**。
263
+ * 与 useLensZoom 里的 USE_MOCK_ZOOM_SKILL 是两个独立开关:
264
+ * 设备没有 ipc_multi_zoom 时入口本身就不渲染,引导自然也不会出现,那种情况要一起打开。
265
+ */
266
+ export declare const FORCE_SHOW_ZOOM_TIP = false;
267
+ /**
268
+ * 圈选提示:圈选框在入口 rect 四周额外留的内边距。
269
+ * 入口是横向的倍率胶囊(宽度随镜头数变化),圈选框按实测 rect 自适应,
270
+ * 不能用固定尺寸 —— 固定值既框不住宽度,上下又会超出。
271
+ */
272
+ export declare const TIP_RING_PADDING = 6;
273
+ /** 圈选提示:圈选框圆角,与 multiCamera 的 tile-tip 保持一致 */
274
+ export declare const TIP_RING_RADIUS = 12;
275
+ /** 圈选提示:引线总长(虚线 17 + 末端圆点 4),比 multiCamera 短,文案更贴近圈选处 */
276
+ export declare const TIP_LINE_TOTAL = 21;
277
+ /** 圈选提示:文案 + 按钮的面板宽度与距播放器边缘的最小间距 */
278
+ export declare const TIP_PANEL_WIDTH = 220;
279
+ export declare const TIP_PANEL_EDGE_GAP = 16;
280
+ /**
281
+ * 圈选提示:出流后延迟多久再测量并展示(ms)。
282
+ * 刚 PLAYING 时左下角元素还在陆续渲染、宽度还在长,等布局沉降再画圈才不会歪;
283
+ * 也顺带避免重连一恢复引导就弹出来。
119
284
  */
120
- export declare const VIBRATE_THROTTLE = 60;
121
- /** 圈选提示:圆环尺寸 */
122
- export declare const TIP_RING_SIZE = 44;
285
+ export declare const TIP_RESHOW_DELAY = 500;
286
+ /** 圈选提示:引线末端到文案面板的间距 */
287
+ export declare const TIP_PANEL_LINE_GAP = 8;
123
288
  /** 可视区判定:避开左下角滚动容器右侧淡出带的安全距离 */
124
289
  export declare const FADE_SAFE_PADDING = 24;