@ray-js/ipc-player-integration 0.0.35-beta.8 → 0.0.35

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/ctx/ctx.js CHANGED
@@ -205,7 +205,6 @@ export const createUseCtx = _ref => {
205
205
  fail: err => {
206
206
  IPCPlayerInstance.current.isMuted({
207
207
  success(res) {
208
- console.log('==== 当前静音状态 ====', res);
209
208
  updateAtom(mute, res);
210
209
  }
211
210
  });
@@ -24,7 +24,7 @@ export async function initPlayerWidgets(ctx, options) {
24
24
  var _options$hideSmartIma;
25
25
  // @ts-ignore
26
26
  newDefaultBottomLeftContent[tryExperienceIndex].initProps = {
27
- hideTryExperienceMenu: (_options$hideSmartIma = options.hideSmartImageQualityState) !== null && _options$hideSmartIma !== void 0 ? _options$hideSmartIma : true
27
+ hideSmartImageQualityState: (_options$hideSmartIma = options.hideSmartImageQualityState) !== null && _options$hideSmartIma !== void 0 ? _options$hideSmartIma : true
28
28
  };
29
29
  }
30
30
  if (resolutionIndex !== -1) {
@@ -33,6 +33,7 @@ declare const Strings: kit.I18N<{
33
33
  ipc_player_trial_preview_text: string;
34
34
  ipc_player_trial_subscribe: string;
35
35
  ipc_player_fetch_error: string;
36
+ ipc_player_low_phone_not_support: string;
36
37
  };
37
38
  zh: {
38
39
  ipc_player_resolution_HD: string;
@@ -67,6 +68,7 @@ declare const Strings: kit.I18N<{
67
68
  ipc_player_trial_preview_text: string;
68
69
  ipc_player_trial_subscribe: string;
69
70
  ipc_player_fetch_error: string;
71
+ ipc_player_low_phone_not_support: string;
70
72
  };
71
73
  }, {
72
74
  ipc_player_resolution_HD: string;
@@ -101,6 +103,7 @@ declare const Strings: kit.I18N<{
101
103
  ipc_player_trial_preview_text: string;
102
104
  ipc_player_trial_subscribe: string;
103
105
  ipc_player_fetch_error: string;
106
+ ipc_player_low_phone_not_support: string;
104
107
  } | {
105
108
  ipc_player_resolution_HD: string;
106
109
  ipc_player_resolution_SD: string;
@@ -134,5 +137,6 @@ declare const Strings: kit.I18N<{
134
137
  ipc_player_trial_preview_text: string;
135
138
  ipc_player_trial_subscribe: string;
136
139
  ipc_player_fetch_error: string;
140
+ ipc_player_low_phone_not_support: string;
137
141
  }>;
138
142
  export default Strings;
@@ -32,6 +32,7 @@ declare const _default: {
32
32
  ipc_player_trial_preview_text: string;
33
33
  ipc_player_trial_subscribe: string;
34
34
  ipc_player_fetch_error: string;
35
+ ipc_player_low_phone_not_support: string;
35
36
  };
36
37
  zh: {
37
38
  ipc_player_resolution_HD: string;
@@ -66,6 +67,7 @@ declare const _default: {
66
67
  ipc_player_trial_preview_text: string;
67
68
  ipc_player_trial_subscribe: string;
68
69
  ipc_player_fetch_error: string;
70
+ ipc_player_low_phone_not_support: string;
69
71
  };
70
72
  };
71
73
  export default _default;
@@ -31,7 +31,8 @@ export default {
31
31
  ipc_player_trial_in_use: 'Trial',
32
32
  ipc_player_trial_preview_text: 'End',
33
33
  ipc_player_trial_subscribe: 'Open',
34
- ipc_player_fetch_error: 'Failed to fetch data'
34
+ ipc_player_fetch_error: 'Failed to fetch data',
35
+ ipc_player_low_phone_not_support: 'This feature is not supported on this device'
35
36
  },
36
37
  zh: {
37
38
  ipc_player_resolution_HD: '高清',
@@ -65,6 +66,7 @@ export default {
65
66
  ipc_player_trial_in_use: '试用中',
66
67
  ipc_player_trial_preview_text: '后结束',
67
68
  ipc_player_trial_subscribe: '开通',
68
- ipc_player_fetch_error: '获取异常'
69
+ ipc_player_fetch_error: '获取异常',
70
+ ipc_player_low_phone_not_support: '当前设备不支持此功能'
69
71
  }
70
72
  };
Binary file
@@ -3,8 +3,19 @@ import { UseCtx } from '../interface';
3
3
  type Props = {
4
4
  ctx: ReturnType<UseCtx>;
5
5
  children: React.ReactNode;
6
- /** 右下角组件占据的宽度(px),左下角通过该值约束自身可视宽度,避免与右下角重叠 */
7
- reservedRight?: number;
6
+ /**
7
+ * 右下角组件占据的宽度,左下角通过该值约束自身可视宽度避免与右下角重叠。
8
+ * - number: 视为 px
9
+ * - string: 视为合法的 CSS 长度(例如 "35%"),用于横屏按百分比预留
10
+ */
11
+ reservedRight?: number | string;
12
+ canOpenSettings?: boolean;
13
+ /**
14
+ * 横滑提示箭头的交互模式:
15
+ * - 'button': 白色小圆底 + 黑色箭头,溢出后常显、可点击滑动(不随拖动消失)
16
+ * - 'pulse' : 旧版渐隐渐显提示箭头,拖动一次后当前屏幕模式不再展示
17
+ */
18
+ scrollHintMode?: 'button' | 'pulse';
8
19
  };
9
- declare const BottomLeftContent: ({ ctx, children, reservedRight }: Props) => React.JSX.Element;
20
+ declare const BottomLeftContent: ({ ctx, children, reservedRight, canOpenSettings, scrollHintMode, }: Props) => React.JSX.Element;
10
21
  export default BottomLeftContent;
@@ -1,44 +1,27 @@
1
1
  import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
2
2
  import "core-js/modules/esnext.iterator.constructor.js";
3
3
  import "core-js/modules/esnext.iterator.find.js";
4
- import React, { useMemo } from 'react';
5
- import { CoverView, View } from '@ray-js/ray';
4
+ import React, { useEffect, useMemo, useRef, useState } from 'react';
5
+ import { CoverView, ScrollView, View } from '@ray-js/ray';
6
6
  import clsx from 'clsx';
7
7
  import { useStore } from '../ctx/store';
8
8
  import { useComponentHideState } from './hooks';
9
9
  import { TrialBadge, useTrialBadge } from '../widgets/trialBadge';
10
- /** 右侧透明渐变宽度,用于提示横向可滑动 */
11
- const FADE_MASK = 'linear-gradient(to right, #000 calc(100% - 16px), transparent 100%)';
10
+ import { pauseTimeToHideAllComponent, startTimeToHideAllComponent } from './constant';
12
11
 
13
- /**
14
- * 试用徽章在不同 screenType 下的容器样式。
15
- * 「贴在底栏顶部」的策略一致(bottom: 100%),只是 left / marginBottom
16
- * 等数值按横竖屏不同的边距规则做差异化处理。
17
- */
18
- const TRIAL_BADGE_WRAP_STYLE_VERTICAL = {
19
- position: 'absolute',
20
- left: '0px',
21
- bottom: '100%',
22
- marginBottom: 4,
23
- zIndex: 2,
24
- width: 'max-content'
25
- };
26
- const TRIAL_BADGE_WRAP_STYLE_FULL = {
27
- position: 'absolute',
28
- left: '25px',
29
- bottom: '100%',
30
- marginBottom: 8,
31
- zIndex: 2,
32
- width: 'max-content'
33
- };
12
+ // 用唯一 className 在 createSelectorQuery 中区分多个实例,避免互相干扰
13
+ let scrollClsSeq = 0;
34
14
  const BottomLeftContent = _ref => {
35
15
  let {
36
16
  ctx,
37
17
  children,
38
- reservedRight = 0
18
+ reservedRight = 0,
19
+ canOpenSettings = true,
20
+ scrollHintMode = 'button'
39
21
  } = _ref;
40
22
  const {
41
23
  screenType,
24
+ playState,
42
25
  brandColor,
43
26
  bottomLeftContent
44
27
  } = useStore({
@@ -50,17 +33,27 @@ const BottomLeftContent = _ref => {
50
33
  const showSmartImageQuality = useMemo(() => {
51
34
  var _tryExp$initProps;
52
35
  const tryExp = bottomLeftContent === null || bottomLeftContent === void 0 ? void 0 : bottomLeftContent.find(item => item.id === 'TryExperience');
53
- return !(tryExp !== null && tryExp !== void 0 && (_tryExp$initProps = tryExp.initProps) !== null && _tryExp$initProps !== void 0 && _tryExp$initProps.hideTryExperienceMenu);
36
+ return !(tryExp !== null && tryExp !== void 0 && (_tryExp$initProps = tryExp.initProps) !== null && _tryExp$initProps !== void 0 && _tryExp$initProps.hideSmartImageQualityState);
54
37
  }, [bottomLeftContent]);
55
38
  const trialRemainingSec = useMemo(() => {
56
39
  var _trialRemainingSec, _tryExp$initProps2;
57
40
  const tryExp = bottomLeftContent === null || bottomLeftContent === void 0 ? void 0 : bottomLeftContent.find(item => item.id === 'TryExperience');
58
41
  return (_trialRemainingSec = tryExp === null || tryExp === void 0 || (_tryExp$initProps2 = tryExp.initProps) === null || _tryExp$initProps2 === void 0 ? void 0 : _tryExp$initProps2.trialRemainingSec) !== null && _trialRemainingSec !== void 0 ? _trialRemainingSec : 0;
59
42
  }, [bottomLeftContent]);
60
- const refreshToken = useMemo(() => {
61
- var _refreshToken, _tryExp$initProps3;
43
+ const buttonState = useMemo(() => {
44
+ var _tryExp$initProps3;
45
+ const tryExp = bottomLeftContent === null || bottomLeftContent === void 0 ? void 0 : bottomLeftContent.find(item => item.id === 'TryExperience');
46
+ return tryExp === null || tryExp === void 0 || (_tryExp$initProps3 = tryExp.initProps) === null || _tryExp$initProps3 === void 0 ? void 0 : _tryExp$initProps3.buttonState;
47
+ }, [bottomLeftContent]);
48
+ const isLowPhone = useMemo(() => {
49
+ var _isLowPhone, _tryExp$initProps4;
50
+ const tryExp = bottomLeftContent === null || bottomLeftContent === void 0 ? void 0 : bottomLeftContent.find(item => item.id === 'TryExperience');
51
+ return (_isLowPhone = tryExp === null || tryExp === void 0 || (_tryExp$initProps4 = tryExp.initProps) === null || _tryExp$initProps4 === void 0 ? void 0 : _tryExp$initProps4.isLowPhone) !== null && _isLowPhone !== void 0 ? _isLowPhone : false;
52
+ }, []);
53
+ const gRawData = useMemo(() => {
54
+ var _gRawData, _tryExp$initProps5;
62
55
  const tryExp = bottomLeftContent === null || bottomLeftContent === void 0 ? void 0 : bottomLeftContent.find(item => item.id === 'TryExperience');
63
- return (_refreshToken = tryExp === null || tryExp === void 0 || (_tryExp$initProps3 = tryExp.initProps) === null || _tryExp$initProps3 === void 0 ? void 0 : _tryExp$initProps3.refreshToken) !== null && _refreshToken !== void 0 ? _refreshToken : 0;
56
+ return (_gRawData = tryExp === null || tryExp === void 0 || (_tryExp$initProps5 = tryExp.initProps) === null || _tryExp$initProps5 === void 0 ? void 0 : _tryExp$initProps5.gRawData) !== null && _gRawData !== void 0 ? _gRawData : false;
64
57
  }, [bottomLeftContent]);
65
58
  const [shouldHide] = useComponentHideState();
66
59
  const {
@@ -68,47 +61,272 @@ const BottomLeftContent = _ref => {
68
61
  handleTrialSubscribe,
69
62
  handleCountdownEnd
70
63
  } = useTrialBadge(ctx.event, ctx.devId, trialRemainingSec);
64
+
65
+ // 横向滚动状态:内容溢出且未滑到对应边缘时,显示左 / 右箭头提示
66
+ const idRef = useRef(++scrollClsSeq);
67
+ const scrollCls = `ipc-bl-scroll-${idRef.current}`;
68
+ const contentCls = `ipc-bl-scroll-content-${idRef.current}`;
69
+ // 内容末尾哨兵节点:用它的真实横坐标反推子项总宽,避免内容容器被撑满导致测不出溢出
70
+ const endCls = `ipc-bl-scroll-end-${idRef.current}`;
71
+ const [scrollLeft, setScrollLeft] = useState(0);
72
+ const [clientWidth, setClientWidth] = useState(0);
73
+ const [contentWidth, setContentWidth] = useState(0);
74
+ // 点击箭头时的受控滚动目标;仅在程序化滚动期间为数值,结束后置回 undefined,
75
+ // 避免持续受控导致用户手动拖动被回弹
76
+ const [scrollTo, setScrollTo] = useState(undefined);
77
+ // 横竖屏切换后,强制 ScrollView 重新挂载并把滚动状态重置为初始值
78
+ const [scrollResetKey, setScrollResetKey] = useState(0);
79
+ // 用户在某种屏幕模式下拖动过一次之后,该模式不再展示箭头(竖屏 / 横屏各自独立记录)
80
+ const [dismissed, setDismissed] = useState({
81
+ vertical: false,
82
+ full: false
83
+ });
84
+
85
+ // 滑动期间暂停整体的自动隐藏,滑动停止后再恢复(无原生 scrollend,用防抖判定结束)
86
+ const scrollingRef = useRef(false);
87
+ const scrollEndTimerRef = useRef();
88
+ // 测量时需要叠加当前滚动量,用 ref 避免闭包拿到过期值
89
+ const scrollLeftRef = useRef(0);
90
+ useEffect(() => {
91
+ setScrollResetKey(k => k + 1);
92
+ setScrollLeft(0);
93
+ setScrollTo(undefined);
94
+ }, [screenType]);
95
+
96
+ // 程序化滚动目标只在动画期间短暂受控,之后释放为非受控,避免回弹影响手动拖动
97
+ useEffect(() => {
98
+ if (scrollTo === undefined) {
99
+ return undefined;
100
+ }
101
+ const t = setTimeout(() => setScrollTo(undefined), 600);
102
+ return () => clearTimeout(t);
103
+ }, [scrollTo]);
71
104
  const paddingLeftPx = screenType === 'vertical' ? 0 : 25;
72
- const trialBadgeWrapStyle = screenType === 'vertical' ? TRIAL_BADGE_WRAP_STYLE_VERTICAL : TRIAL_BADGE_WRAP_STYLE_FULL;
105
+ // 滚动末端的安全距离:滑到最右时最后一个元素与右边缘留更宽松的间距(横屏更宽)
106
+ const paddingRightPx = screenType === 'vertical' ? 22 : 32;
107
+ const paddingBottomPx = screenType === 'vertical' ? 0 : 25;
108
+ const showBadge = showSmartImageQuality && showTrialBadge && canOpenSettings && gRawData && !isLowPhone && buttonState === 1 && trialRemainingSec > 0;
109
+
110
+ // 测量滚动容器可视宽度与内部内容宽度。
111
+ // 首屏漏判溢出的根因:左下角子元素(画质菜单 / 试用角标等)是异步加载的,宽度会在
112
+ // 首次测量之后才“长大”,而固定几次延时跑完就不再测,导致 contentWidth 偏小、判不出溢出。
113
+ // 这里在一段时间窗内持续轮询(每 400ms 测一次,约 6s),每次都用最新读数更新;
114
+ // 值未变化时 React 会自动跳过重渲染,因此持续轮询不会带来额外开销,
115
+ // 同时能兜住异步内容撑开与 overlay 布局滞后导致的尺寸变化。
116
+ useEffect(() => {
117
+ let timer;
118
+ let attempts = 0;
119
+ const measure = () => {
120
+ const query = ty.createSelectorQuery();
121
+ query.select(`.${scrollCls}`).boundingClientRect();
122
+ query.select(`.${contentCls}`).boundingClientRect();
123
+ query.select(`.${endCls}`).boundingClientRect();
124
+ query.exec(res => {
125
+ const scrollRect = res === null || res === void 0 ? void 0 : res[0];
126
+ const contentRect = res === null || res === void 0 ? void 0 : res[1];
127
+ const endRect = res === null || res === void 0 ? void 0 : res[2];
128
+ const cw = (scrollRect === null || scrollRect === void 0 ? void 0 : scrollRect.width) || 0;
129
+ if (cw) {
130
+ setClientWidth(cw);
131
+ }
132
+
133
+ // 优先用末尾哨兵反推子项真实总宽:
134
+ // 哨兵视口 x = 滚动容器左 + 左padding + 子项总宽 - 当前 scrollLeft
135
+ // => 子项总宽 = 哨兵.left - 滚动容器.left - 左padding + scrollLeft
136
+ // 这样即使内容容器被撑满到可视内盒宽,也能拿到真实内容宽;哨兵不可用时回退到容器宽。
137
+ let tw = 0;
138
+ if (scrollRect && endRect && typeof endRect.left === 'number') {
139
+ tw = endRect.left - scrollRect.left - paddingLeftPx + scrollLeftRef.current;
140
+ }
141
+ if (!(tw > 0)) {
142
+ tw = (contentRect === null || contentRect === void 0 ? void 0 : contentRect.width) || 0;
143
+ }
144
+ if (tw > 0) {
145
+ setContentWidth(tw);
146
+ }
147
+ attempts += 1;
148
+ // 最多轮询 15 次(约 6s)兜底,避免无限轮询
149
+ if (attempts >= 15) {
150
+ return;
151
+ }
152
+ timer = setTimeout(measure, 400);
153
+ });
154
+ };
155
+ timer = setTimeout(measure, 200);
156
+ return () => {
157
+ if (timer) clearTimeout(timer);
158
+ };
159
+ }, [screenType,
160
+ // reservedRight 决定 ScrollView 可视宽度(右下角内容异步加载后会从 0 变为实际宽度)
161
+ reservedRight,
162
+ // playState:视频就绪后 overlay 可能重新布局,需重新测量
163
+ playState, bottomLeftContent === null || bottomLeftContent === void 0 ? void 0 : bottomLeftContent.length, shouldHide, showBadge, scrollCls, contentCls, scrollResetKey]);
164
+ // 卸载时清理防抖定时器;若仍处于滑动中则恢复自动隐藏,避免遗留暂停态
165
+ useEffect(() => {
166
+ return () => {
167
+ if (scrollEndTimerRef.current) {
168
+ clearTimeout(scrollEndTimerRef.current);
169
+ }
170
+ if (scrollingRef.current) {
171
+ var _ctx$event3;
172
+ scrollingRef.current = false;
173
+ (_ctx$event3 = ctx.event) === null || _ctx$event3 === void 0 || _ctx$event3.emit(startTimeToHideAllComponent);
174
+ }
175
+ };
176
+ }, [ctx.event]);
177
+
178
+ // ScrollView 的实际可滚动宽度 = 左右 padding + 子元素总宽
179
+ const totalContentWidth = contentWidth > 0 ? paddingLeftPx + contentWidth + paddingRightPx : 0;
180
+ const isOverflow = totalContentWidth > clientWidth + 1;
181
+ const atEnd = !isOverflow || scrollLeft + clientWidth >= totalContentWidth - 2;
182
+ const isDismissed = dismissed[screenType];
183
+ const isButtonHint = scrollHintMode === 'button';
184
+ // button 模式:溢出且未到末端即常显(不受 dismissed 影响);pulse 模式:保留拖动后消失的旧逻辑
185
+
186
+ // 右侧还有未滚到头的内容时,启用右边缘淡出,把没完全露出的元素渐隐隐藏(横竖屏都生效)
187
+
188
+ // 点击右箭头:直接滚动到最右端。
189
+ // 给一个足够大的目标值,交由 ScrollView 自身夹取到真实最右端,避免测量误差导致滚不到底。
190
+
191
+ const coverHeight = screenType === 'vertical' ? shouldHide ? 49 : 48 : shouldHide ? 84 : 83;
192
+ const containerHeight = screenType === 'vertical' ? 48 : 58;
193
+
194
+ // reservedRight 兼容 number(px)与 string(任意 CSS 长度,例如 "35%")
195
+ const rightCss = typeof reservedRight === 'number' ? `${reservedRight}px` : reservedRight;
196
+
197
+ // 与 bottomRightContent 对齐:bottomRight 的 icon 行是贴在 wrap 顶部(不居中)的,
198
+ // 这里 wrap 用 flex-start,让 ScrollView 也贴顶;icon 在 ScrollView 内通过 align-items
199
+ // 居中到可视行中线,从而和右侧 icon 在同一水平线上。
200
+ // hintBottom 锚定 wrap 底部:
201
+ // icon 中心到 wrap 底部 = (wrap底部→SV底部空隙) + SV的paddingBottom + 可视行高/2
202
+ // 竖屏: 0 + 0 + 24 = 24,箭头 16 → bottom = 16
203
+ // 横屏: 25 + 25 + 16.5 = 66.5,箭头 16 → bottom = 58
204
+
205
+ const hintBottom = screenType === 'vertical' ? 16 : 58;
206
+ // button 模式用半透明圆做点击热区,比 pulse 提示大;下移半个差值以保持图标中心不变
207
+ const hintSize = isButtonHint ? 26 : 16;
208
+ // 竖屏 button 模式给 ScrollView 右侧留一条较窄的“箭头槽”:只裁掉内容最右一小段,
209
+ // 箭头压住最后一个元素的少量边缘(不完全遮挡),整体更贴近内容
210
+ const arrowGutter = isButtonHint && screenType === 'vertical' ? 12 : 0;
211
+ // 箭头贴右边缘放置(竖屏 4px / 横屏 6px),不再随 gutter 居中
212
+ const hintRight = arrowGutter > 0 ? 4 : 6;
73
213
  return /*#__PURE__*/React.createElement(CoverView, {
74
- className: clsx('ipc-player-bottom-left-content-wrap')
75
- // 注意:不要在这里覆盖 position,CSS 类已经设置 position: absolute; left: 0; bottom: 0
76
- // 否则 children 会失去贴底定位,从而与 bottomRightContent 不对齐
77
- ,
214
+ className: clsx('ipc-player-bottom-left-content-wrap'),
78
215
  style: {
79
- right: `${reservedRight}px`,
80
- height: screenType === 'vertical' ? shouldHide ? '41px' : '40px' : shouldHide ? '57px' : '58px'
81
- }
82
- }, showSmartImageQuality && showTrialBadge && /*#__PURE__*/React.createElement(View, {
83
- className: clsx('bottom-left-item-container'),
84
- style: _objectSpread(_objectSpread({}, trialBadgeWrapStyle), {}, {
85
- transform: shouldHide ? 'translateY(40px)' : 'translateY(0)',
216
+ right: rightCss,
217
+ height: showBadge ? `${coverHeight + 30}px` : `${coverHeight}px`,
218
+ display: 'flex',
219
+ flexDirection: 'column',
220
+ justifyContent: 'flex-start',
86
221
  opacity: shouldHide ? 0 : 1,
87
- transition: 'transform 0.3s ease-in-out, opacity 0.3s ease-in-out'
88
- })
222
+ pointerEvents: shouldHide ? 'none' : 'auto'
223
+ }
224
+ }, showBadge && /*#__PURE__*/React.createElement(View, {
225
+ style: {
226
+ height: '32px',
227
+ marginLeft: screenType === 'vertical' ? '12px' : '25px'
228
+ }
89
229
  }, /*#__PURE__*/React.createElement(TrialBadge, {
90
230
  brandColor: brandColor,
91
231
  trialRemainingSec: trialRemainingSec,
92
- refreshToken: refreshToken,
93
232
  onSubscribe: handleTrialSubscribe,
94
233
  onCountdownEnd: handleCountdownEnd
95
- })), /*#__PURE__*/React.createElement(View, {
234
+ })), /*#__PURE__*/React.createElement(ScrollView, {
235
+ key: scrollResetKey,
236
+ scrollX: true,
237
+ scrollLeft: scrollTo,
238
+ scrollWithAnimation: true,
239
+ onScroll: e => {
240
+ var _e$detail;
241
+ // 滑动开始时暂停自动隐藏(仅在一次滑动会话内触发一次),滑动停止后恢复
242
+ if (!scrollingRef.current) {
243
+ var _ctx$event;
244
+ scrollingRef.current = true;
245
+ (_ctx$event = ctx.event) === null || _ctx$event === void 0 || _ctx$event.emit(pauseTimeToHideAllComponent);
246
+ }
247
+ if (scrollEndTimerRef.current) {
248
+ clearTimeout(scrollEndTimerRef.current);
249
+ }
250
+ scrollEndTimerRef.current = setTimeout(() => {
251
+ var _ctx$event2;
252
+ scrollingRef.current = false;
253
+ (_ctx$event2 = ctx.event) === null || _ctx$event2 === void 0 || _ctx$event2.emit(startTimeToHideAllComponent);
254
+ }, 200);
255
+
256
+ // Ray ScrollView 在不同实现下,scrollLeft 可能直接挂在事件上,也可能在 detail 内
257
+ const sl = typeof (e === null || e === void 0 ? void 0 : e.scrollLeft) === 'number' ? e.scrollLeft : e === null || e === void 0 || (_e$detail = e.detail) === null || _e$detail === void 0 ? void 0 : _e$detail.scrollLeft;
258
+ if (typeof sl === 'number') {
259
+ scrollLeftRef.current = sl;
260
+ setScrollLeft(sl);
261
+ // 一旦真的拖动过(>2px 阈值过滤抖动 / 重置触发的 0 值),当前屏幕模式下不再展示箭头
262
+ if (sl > 2 && !dismissed[screenType]) {
263
+ setDismissed(d => _objectSpread(_objectSpread({}, d), {}, {
264
+ [screenType]: true
265
+ }));
266
+ }
267
+ }
268
+ },
269
+ className: clsx(scrollCls, {
270
+ 'ipc-player-bottom-left-scroll-fademask': isOverflow && !atEnd
271
+ }),
96
272
  style: {
97
- paddingBottom: screenType === 'vertical' ? '14px' : '32px',
273
+ paddingBottom: `${paddingBottomPx}px`,
98
274
  paddingLeft: `${paddingLeftPx}px`,
99
- paddingRight: '16px',
100
- width: '100%',
101
- overflowX: 'auto',
102
- overflowY: 'hidden',
103
- whiteSpace: 'nowrap',
104
- WebkitOverflowScrolling: 'touch',
105
- WebkitMaskImage: FADE_MASK,
106
- maskImage: FADE_MASK
275
+ paddingRight: `${paddingRightPx}px`,
276
+ width: arrowGutter > 0 ? `calc(100% - ${arrowGutter}px)` : '100%',
277
+ height: `${containerHeight}px`,
278
+ marginTop: showBadge ? '-2px' : 0,
279
+ whiteSpace: 'nowrap'
280
+ }
281
+ }, /*#__PURE__*/React.createElement(View, {
282
+ className: clsx('ipc-player-bottom-left-content-container', contentCls),
283
+ style: {
284
+ display: 'inline-flex',
285
+ flexDirection: 'row',
286
+ height: `${containerHeight - paddingBottomPx}px`,
287
+ alignItems: 'center'
288
+ }
289
+ }, children, /*#__PURE__*/React.createElement(View, {
290
+ className: endCls,
291
+ style: {
292
+ width: '1px',
293
+ height: '1px',
294
+ flexShrink: 0
295
+ }
296
+ }))), isOverflow && !atEnd && (isButtonHint || !isDismissed) && /*#__PURE__*/React.createElement(CoverView, {
297
+ className: clsx('ipc-player-bottom-left-scroll-hint', {
298
+ 'ipc-player-bottom-left-scroll-hint--button': isButtonHint
299
+ }),
300
+ style: {
301
+ position: 'absolute',
302
+ right: `${hintRight}px`,
303
+ bottom: `${hintBottom - (hintSize - 16) / 2}px`,
304
+ width: `${hintSize}px`,
305
+ height: `${hintSize}px`,
306
+ display: 'flex',
307
+ alignItems: 'center',
308
+ justifyContent: 'center',
309
+ pointerEvents: isButtonHint ? 'auto' : 'none'
107
310
  },
108
- className: clsx('ipc-player-bottom-left-content-container', {
109
- // 'ipc-player-bottom-left-content-hide': shouldHide,
110
- // 'ipc-player-bottom-left-content-show': !shouldHide,
311
+ onClick: isButtonHint ? () => {
312
+ if (!isOverflow) {
313
+ return;
314
+ }
315
+ setScrollTo(totalContentWidth + clientWidth + 9999);
316
+ } : undefined
317
+ }, /*#__PURE__*/React.createElement(View, {
318
+ className: clsx('ipc-player-bottom-left-scroll-hint-arrow', {
319
+ 'ipc-player-bottom-left-scroll-hint-arrow--right': true,
320
+ 'ipc-player-bottom-left-scroll-hint-arrow--light': isButtonHint,
321
+ 'ipc-player-bottom-left-scroll-hint-arrow--static': isButtonHint
111
322
  })
112
- }, children));
323
+ // 右向 chevron 由 top+right 描边旋转而成,视觉重心偏右;放大并整体左移使其在圆内居中
324
+ ,
325
+ style: isButtonHint ? {
326
+ width: '9px',
327
+ height: '9px',
328
+ transform: 'translate(-1px, 0) rotate(45deg)'
329
+ } : undefined
330
+ })));
113
331
  };
114
332
  export default BottomLeftContent;
@@ -3,8 +3,12 @@ import { UseCtx } from '../interface';
3
3
  type Props = {
4
4
  ctx: ReturnType<UseCtx>;
5
5
  children: React.ReactNode;
6
- /** 由 ui.tsx 计算得到的右下角组件宽度(与左下角的 reservedRight 保持一致) */
7
- width?: number;
6
+ /**
7
+ * 由 ui.tsx 计算得到的右下角组件宽度(与左下角的 reservedRight 保持一致)
8
+ * - number: 视为 px
9
+ * - string: 视为合法的 CSS width 值(例如 "35%" / "20vw")
10
+ */
11
+ width?: number | string;
8
12
  };
9
13
  declare const BottomRightContent: ({ ctx, children, width }: Props) => React.JSX.Element;
10
14
  export default BottomRightContent;
@@ -17,20 +17,29 @@ const BottomRightContent = _ref => {
17
17
  playState: ctx.playState
18
18
  });
19
19
  const [shouldHide] = useComponentHideState();
20
+ const containerHeight = screenType === 'vertical' ? 48 : 58;
21
+
22
+ // 将 width 统一规范成 CSS 合法值。number → "${n}px",string → 原样透传。
23
+ // 0 / 空串视为「没有传宽度」,沿用默认布局(不设 width)。
24
+ const widthStyle = typeof width === 'number' && width > 0 ? {
25
+ width: `${width}px`,
26
+ boxSizing: 'border-box'
27
+ } : typeof width === 'string' && width ? {
28
+ width,
29
+ boxSizing: 'border-box'
30
+ } : null;
20
31
  return /*#__PURE__*/React.createElement(CoverView, {
21
32
  className: clsx('ipc-player-bottom-right-content-wrap'),
22
33
  style: _objectSpread({
23
34
  paddingLeft: screenType === 'vertical' ? '10px' : '0',
24
- height: screenType === 'vertical' ? shouldHide ? '41px' : '40px' : shouldHide ? '57px' : '58px'
25
- }, typeof width === 'number' && width > 0 ? {
26
- width: `${width}px`,
27
- boxSizing: 'border-box'
28
- } : null)
35
+ height: screenType === 'vertical' ? shouldHide ? '49px' : '48px' : shouldHide ? '84px' : '83px'
36
+ }, widthStyle)
29
37
  }, /*#__PURE__*/React.createElement(View, {
30
38
  style: {
31
- paddingBottom: screenType === 'vertical' ? '14px' : '32px',
39
+ paddingBottom: screenType === 'vertical' ? 0 : '25px',
32
40
  paddingRight: screenType === 'vertical' ? 0 : '25px',
33
41
  width: '100%',
42
+ height: `${containerHeight}px`,
34
43
  justifyContent: 'flex-end'
35
44
  },
36
45
  className: clsx('ipc-player-bottom-right-content-container', {
@@ -26,6 +26,8 @@ export declare const widgetClick = "widgetClick";
26
26
  * 业务侧/相关控件可监听此事件刷新自己的状态(例如 TryExperience 重新拉取状态)
27
27
  */
28
28
  export declare const trialCountdownEnd = "trialCountdownEnd";
29
+ export declare const hideTrialBadgeEvent = "hideTrialBadge";
30
+ export declare const refreshSmartImageQualityEvent = "refreshSmartImageQuality";
29
31
  /** 控件点击事件用的 widgetId,统一在此维护 */
30
32
  export declare const widgetLabs: {
31
33
  readonly SCREENSHOT: "Screenshot";
@@ -27,6 +27,8 @@ export const widgetClick = 'widgetClick';
27
27
  * 业务侧/相关控件可监听此事件刷新自己的状态(例如 TryExperience 重新拉取状态)
28
28
  */
29
29
  export const trialCountdownEnd = 'trialCountdownEnd';
30
+ export const hideTrialBadgeEvent = 'hideTrialBadge';
31
+ export const refreshSmartImageQualityEvent = 'refreshSmartImageQuality';
30
32
 
31
33
  /** 控件点击事件用的 widgetId,统一在此维护 */
32
34
  export const widgetLabs = {
package/lib/ui/ui.d.ts CHANGED
@@ -32,12 +32,13 @@ type Props = {
32
32
  awakeStatus?: boolean | undefined;
33
33
  eventRef?: React.RefObject<EventInstance>;
34
34
  onPlayerTap?: (data: any) => void;
35
- defaultAutoPlay?: boolean;
36
35
  limitFlow?: boolean;
37
36
  showFlowLowTip?: boolean;
38
37
  extend?: Record<string, any>;
39
38
  refreshElement?: boolean;
40
39
  autoWakeUp?: boolean;
40
+ previewEnabled?: boolean;
41
+ onPreviewEnabledChange?: (enabled: boolean) => void;
41
42
  };
42
43
  export declare const IPCPlayerIntegration: React.MemoExoticComponent<(props: Props) => React.JSX.Element>;
43
44
  export {};