@ray-js/ipc-player-integration 0.0.35-beta.3 → 0.0.35-beta.31
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 +0 -1
- package/lib/features/initPlayerWidgets/index.js +1 -2
- package/lib/i18n/index.d.ts +4 -0
- package/lib/i18n/strings.d.ts +2 -0
- package/lib/i18n/strings.js +4 -2
- package/lib/res/try/try_close.png +0 -0
- package/lib/ui/bottomLeftContent.d.ts +14 -3
- package/lib/ui/bottomLeftContent.js +277 -58
- package/lib/ui/bottomRightContent.d.ts +6 -2
- package/lib/ui/bottomRightContent.js +15 -6
- package/lib/ui/constant.d.ts +2 -2
- package/lib/ui/constant.js +2 -3
- package/lib/ui/index.d.ts +1 -1
- package/lib/ui/index.js +1 -1
- package/lib/ui/ui.d.ts +2 -1
- package/lib/ui/ui.js +147 -73
- package/lib/ui/ui.less +70 -6
- package/lib/utils/index.d.ts +0 -1
- package/lib/utils/index.js +0 -10
- package/lib/utils/navigation.d.ts +1 -0
- package/lib/utils/navigation.js +5 -2
- package/lib/utils/ttt.d.ts +7 -5
- package/lib/utils/ttt.js +132 -74
- package/lib/widgets/trialBadge/index.d.ts +1 -3
- package/lib/widgets/trialBadge/index.js +20 -22
- package/lib/widgets/trialBadge/index.less +17 -6
- package/lib/widgets/trialBadge/useTrialBadge.js +24 -15
- package/lib/widgets/tryExperience/tryExperience.d.ts +6 -1
- package/lib/widgets/tryExperience/tryExperience.js +111 -15
- package/package.json +3 -3
package/lib/ctx/ctx.js
CHANGED
|
@@ -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
|
-
|
|
27
|
+
hideSmartImageQualityState: (_options$hideSmartIma = options.hideSmartImageQualityState) !== null && _options$hideSmartIma !== void 0 ? _options$hideSmartIma : true
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
if (resolutionIndex !== -1) {
|
|
@@ -52,7 +52,6 @@ export async function initPlayerWidgets(ctx, options) {
|
|
|
52
52
|
newDefaultBottomLeftContent[magnificationIndex].hidden = false;
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
-
console.log('res===1', newDefaultBottomLeftContent);
|
|
56
55
|
ctx.addContent('bottomLeft', newDefaultBottomLeftContent);
|
|
57
56
|
const newDefaultBottomRightContent = _cloneDeep(options.bottomRightContent || defaultBottomRightContent);
|
|
58
57
|
const fullScreenIndex = newDefaultBottomRightContent.findIndex(item => item.id === 'FullScreen');
|
package/lib/i18n/index.d.ts
CHANGED
|
@@ -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;
|
package/lib/i18n/strings.d.ts
CHANGED
|
@@ -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;
|
package/lib/i18n/strings.js
CHANGED
|
@@ -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
|
-
/**
|
|
7
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
61
|
-
var
|
|
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 (
|
|
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,46 +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
|
-
|
|
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:
|
|
80
|
-
height:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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(
|
|
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:
|
|
273
|
+
paddingBottom: `${paddingBottomPx}px`,
|
|
98
274
|
paddingLeft: `${paddingLeftPx}px`,
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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'
|
|
106
310
|
},
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
|
110
322
|
})
|
|
111
|
-
|
|
323
|
+
// 右向 chevron 由 top+right 描边旋转而成,视觉重心偏右;放大并整体左移使其在圆内居中
|
|
324
|
+
,
|
|
325
|
+
style: isButtonHint ? {
|
|
326
|
+
width: '9px',
|
|
327
|
+
height: '9px',
|
|
328
|
+
transform: 'translate(-1px, 0) rotate(45deg)'
|
|
329
|
+
} : undefined
|
|
330
|
+
})));
|
|
112
331
|
};
|
|
113
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
|
-
/**
|
|
7
|
-
|
|
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 ? '
|
|
25
|
-
},
|
|
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' ?
|
|
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', {
|
package/lib/ui/constant.d.ts
CHANGED
|
@@ -21,13 +21,13 @@ export declare const landscapeTipId = "landscapeTipId";
|
|
|
21
21
|
export declare const multiPtzId = "multiPtzId";
|
|
22
22
|
/** 控件点击统一事件:任意播放器内控件被点击时触发,可用于埋点或统一处理 */
|
|
23
23
|
export declare const widgetClick = "widgetClick";
|
|
24
|
-
/** 「去体验」按钮业务事件,与清晰度 resolutionBtnControlClick 用法一致 */
|
|
25
|
-
export declare const tryExperienceBtnClick = "tryExperienceBtnClick";
|
|
26
24
|
/**
|
|
27
25
|
* 试看倒计时结束事件:试看徽章倒计时归零时触发,
|
|
28
26
|
* 业务侧/相关控件可监听此事件刷新自己的状态(例如 TryExperience 重新拉取状态)
|
|
29
27
|
*/
|
|
30
28
|
export declare const trialCountdownEnd = "trialCountdownEnd";
|
|
29
|
+
export declare const hideTrialBadgeEvent = "hideTrialBadge";
|
|
30
|
+
export declare const refreshSmartImageQualityEvent = "refreshSmartImageQuality";
|
|
31
31
|
/** 控件点击事件用的 widgetId,统一在此维护 */
|
|
32
32
|
export declare const widgetLabs: {
|
|
33
33
|
readonly SCREENSHOT: "Screenshot";
|
package/lib/ui/constant.js
CHANGED
|
@@ -22,14 +22,13 @@ export const multiPtzId = 'multiPtzId';
|
|
|
22
22
|
/** 控件点击统一事件:任意播放器内控件被点击时触发,可用于埋点或统一处理 */
|
|
23
23
|
export const widgetClick = 'widgetClick';
|
|
24
24
|
|
|
25
|
-
/** 「去体验」按钮业务事件,与清晰度 resolutionBtnControlClick 用法一致 */
|
|
26
|
-
export const tryExperienceBtnClick = 'tryExperienceBtnClick';
|
|
27
|
-
|
|
28
25
|
/**
|
|
29
26
|
* 试看倒计时结束事件:试看徽章倒计时归零时触发,
|
|
30
27
|
* 业务侧/相关控件可监听此事件刷新自己的状态(例如 TryExperience 重新拉取状态)
|
|
31
28
|
*/
|
|
32
29
|
export const trialCountdownEnd = 'trialCountdownEnd';
|
|
30
|
+
export const hideTrialBadgeEvent = 'hideTrialBadge';
|
|
31
|
+
export const refreshSmartImageQualityEvent = 'refreshSmartImageQuality';
|
|
33
32
|
|
|
34
33
|
/** 控件点击事件用的 widgetId,统一在此维护 */
|
|
35
34
|
export const widgetLabs = {
|
package/lib/ui/index.d.ts
CHANGED
|
@@ -3,4 +3,4 @@ export * from './hooks';
|
|
|
3
3
|
export * from './context';
|
|
4
4
|
export type * from './event';
|
|
5
5
|
export { widgetClick } from './widgetClick';
|
|
6
|
-
export { widgetLabs,
|
|
6
|
+
export { widgetLabs, trialCountdownEnd } from './constant';
|
package/lib/ui/index.js
CHANGED
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 {};
|