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

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.
@@ -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 (!(isVertical && !isFlat && !isCompact) || measuredCount === 0) return 0;
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
- // 竖屏只有锁住宽度时箭头才有专属的露出区可压;否则箭头一定会盖到前 4 项上,索性不出
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
- // 锁宽档可视区只有“前 4 项 + 一角”,渐变区要跟着收窄,否则会把第 4 项也淡掉一大半
412
- 'ipc-player-bottom-left-scroll-fademask--narrow': fadeRightEdge && verticalScrollWidth > 0
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 || verticalScrollWidth > 0) && (isButtonHint || !isDismissed) && /*#__PURE__*/React.createElement(CoverView, {
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
- paddingLeft: screenType === 'vertical' ? '10px' : '0',
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
- const BOTTOM_RIGHT_SAFE_GAP = 12;
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 (bottomRightMeasuredWidth > 0 && (!maxReserved || bottomRightMeasuredWidth <= maxReserved)) {
810
- return Math.ceil(bottomRightMeasuredWidth) + BOTTOM_RIGHT_SAFE_GAP;
816
+ if (maxReserved && bottomRightMeasuredWidth > maxReserved) {
817
+ return bottomRightWidth;
811
818
  }
812
- return bottomRightWidth;
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ray-js/ipc-player-integration",
3
- "version": "0.0.50-beta.5",
3
+ "version": "0.0.50",
4
4
  "description": "IPC 融合播放器",
5
5
  "main": "lib/index",
6
6
  "files": [