@ray-js/ipc-player-integration 0.0.53-beta.7 → 0.0.53-beta.9

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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,150 @@
1
+ import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
2
+ import { act, renderHook } from '@testing-library/react-hooks';
3
+ import { ClarityType } from '@ray-js/ray-ipc-utils/lib/interface';
4
+ import { createUseCtx } from './ctx';
5
+ import { useStore } from './store';
6
+ import { PlayState } from '../interface';
7
+ import { getCachedAutoClarity, setCachedAutoClarity, subscribeClarityChanged } from '../utils/ttt';
8
+ jest.mock('@ray-js/ray', () => ({}));
9
+ jest.mock('@ray-js/ray-ipc-utils/lib/interface', () => ({
10
+ ClarityType: {
11
+ SS: 'SS',
12
+ SD: 'SD',
13
+ HD: 'HD',
14
+ UD: 'UD',
15
+ SSP: 'SSP',
16
+ AUTO: 'AUTO',
17
+ AUDIO: 'AUDIO'
18
+ },
19
+ IntercomMode: {
20
+ OneWay: 'OneWay',
21
+ TwoWay: 'TwoWay'
22
+ }
23
+ }));
24
+ jest.mock('../i18n', () => ({
25
+ __esModule: true,
26
+ default: {
27
+ getLang: key => key
28
+ }
29
+ }));
30
+ jest.mock('./multiCameraCtx', () => ({
31
+ isMultiCameraTileSupported: () => false,
32
+ useMultiCameraCtx: () => ({})
33
+ }));
34
+ jest.mock('../utils/ttt', () => ({
35
+ getCachedAutoClarity: jest.fn(),
36
+ isClarityChangedSupported: () => true,
37
+ setCachedAutoClarity: jest.fn(),
38
+ subscribeClarityChanged: jest.fn(() => () => undefined)
39
+ }));
40
+ const cameraState = resolution => ({
41
+ resolution,
42
+ resolutionList: [ClarityType.HD, ClarityType.SD],
43
+ mute: true,
44
+ intercomMode: 'TwoWay',
45
+ isIntercomSupported: false,
46
+ maxZoomSettings: 6
47
+ });
48
+ describe('cached auto clarity restoration', () => {
49
+ beforeEach(() => {
50
+ jest.clearAllMocks();
51
+ });
52
+ const mountContext = resolution => {
53
+ const setClarity = jest.fn();
54
+ const getMemoryState = jest.fn().mockResolvedValue(cameraState(resolution));
55
+ const useCtx = createUseCtx({
56
+ createPlayContext: () => ({
57
+ setClarity
58
+ }),
59
+ getMemoryState,
60
+ setResolutionMemoryState: jest.fn(),
61
+ setMuteMemoryState: jest.fn(),
62
+ authorizeStatus: jest.fn(),
63
+ toast: jest.fn()
64
+ });
65
+ const hook = renderHook(() => {
66
+ const ctx = useCtx({
67
+ devId: 'camera-1'
68
+ });
69
+ const state = useStore({
70
+ resolution: ctx.resolution,
71
+ autoClarity: ctx.autoClarity
72
+ });
73
+ return _objectSpread(_objectSpread({}, ctx), {}, {
74
+ state
75
+ });
76
+ });
77
+ return _objectSpread(_objectSpread({}, hook), {}, {
78
+ setClarity
79
+ });
80
+ };
81
+ it('shows last listener value on reopening AUTO and refreshes it after preview', async () => {
82
+ getCachedAutoClarity.mockReturnValue(ClarityType.HD);
83
+ const {
84
+ result,
85
+ setClarity,
86
+ unmount
87
+ } = mountContext(ClarityType.AUTO);
88
+ await act(async () => Promise.resolve());
89
+ expect(result.current.state).toEqual({
90
+ resolution: ClarityType.AUTO,
91
+ autoClarity: ClarityType.HD
92
+ });
93
+ act(() => result.current.setPlayState(PlayState.PLAYING));
94
+ await act(async () => Promise.resolve());
95
+ expect(setClarity).toHaveBeenCalledTimes(1);
96
+ expect(setClarity).toHaveBeenCalledWith(expect.objectContaining({
97
+ clarity: 'auto'
98
+ }));
99
+ const onReport = subscribeClarityChanged.mock.calls[0][1];
100
+ act(() => onReport({
101
+ deviceId: 'camera-1',
102
+ clarity: ClarityType.SD
103
+ }));
104
+ expect(result.current.state.autoClarity).toBe(ClarityType.SD);
105
+ expect(setCachedAutoClarity).toHaveBeenCalledWith('camera-1', ClarityType.SD);
106
+ unmount();
107
+ });
108
+ it('does not restore cached actual clarity in manual mode', async () => {
109
+ getCachedAutoClarity.mockReturnValue(ClarityType.HD);
110
+ const {
111
+ result,
112
+ unmount
113
+ } = mountContext(ClarityType.SD);
114
+ await act(async () => Promise.resolve());
115
+ expect(result.current.state).toEqual({
116
+ resolution: ClarityType.SD,
117
+ autoClarity: null
118
+ });
119
+ expect(getCachedAutoClarity).not.toHaveBeenCalled();
120
+ const onReport = subscribeClarityChanged.mock.calls[0][1];
121
+ act(() => onReport({
122
+ deviceId: 'camera-1',
123
+ clarity: ClarityType.HD
124
+ }));
125
+ expect(result.current.state.autoClarity).toBeNull();
126
+ expect(setCachedAutoClarity).not.toHaveBeenCalled();
127
+ unmount();
128
+ });
129
+ it('requests a fresh report when reopening AUTO without an actual clarity cache', async () => {
130
+ getCachedAutoClarity.mockReturnValue(null);
131
+ const {
132
+ result,
133
+ setClarity,
134
+ unmount
135
+ } = mountContext(ClarityType.AUTO);
136
+ await act(async () => Promise.resolve());
137
+ expect(result.current.state.autoClarity).toBeNull();
138
+ act(() => result.current.setPlayState(PlayState.PLAYING));
139
+ expect(setClarity).toHaveBeenCalledWith(expect.objectContaining({
140
+ clarity: 'auto'
141
+ }));
142
+ const onReport = subscribeClarityChanged.mock.calls[0][1];
143
+ act(() => onReport({
144
+ deviceId: 'camera-1',
145
+ clarity: ClarityType.HD
146
+ }));
147
+ expect(result.current.state.autoClarity).toBe(ClarityType.HD);
148
+ unmount();
149
+ });
150
+ });
package/lib/ctx/ctx.js CHANGED
@@ -12,7 +12,7 @@ import { changeIgnoreHideStopPreview } from '../ui/constant';
12
12
  import { ClarityType } from '@ray-js/ray-ipc-utils/lib/interface';
13
13
  import { getEventInstance } from './event';
14
14
  import { isMultiCameraTileSupported, useMultiCameraCtx } from './multiCameraCtx';
15
- import { isClarityChangedSupported, subscribeClarityChanged } from '../utils/ttt';
15
+ import { getCachedAutoClarity, isClarityChangedSupported, setCachedAutoClarity, subscribeClarityChanged } from '../utils/ttt';
16
16
  const SAVE_TO_ALBUM = 1;
17
17
  export const createUseCtx = _ref => {
18
18
  let {
@@ -110,13 +110,27 @@ export const createUseCtx = _ref => {
110
110
  _playState: playState,
111
111
  currentResolution: resolution
112
112
  });
113
+ const restoredAutoRef = useRef(false);
114
+ const refreshedAutoRef = useRef(false);
115
+ const freshAutoClarityRef = useRef(false);
116
+ const pendingAutoSelectionRef = useRef(false);
117
+ const userSelectedResolutionRef = useRef(false);
113
118
  useEffect(() => {
114
119
  updateAtom(autoClarity, null);
115
- if (currentResolution !== ClarityType.AUTO || !devId) return undefined;
120
+ restoredAutoRef.current = false;
121
+ refreshedAutoRef.current = false;
122
+ freshAutoClarityRef.current = false;
123
+ userSelectedResolutionRef.current = false;
124
+ if (!devId) return undefined;
116
125
  return subscribeClarityChanged(devId, res => {
126
+ if (getDefaultStore().get(resolution) !== ClarityType.AUTO && !pendingAutoSelectionRef.current) {
127
+ return;
128
+ }
129
+ freshAutoClarityRef.current = true;
117
130
  updateAtom(autoClarity, res.clarity);
131
+ setCachedAutoClarity(devId, res.clarity);
118
132
  });
119
- }, [autoClarity, currentResolution, devId]);
133
+ }, [autoClarity, devId, resolution]);
120
134
  const eventRef = useRef();
121
135
  if (!eventRef.current) {
122
136
  eventRef.current = getEventInstance();
@@ -139,6 +153,18 @@ export const createUseCtx = _ref => {
139
153
  if (resolution !== ClarityType.AUTO || canShowAutoClarity(list)) return resolution;
140
154
  return list.find(clarity => clarity !== ClarityType.AUTO) || ClarityType.HD;
141
155
  };
156
+ const restoreResolution = (cachedResolution, list) => {
157
+ if (userSelectedResolutionRef.current) return;
158
+ const target = normalizeResolution(cachedResolution, list);
159
+ if (target === ClarityType.AUTO) {
160
+ restoredAutoRef.current = true;
161
+ const cachedClarity = getCachedAutoClarity(devId);
162
+ if (cachedClarity && list.includes(cachedClarity) && !getDefaultStore().get(autoClarity)) {
163
+ updateAtom(autoClarity, cachedClarity);
164
+ }
165
+ }
166
+ _setResolution(target, true);
167
+ };
142
168
  useEffect(() => {
143
169
  // 初始化获取缓存值
144
170
 
@@ -148,7 +174,7 @@ export const createUseCtx = _ref => {
148
174
  var _res$maxZoomSettings;
149
175
  const res = await getMemoryState(devId);
150
176
  setResolutionList(withAutoClarity(res.resolutionList));
151
- _setResolution(normalizeResolution(res.resolution, res.resolutionList), true);
177
+ restoreResolution(res.resolution, res.resolutionList);
152
178
  _setMute(res.mute, true);
153
179
  setIntercomMode(res.intercomMode);
154
180
  setIntercomSupported(res.isIntercomSupported);
@@ -165,7 +191,7 @@ export const createUseCtx = _ref => {
165
191
  var _res$maxZoomSettings2;
166
192
  setResolutionList(withAutoClarity(res.resolutionList));
167
193
  // 传true表示值即可,会取播放器的值
168
- _setResolution(normalizeResolution(res.resolution, res.resolutionList), true);
194
+ restoreResolution(res.resolution, res.resolutionList);
169
195
  _setMute(res.mute, true);
170
196
  setIntercomMode(res.intercomMode);
171
197
  setIntercomSupported(res.isIntercomSupported);
@@ -173,6 +199,18 @@ export const createUseCtx = _ref => {
173
199
  });
174
200
  }
175
201
  }, [devId, _playState]);
202
+ useEffect(() => {
203
+ var _IPCPlayerInstance$cu;
204
+ if (!devId || _playState !== PlayState.PLAYING || currentResolution !== ClarityType.AUTO || !restoredAutoRef.current || freshAutoClarityRef.current || refreshedAutoRef.current) {
205
+ return;
206
+ }
207
+ refreshedAutoRef.current = true;
208
+ // 缓存只保证首屏有上次档位;出流后重申自动,让宿主回报本次实际档位。
209
+ (_IPCPlayerInstance$cu = IPCPlayerInstance.current) === null || _IPCPlayerInstance$cu === void 0 || _IPCPlayerInstance$cu.setClarity({
210
+ clarity: 'auto',
211
+ fail: error => console.warn('res===restore auto clarity fail', error)
212
+ });
213
+ }, [devId, _playState, currentResolution]);
176
214
  // 获取content数据
177
215
  const getContent = () => {
178
216
  const store = getDefaultStore();
@@ -267,6 +305,13 @@ export const createUseCtx = _ref => {
267
305
  updateAtom(resolution, target);
268
306
  return false;
269
307
  }
308
+ userSelectedResolutionRef.current = true;
309
+ // 清晰度事件可能在 setClarity 的 success 回调前到达,进入自动前先清掉上次的值。
310
+ restoredAutoRef.current = false;
311
+ if (target === ClarityType.AUTO) {
312
+ pendingAutoSelectionRef.current = true;
313
+ updateAtom(autoClarity, null);
314
+ }
270
315
  return new Promise((resolve, reject) => {
271
316
  const clarityMap = {
272
317
  [ClarityType.SS]: 'ss',
@@ -280,11 +325,13 @@ export const createUseCtx = _ref => {
280
325
  IPCPlayerInstance.current.setClarity({
281
326
  clarity: clarityMap[target] || 'normal',
282
327
  success: () => {
328
+ pendingAutoSelectionRef.current = false;
283
329
  setResolutionMemoryState(devId, target);
284
330
  updateAtom(resolution, target);
285
331
  resolve(true);
286
332
  },
287
333
  fail: err => {
334
+ pendingAutoSelectionRef.current = false;
288
335
  console.log(err, 'setClarity err');
289
336
  // updateAtom(mute, !target);
290
337
  reject(err);
package/lib/ui/ui.js CHANGED
@@ -1,14 +1,17 @@
1
1
  import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
2
+ import _extends from "@babel/runtime/helpers/esm/extends";
2
3
  import "core-js/modules/esnext.iterator.constructor.js";
3
4
  import "core-js/modules/esnext.iterator.filter.js";
4
5
  import "core-js/modules/esnext.iterator.find.js";
5
6
  import "core-js/modules/esnext.iterator.map.js";
7
+ import "core-js/modules/esnext.iterator.some.js";
6
8
  import React, { useContext, useState, useRef, useMemo, useEffect, useImperativeHandle } from 'react';
7
9
  import { View, CoverView, getSystemInfoSync, usePageEvent, setNavigationBarBack, setPageOrientation, getCurrentPages } from '@ray-js/ray';
8
10
  import { unstable_batchedUpdates as batchedUpdates } from '@ray-core/ray';
9
11
  import clsx from 'clsx';
10
12
  import IPCPlayer from '@ray-js/ray-ipc-player';
11
13
  import { ipcTTTOperatorLog } from '@ray-js/ray-ipc-utils';
14
+ import { ClarityType } from '@ray-js/ray-ipc-utils/lib/interface';
12
15
  import { PlayState, PlayerStreamStatus, MultiCameraScreenMode } from '../interface';
13
16
  import { useCtx } from '../ctx/ctx.composition';
14
17
  import { useStore, updateAtom, getDefaultStore } from '../ctx/store';
@@ -18,7 +21,7 @@ import BottomLeftContent from './bottomLeftContent';
18
21
  import BottomRightContent from './bottomRightContent';
19
22
  import TopLeftContent from './topLeftContent';
20
23
  import TopRightContent from './topRightContent';
21
- import { FlowLowTip } from '../widgets';
24
+ import { FlowLowTip, VideoBitKBP } from '../widgets';
22
25
  import { useMemoizedFn } from '../hooks';
23
26
  import { Storage } from '../utils/storage';
24
27
  import './ui.less';
@@ -27,6 +30,36 @@ import { getNgRawData, getSmartImageQualityState, isLowPhone, notifyOfflineShown
27
30
  import { getCaptureResultSummary } from '../utils/captureResult';
28
31
  import { lensCount } from '../widgets/zoomTurntable/lensSkill';
29
32
  import { shouldRenderContent } from './absoluteContent';
33
+ const AutoClarityOnly = _ref => {
34
+ let {
35
+ instance
36
+ } = _ref;
37
+ return /*#__PURE__*/React.createElement(VideoBitKBP, _extends({}, instance, {
38
+ IPCPlayerContext: instance.IPCPlayerInstance,
39
+ className: "top-right-item-container",
40
+ hideKbsMenu: true,
41
+ hideSignalMenu: true
42
+ }));
43
+ };
44
+ const StandaloneAutoClarity = _ref2 => {
45
+ let {
46
+ instance,
47
+ show
48
+ } = _ref2;
49
+ const {
50
+ resolution,
51
+ autoClarity
52
+ } = useStore({
53
+ resolution: instance.resolution,
54
+ autoClarity: instance.autoClarity
55
+ });
56
+ if (!show || resolution !== ClarityType.AUTO || !autoClarity) return null;
57
+ return /*#__PURE__*/React.createElement(TopRightContent, {
58
+ ctx: instance
59
+ }, /*#__PURE__*/React.createElement(AutoClarityOnly, {
60
+ instance: instance
61
+ }));
62
+ };
30
63
  function getCtxInstance(instance, devId) {
31
64
  if (instance) return instance;
32
65
  // eslint-disable-next-line react-hooks/rules-of-hooks
@@ -1013,6 +1046,7 @@ export const IPCPlayerIntegration = /*#__PURE__*/React.memo(props => {
1013
1046
  };
1014
1047
  }, []);
1015
1048
  const playerReady = playState === PlayState.PLAYING;
1049
+ const hasVisibleBitrateWidget = topRightContent === null || topRightContent === void 0 ? void 0 : topRightContent.some(item => item.id === 'VideoBitKBP' && !item.hidden);
1016
1050
  /**
1017
1051
  * 监听当前的zoom比例状态
1018
1052
  */
@@ -1206,7 +1240,12 @@ export const IPCPlayerIntegration = /*#__PURE__*/React.memo(props => {
1206
1240
  ctx: instance
1207
1241
  }, renderTopLeftContent), /*#__PURE__*/React.createElement(TopRightContent, {
1208
1242
  ctx: instance
1209
- }, renderTopRightContent))), /*#__PURE__*/React.createElement(View, {
1243
+ }, renderTopRightContent, !hasVisibleBitrateWidget && /*#__PURE__*/React.createElement(AutoClarityOnly, {
1244
+ instance: instance
1245
+ })))), /*#__PURE__*/React.createElement(StandaloneAutoClarity, {
1246
+ instance: instance,
1247
+ show: !showTopContent || componentHideState || !playerReady
1248
+ }), /*#__PURE__*/React.createElement(View, {
1210
1249
  className: clsx('ipc-player-bottom-content', {
1211
1250
  'ipc-player-bottom-content-hide': componentHideState,
1212
1251
  'ipc-player-bottom-content-show': !componentHideState,
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,75 @@
1
+ import { ClarityType } from '@ray-js/ray-ipc-utils/lib/interface';
2
+ import { getCachedAutoClarity, setCachedAutoClarity, subscribeClarityChanged } from './ttt';
3
+ jest.mock('@ray-js/ray-ipc-utils/lib/interface', () => ({
4
+ ClarityType: {
5
+ SS: 'SS',
6
+ SD: 'SD',
7
+ HD: 'HD',
8
+ UD: 'UD',
9
+ SSP: 'SSP',
10
+ AUTO: 'AUTO',
11
+ AUDIO: 'AUDIO'
12
+ }
13
+ }));
14
+ describe('auto clarity cache and listener', () => {
15
+ const storage = new Map();
16
+ let listener;
17
+ beforeEach(() => {
18
+ storage.clear();
19
+ globalThis.ty = {
20
+ getStorageSync: jest.fn(_ref => {
21
+ let {
22
+ key
23
+ } = _ref;
24
+ return storage.get(key);
25
+ }),
26
+ setStorageSync: jest.fn(_ref2 => {
27
+ let {
28
+ key,
29
+ data
30
+ } = _ref2;
31
+ return storage.set(key, data);
32
+ }),
33
+ ipc: {
34
+ onClarityChanged: jest.fn(callback => {
35
+ listener = callback;
36
+ }),
37
+ offClarityChanged: jest.fn()
38
+ }
39
+ };
40
+ });
41
+ it('restores the last reported actual clarity only for its device', () => {
42
+ setCachedAutoClarity('camera-1', ClarityType.HD);
43
+ expect(getCachedAutoClarity('camera-1')).toBe(ClarityType.HD);
44
+ expect(getCachedAutoClarity('camera-2')).toBeNull();
45
+ setCachedAutoClarity('camera-1', ClarityType.AUTO);
46
+ expect(getCachedAutoClarity('camera-1')).toBe(ClarityType.HD);
47
+ globalThis.ty.getStorageSync = jest.fn(() => ({
48
+ data: ClarityType.SD
49
+ }));
50
+ expect(getCachedAutoClarity('camera-1')).toBe(ClarityType.SD);
51
+ });
52
+ it('accepts a new actual clarity report for the same device', () => {
53
+ const handler = jest.fn();
54
+ const unsubscribe = subscribeClarityChanged('camera-1', handler);
55
+ listener({
56
+ deviceId: 'camera-2',
57
+ clarity: ClarityType.SD
58
+ });
59
+ listener({
60
+ deviceId: 'camera-1',
61
+ clarity: ClarityType.AUTO
62
+ });
63
+ listener({
64
+ deviceId: 'camera-1',
65
+ clarity: ClarityType.SD
66
+ });
67
+ expect(handler).toHaveBeenCalledTimes(1);
68
+ expect(handler).toHaveBeenCalledWith({
69
+ deviceId: 'camera-1',
70
+ clarity: ClarityType.SD
71
+ });
72
+ unsubscribe();
73
+ expect(globalThis.ty.ipc.offClarityChanged).toHaveBeenCalledWith(listener);
74
+ });
75
+ });
@@ -54,6 +54,8 @@ export type ClarityChangedResult = {
54
54
  };
55
55
  export type ClarityChangedHandler = (result: ClarityChangedResult) => void;
56
56
  type ClarityChangedListener = (payload: ClarityChangedResult) => void;
57
+ export declare function getCachedAutoClarity(devId: string): ClarityType | null;
58
+ export declare function setCachedAutoClarity(devId: string, clarity: ClarityType): void;
57
59
  export declare function offClarityChanged(listener: ClarityChangedListener): void;
58
60
  /**
59
61
  * 监听自动清晰度变化,返回取消监听函数。
package/lib/utils/ttt.js CHANGED
@@ -171,6 +171,30 @@ export function isClarityChangedSupported() {
171
171
  var _ty5;
172
172
  return typeof ty !== 'undefined' && typeof ((_ty5 = ty) === null || _ty5 === void 0 || (_ty5 = _ty5.ipc) === null || _ty5 === void 0 ? void 0 : _ty5.onClarityChanged) === 'function';
173
173
  }
174
+ const actualClarityTypes = [ClarityType.SS, ClarityType.SD, ClarityType.HD, ClarityType.UD, ClarityType.SSP];
175
+ const autoClarityStorageKey = devId => `${devId}_auto_clarity`;
176
+ export function getCachedAutoClarity(devId) {
177
+ try {
178
+ const stored = ty.getStorageSync({
179
+ key: autoClarityStorageKey(devId)
180
+ });
181
+ const clarity = typeof stored === 'string' ? stored : stored === null || stored === void 0 ? void 0 : stored.data;
182
+ return actualClarityTypes.includes(clarity) ? clarity : null;
183
+ } catch (error) {
184
+ return null;
185
+ }
186
+ }
187
+ export function setCachedAutoClarity(devId, clarity) {
188
+ if (!actualClarityTypes.includes(clarity)) return;
189
+ try {
190
+ ty.setStorageSync({
191
+ key: autoClarityStorageKey(devId),
192
+ data: clarity
193
+ });
194
+ } catch (error) {
195
+ console.warn('res===setCachedAutoClarity fail', error);
196
+ }
197
+ }
174
198
  export function offClarityChanged(listener) {
175
199
  var _ty6;
176
200
  const ipc = typeof ty !== 'undefined' ? (_ty6 = ty) === null || _ty6 === void 0 ? void 0 : _ty6.ipc : undefined;
@@ -202,8 +226,8 @@ export function subscribeClarityChanged(devId, handler) {
202
226
  deviceId,
203
227
  clarity
204
228
  } = payload || {};
205
- if (!Object.values(ClarityType).includes(clarity)) {
206
- console.warn('[resolution] onClarityChanged 缺少 clarity');
229
+ if (!actualClarityTypes.includes(clarity)) {
230
+ console.warn('[resolution] onClarityChanged 未返回实际清晰度');
207
231
  return;
208
232
  }
209
233
  // 一页多设备时只认自己的;宿主没带 deviceId 就不过滤
@@ -56,13 +56,14 @@ export const VideoBitKBP = props => {
56
56
  }
57
57
  }
58
58
  };
59
- const hasKbps = !hideKbsMenu && bitKBP !== '' && bitKBP !== '0';
60
- const hasSignal = !hideSignalMenu && signalStrength !== null;
59
+ const isPlaying = store.playState === PlayState.PLAYING;
60
+ const hasKbps = isPlaying && !hideKbsMenu && bitKBP !== '' && bitKBP !== '0';
61
+ const hasSignal = isPlaying && !hideSignalMenu && signalStrength !== null;
61
62
  const activeClarity = store.resolution === ClarityType.AUTO ? store.autoClarity : null;
62
- if (!hasKbps && !hasSignal || store.playState !== PlayState.PLAYING) {
63
+ const hasClarity = Boolean(activeClarity);
64
+ if (!hasKbps && !hasSignal && !hasClarity) {
63
65
  return null;
64
66
  }
65
- if (hideKbsMenu && hideSignalMenu) return null;
66
67
  return /*#__PURE__*/React.createElement(View, {
67
68
  className: clsx({
68
69
  'ipc-player-plugin-videoBitKBP-container': true,
@@ -76,12 +77,14 @@ export const VideoBitKBP = props => {
76
77
  className: clsx('icon-panel', 'icon-panel-wi-Fi', 'ipc-player-plugin-videoBitKBP-signal-icon')
77
78
  }), /*#__PURE__*/React.createElement(Text, null, signalStrength), /*#__PURE__*/React.createElement(Text, {
78
79
  className: "ipc-player-plugin-videoBitKBP-unit-percent"
79
- }, signalStrength > 0 ? '%' : 'dBm')), hasSignal && hasKbps && /*#__PURE__*/React.createElement(View, {
80
+ }, signalStrength > 0 ? '%' : 'dBm')), hasSignal && (hasKbps || hasClarity) && /*#__PURE__*/React.createElement(View, {
80
81
  className: "ipc-player-plugin-videoBitKBP-divider"
81
- }), hasKbps && /*#__PURE__*/React.createElement(View, {
82
+ }), (hasKbps || hasClarity) && /*#__PURE__*/React.createElement(View, {
82
83
  className: "ipc-player-plugin-videoBitKBP-kbps"
83
84
  }, activeClarity && /*#__PURE__*/React.createElement(Text, {
84
- className: "ipc-player-plugin-videoBitKBP-clarity"
85
+ className: clsx('ipc-player-plugin-videoBitKBP-clarity', {
86
+ 'ipc-player-plugin-videoBitKBP-clarity-only': !hasKbps
87
+ })
85
88
  }, (clarity => {
86
89
  switch (clarity) {
87
90
  case ClarityType.SS:
@@ -101,7 +104,7 @@ export const VideoBitKBP = props => {
101
104
  default:
102
105
  return clarity;
103
106
  }
104
- })(activeClarity)), /*#__PURE__*/React.createElement(Text, null, bitKBP), /*#__PURE__*/React.createElement(Text, {
107
+ })(activeClarity)), hasKbps && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, null, bitKBP), /*#__PURE__*/React.createElement(Text, {
105
108
  className: "ipc-player-plugin-videoBitKBP-unit-kbs"
106
- }, "KB/S"))));
109
+ }, "KB/S")))));
107
110
  };
@@ -39,6 +39,9 @@
39
39
  .ipc-player-plugin-videoBitKBP-clarity {
40
40
  margin-right: calc(4px * var(--ipc-player-size-scale, 1));
41
41
  }
42
+ .ipc-player-plugin-videoBitKBP-clarity-only {
43
+ margin-right: 0;
44
+ }
42
45
  .ipc-player-plugin-videoBitKBP-unit-kbs {
43
46
  margin-left: calc(4px * var(--ipc-player-size-scale, 1));
44
47
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,81 @@
1
+ import React from 'react';
2
+ import { renderToStaticMarkup } from 'react-dom/server';
3
+ let mockStoreState;
4
+ jest.mock('@ray-js/ray', () => ({
5
+ View: _ref => {
6
+ let {
7
+ children,
8
+ className
9
+ } = _ref;
10
+ return /*#__PURE__*/React.createElement("div", {
11
+ className: className
12
+ }, children);
13
+ },
14
+ Text: _ref2 => {
15
+ let {
16
+ children,
17
+ className
18
+ } = _ref2;
19
+ return /*#__PURE__*/React.createElement("span", {
20
+ className: className
21
+ }, children);
22
+ },
23
+ usePageEvent: () => undefined
24
+ }));
25
+ jest.mock('@ray-js/ray-ipc-utils', () => ({
26
+ getVideoBitrateKbps: jest.fn(),
27
+ requestWifiSignal: jest.fn()
28
+ }));
29
+ jest.mock('@ray-js/ray-ipc-utils/lib/interface', () => ({
30
+ ClarityType: {
31
+ SS: 'SS',
32
+ SD: 'SD',
33
+ HD: 'HD',
34
+ UD: 'UD',
35
+ SSP: 'SSP',
36
+ AUTO: 'AUTO',
37
+ AUDIO: 'AUDIO'
38
+ }
39
+ }));
40
+ jest.mock('../../ctx/store', () => ({
41
+ useStore: () => mockStoreState
42
+ }));
43
+ jest.mock('../../i18n', () => ({
44
+ __esModule: true,
45
+ default: {
46
+ getLang: key => key
47
+ }
48
+ }));
49
+ jest.mock('./videoBitKBP.less', () => ({}));
50
+ const {
51
+ VideoBitKBP
52
+ } = require('./videoBitKBP');
53
+ const renderWidget = function (resolution, autoClarity) {
54
+ let playState = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
55
+ mockStoreState = {
56
+ resolution,
57
+ autoClarity,
58
+ playState
59
+ };
60
+ return renderToStaticMarkup(/*#__PURE__*/React.createElement(VideoBitKBP, {
61
+ devId: 'device',
62
+ playState: {},
63
+ resolution: {},
64
+ autoClarity: {},
65
+ hideKbsMenu: true,
66
+ hideSignalMenu: true
67
+ }));
68
+ };
69
+ describe('VideoBitKBP auto clarity', () => {
70
+ it('shows actual clarity independently of bitrate, signal and play state', () => {
71
+ const markup = renderWidget('AUTO', 'HD', 2);
72
+ expect(markup).toContain('ipc_player_resolution_HD');
73
+ expect(markup).not.toContain('KB/S');
74
+ });
75
+ it('does not show clarity outside auto mode', () => {
76
+ expect(renderWidget('SD', 'HD')).toBe('');
77
+ });
78
+ it('waits for the native clarity report in auto mode', () => {
79
+ expect(renderWidget('AUTO', null)).toBe('');
80
+ });
81
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ray-js/ipc-player-integration",
3
- "version": "0.0.53-beta.7",
3
+ "version": "0.0.53-beta.9",
4
4
  "description": "IPC 融合播放器",
5
5
  "main": "lib/index",
6
6
  "files": [
@@ -37,7 +37,7 @@
37
37
  "dependencies": {
38
38
  "@ray-js/direction-control": "^0.0.8",
39
39
  "@ray-js/ipc-ptz-zoom": "^0.0.3",
40
- "@ray-js/ray-ipc-player": "^2.1.3",
40
+ "@ray-js/ray-ipc-player": "2.1.4-beta.0",
41
41
  "@ray-js/ray-ipc-utils": "^1.1.16",
42
42
  "@ray-js/svg": "0.2.0",
43
43
  "clsx": "^1.2.1",