@ray-js/ipc-player-integration 0.0.51 → 0.0.52-beta.0

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
@@ -36,6 +36,7 @@ export const createUseCtx = _ref => {
36
36
  initBottomRightContent,
37
37
  initAbsoluteContent,
38
38
  saveToAlbum = SAVE_TO_ALBUM,
39
+ multiLens = false,
39
40
  showPtzControlTip = false,
40
41
  videoSplitProtocol
41
42
  } = _ref2;
@@ -55,6 +56,7 @@ export const createUseCtx = _ref => {
55
56
 
56
57
  // 录像中
57
58
  const [recording] = useAtom(false);
59
+ const recordingMultiLensRef = useRef(false);
58
60
 
59
61
  // 录像是否不可点击
60
62
  const [recordingDisabled, setRecordingDisabled] = useAtom(false);
@@ -251,6 +253,34 @@ export const createUseCtx = _ref => {
251
253
  });
252
254
  });
253
255
  };
256
+ const stopRecording = showError => {
257
+ const sessionMultiLens = recordingMultiLensRef.current;
258
+ return new Promise((resolve, reject) => {
259
+ IPCPlayerInstance.current.stopRecord(_objectSpread(_objectSpread({
260
+ saveToAlbum
261
+ }, sessionMultiLens ? {
262
+ multiLens: true
263
+ } : {}), {}, {
264
+ success: res => {
265
+ recordingMultiLensRef.current = false;
266
+ updateAtom(recording, false);
267
+ resolve(res);
268
+ },
269
+ fail: err => {
270
+ recordingMultiLensRef.current = false;
271
+ updateAtom(recording, false);
272
+ reject(err);
273
+ if (showError) {
274
+ const errObj = err.innerError || err || {};
275
+ const errMsg = errObj.errorMsg || Strings.getLang('ipc_player_stop_recording_err');
276
+ toast({
277
+ title: errMsg
278
+ });
279
+ }
280
+ }
281
+ }));
282
+ });
283
+ };
254
284
  const multiCameraCtx = useMultiCameraCtx({
255
285
  devId,
256
286
  IPCPlayerInstance: IPCPlayerInstance.current,
@@ -261,6 +291,7 @@ export const createUseCtx = _ref => {
261
291
  brandColor,
262
292
  verticalMic,
263
293
  saveToAlbum,
294
+ multiLens,
264
295
  screenType,
265
296
  isVerticalFullLayout,
266
297
  setIsVerticalFullLayout,
@@ -404,82 +435,64 @@ export const createUseCtx = _ref => {
404
435
  eventRef.current.emit(changeIgnoreHideStopPreview, false);
405
436
  return false;
406
437
  }
438
+ if (!target) return stopRecording(true);
407
439
  return new Promise((resolve, reject) => {
408
- if (target) {
409
- // 写入相册
410
- ty.authorizeStatus({
411
- scope: 'scope.writePhotosAlbum',
440
+ const sessionMultiLens = multiLens;
441
+ const startRecord = () => {
442
+ IPCPlayerInstance.current.startRecord(_objectSpread(_objectSpread({
443
+ saveToAlbum
444
+ }, sessionMultiLens ? {
445
+ multiLens: true
446
+ } : {}), {}, {
412
447
  success: () => {
413
- eventRef.current.emit(changeIgnoreHideStopPreview, false);
414
- IPCPlayerInstance.current.startRecord({
415
- saveToAlbum,
416
- success: () => {
417
- updateAtom(recording, true);
418
- resolve(true);
419
- },
420
- fail: err => {
421
- eventRef.current.emit(changeIgnoreHideStopPreview, false);
422
- reject(err);
423
- }
424
- });
425
- },
426
- fail: () => {
427
- ty.authorize({
428
- scope: 'scope.writePhotosAlbum',
429
- success: () => {
430
- IPCPlayerInstance.current.startRecord({
431
- saveToAlbum,
432
- success: () => {
433
- updateAtom(recording, true);
434
- resolve(true);
435
- },
436
- fail: err => {
437
- reject(err);
438
- }
439
- });
440
- },
441
- fail: () => {
442
- showModal({
443
- title: '',
444
- content: Strings.getLang('ipc_player_no_album_permission'),
445
- cancelText: Strings.getLang('ipc_player_authorize_cancel_text'),
446
- confirmText: Strings.getLang('ipc_player_authorize_confirm_text'),
447
- isShowGlobal: true,
448
- success: res => {
449
- if (res.confirm) {
450
- openAppSystemSettingPage({
451
- scope: 'App-Settings',
452
- success: () => {
453
- console.log('跳转成功');
454
- }
455
- });
456
- }
457
- }
458
- });
459
- }
460
- });
461
- }
462
- });
463
- } else {
464
- IPCPlayerInstance.current.stopRecord({
465
- saveToAlbum,
466
- success: res => {
467
- updateAtom(recording, false);
468
- resolve(res);
448
+ recordingMultiLensRef.current = sessionMultiLens;
449
+ updateAtom(recording, true);
450
+ resolve(true);
469
451
  },
470
452
  fail: err => {
471
- updateAtom(recording, false);
453
+ eventRef.current.emit(changeIgnoreHideStopPreview, false);
472
454
  reject(err);
473
- const errObj = err.innerError || err || {};
474
- const errMsg = errObj.errorMsg || Strings.getLang('ipc_player_stop_recording_err');
475
- toast({
476
- title: errMsg
477
- });
478
455
  }
479
- });
480
- }
456
+ }));
457
+ };
458
+ // 写入相册
459
+ ty.authorizeStatus({
460
+ scope: 'scope.writePhotosAlbum',
461
+ success: () => {
462
+ eventRef.current.emit(changeIgnoreHideStopPreview, false);
463
+ startRecord();
464
+ },
465
+ fail: () => {
466
+ ty.authorize({
467
+ scope: 'scope.writePhotosAlbum',
468
+ success: () => {
469
+ startRecord();
470
+ },
471
+ fail: () => {
472
+ showModal({
473
+ title: '',
474
+ content: Strings.getLang('ipc_player_no_album_permission'),
475
+ cancelText: Strings.getLang('ipc_player_authorize_cancel_text'),
476
+ confirmText: Strings.getLang('ipc_player_authorize_confirm_text'),
477
+ isShowGlobal: true,
478
+ success: res => {
479
+ if (res.confirm) {
480
+ openAppSystemSettingPage({
481
+ scope: 'App-Settings',
482
+ success: () => {
483
+ console.log('跳转成功');
484
+ }
485
+ });
486
+ }
487
+ }
488
+ });
489
+ }
490
+ });
491
+ }
492
+ });
481
493
  });
482
494
  },
495
+ stopRecordingOnUnload: () => stopRecording(false),
483
496
  setRecordingDisabled,
484
497
  setScreenType,
485
498
  topLeftContent,
@@ -5,8 +5,11 @@ declare const Strings: kit.I18N<{
5
5
  ipc_player_resolution_SD: string;
6
6
  ipc_player_current_resolution_is_equal: string;
7
7
  ipc_player_screenshot_success_tip: string;
8
+ ipc_player_screenshot_multi_success_tip: string;
8
9
  ipc_player_error_screenshot_error_tip: string;
9
10
  ipc_player_record_success_tip: string;
11
+ ipc_player_record_multi_success_tip: string;
12
+ ipc_player_multi_capture_success_count: string;
10
13
  ipc_player_shot_success_check: string;
11
14
  ipc_player_record_success_check: string;
12
15
  ipc_player_recording_now_tip: string;
@@ -42,8 +45,11 @@ declare const Strings: kit.I18N<{
42
45
  ipc_player_resolution_SD: string;
43
46
  ipc_player_current_resolution_is_equal: string;
44
47
  ipc_player_screenshot_success_tip: string;
48
+ ipc_player_screenshot_multi_success_tip: string;
45
49
  ipc_player_error_screenshot_error_tip: string;
46
50
  ipc_player_record_success_tip: string;
51
+ ipc_player_record_multi_success_tip: string;
52
+ ipc_player_multi_capture_success_count: string;
47
53
  ipc_player_shot_success_check: string;
48
54
  ipc_player_record_success_check: string;
49
55
  ipc_player_recording_now_tip: string;
@@ -79,8 +85,11 @@ declare const Strings: kit.I18N<{
79
85
  ipc_player_resolution_SD: string;
80
86
  ipc_player_current_resolution_is_equal: string;
81
87
  ipc_player_screenshot_success_tip: string;
88
+ ipc_player_screenshot_multi_success_tip: string;
82
89
  ipc_player_error_screenshot_error_tip: string;
83
90
  ipc_player_record_success_tip: string;
91
+ ipc_player_record_multi_success_tip: string;
92
+ ipc_player_multi_capture_success_count: string;
84
93
  ipc_player_shot_success_check: string;
85
94
  ipc_player_record_success_check: string;
86
95
  ipc_player_recording_now_tip: string;
@@ -115,8 +124,11 @@ declare const Strings: kit.I18N<{
115
124
  ipc_player_resolution_SD: string;
116
125
  ipc_player_current_resolution_is_equal: string;
117
126
  ipc_player_screenshot_success_tip: string;
127
+ ipc_player_screenshot_multi_success_tip: string;
118
128
  ipc_player_error_screenshot_error_tip: string;
119
129
  ipc_player_record_success_tip: string;
130
+ ipc_player_record_multi_success_tip: string;
131
+ ipc_player_multi_capture_success_count: string;
120
132
  ipc_player_shot_success_check: string;
121
133
  ipc_player_record_success_check: string;
122
134
  ipc_player_recording_now_tip: string;
@@ -4,8 +4,11 @@ declare const _default: {
4
4
  ipc_player_resolution_SD: string;
5
5
  ipc_player_current_resolution_is_equal: string;
6
6
  ipc_player_screenshot_success_tip: string;
7
+ ipc_player_screenshot_multi_success_tip: string;
7
8
  ipc_player_error_screenshot_error_tip: string;
8
9
  ipc_player_record_success_tip: string;
10
+ ipc_player_record_multi_success_tip: string;
11
+ ipc_player_multi_capture_success_count: string;
9
12
  ipc_player_shot_success_check: string;
10
13
  ipc_player_record_success_check: string;
11
14
  ipc_player_recording_now_tip: string;
@@ -41,8 +44,11 @@ declare const _default: {
41
44
  ipc_player_resolution_SD: string;
42
45
  ipc_player_current_resolution_is_equal: string;
43
46
  ipc_player_screenshot_success_tip: string;
47
+ ipc_player_screenshot_multi_success_tip: string;
44
48
  ipc_player_error_screenshot_error_tip: string;
45
49
  ipc_player_record_success_tip: string;
50
+ ipc_player_record_multi_success_tip: string;
51
+ ipc_player_multi_capture_success_count: string;
46
52
  ipc_player_shot_success_check: string;
47
53
  ipc_player_record_success_check: string;
48
54
  ipc_player_recording_now_tip: string;
@@ -4,8 +4,11 @@ export default {
4
4
  ipc_player_resolution_SD: 'SD',
5
5
  ipc_player_current_resolution_is_equal: 'Already at current clarity',
6
6
  ipc_player_screenshot_success_tip: 'Screenshot saved to album',
7
+ ipc_player_screenshot_multi_success_tip: 'Screenshots saved to album',
7
8
  ipc_player_error_screenshot_error_tip: 'Screenshot failed. Please try again later.',
8
9
  ipc_player_record_success_tip: 'Recording saved to album',
10
+ ipc_player_record_multi_success_tip: 'Recordings saved to album',
11
+ ipc_player_multi_capture_success_count: '{0} items',
9
12
  ipc_player_shot_success_check: 'View',
10
13
  ipc_player_record_success_check: 'View',
11
14
  ipc_player_recording_now_tip: 'Recording in progress...',
@@ -41,8 +44,11 @@ export default {
41
44
  ipc_player_resolution_SD: '标清',
42
45
  ipc_player_current_resolution_is_equal: '已是当前清晰度',
43
46
  ipc_player_screenshot_success_tip: '截图已保存在APP相册',
47
+ ipc_player_screenshot_multi_success_tip: '图片已保存至相册',
44
48
  ipc_player_error_screenshot_error_tip: '截图失败了,请稍后再试',
45
49
  ipc_player_record_success_tip: '录像已保存在APP相册',
50
+ ipc_player_record_multi_success_tip: '视频已保存至相册',
51
+ ipc_player_multi_capture_success_count: '共{0}个',
46
52
  ipc_player_shot_success_check: '查看',
47
53
  ipc_player_record_success_check: '查看',
48
54
  ipc_player_recording_now_tip: '正在录像中...',
@@ -46,6 +46,7 @@ export type UseCtx = (options: {
46
46
  initBottomRightContent?: ComponentConfig[];
47
47
  initAbsoluteContent?: ComponentConfig[];
48
48
  saveToAlbum?: 0 | 1;
49
+ multiLens?: boolean;
49
50
  showPtzControlTip?: boolean;
50
51
  videoSplitProtocol?: VideoSplitProtocol;
51
52
  }) => {
@@ -53,6 +54,7 @@ export type UseCtx = (options: {
53
54
  brandColor: RetAtom<string>;
54
55
  verticalMic: RetAtom<boolean>;
55
56
  saveToAlbum: 0 | 1;
57
+ multiLens: boolean;
56
58
  showPtzControlTip: boolean;
57
59
  screenType: RetAtom<ScreenType>;
58
60
  isVerticalFullLayout: RetAtom<boolean>;
@@ -87,7 +89,8 @@ export type UseCtx = (options: {
87
89
  setRecordingDisabled: (type: boolean) => void;
88
90
  setResolution: (type: string) => void;
89
91
  setResolutionList: (type: string[]) => void;
90
- setRecording: (value: boolean, onlyUpdateAtom?: boolean) => Promise<RecordInfoData>;
92
+ setRecording: (value: boolean, onlyUpdateAtom?: boolean) => Promise<CaptureResult>;
93
+ stopRecordingOnUnload: () => Promise<CaptureResult>;
91
94
  setIntercom: (value: boolean, onlyUpdateAtom?: boolean) => Promise<boolean>;
92
95
  setMute: (value: boolean) => Promise<boolean>;
93
96
  changeStreamStatus: (value: PlayerStreamStatus) => void;
@@ -127,11 +130,22 @@ export type PlayStatusData = {
127
130
  playState: PlayState;
128
131
  playCode: number;
129
132
  };
130
- export type RecordInfoData = boolean | {
133
+ export type CaptureFileInfo = {
134
+ index?: number;
131
135
  tempImagePath: string;
132
136
  filePath: string;
133
- thumbPath: string;
137
+ thumbPath?: string;
138
+ width?: number;
139
+ height?: number;
140
+ data?: string;
134
141
  };
142
+ export type MultiCaptureFileInfo = CaptureFileInfo & {
143
+ index: number;
144
+ };
145
+ export type CaptureResult = boolean | CaptureFileInfo | {
146
+ items: MultiCaptureFileInfo[];
147
+ };
148
+ export type RecordInfoData = CaptureResult;
135
149
  /**
136
150
  * 多目屏幕模式
137
151
  */
package/lib/ui/ui.js CHANGED
@@ -24,6 +24,7 @@ import { Storage } from '../utils/storage';
24
24
  import './ui.less';
25
25
  import { updatePlayerWidgetProps } from '../features';
26
26
  import { getNgRawData, getSmartImageQualityState, isLowPhone } from '../utils/ttt';
27
+ import { getCaptureResultSummary } from '../utils/captureResult';
27
28
  import { lensCount } from '../widgets/zoomTurntable/lensSkill';
28
29
  function getCtxInstance(instance, devId) {
29
30
  if (instance) return instance;
@@ -651,14 +652,11 @@ export const IPCPlayerIntegration = /*#__PURE__*/React.memo(props => {
651
652
  ipcTTTOperatorLog('hide: 调用停止录制方法');
652
653
  setRecording(false, true);
653
654
  setIntercom(false, true);
654
- instance === null || instance === void 0 || instance.IPCPlayerInstance.stopRecord({
655
- saveToAlbum: 1,
656
- success: () => {
657
- ipcTTTOperatorLog('hide: 调用停止录制方法成功');
658
- },
659
- fail: () => {
660
- ipcTTTOperatorLog('hide: 调用停止录制方法失败');
661
- }
655
+ instance === null || instance === void 0 || instance.stopRecordingOnUnload().then(result => {
656
+ const summary = getCaptureResultSummary(result);
657
+ ipcTTTOperatorLog(summary !== null && summary !== void 0 && summary.isMultiLens ? `hide: stop recording succeeded with ${summary.count} files` : 'hide: 调用停止录制方法成功');
658
+ }).catch(() => {
659
+ ipcTTTOperatorLog('hide: 调用停止录制方法失败');
662
660
  });
663
661
  ipcTTTOperatorLog('hide: 调用停止对讲方法');
664
662
  instance === null || instance === void 0 || instance.IPCPlayerInstance.stopTalk({
@@ -0,0 +1,7 @@
1
+ import type { CaptureResult } from '../interface';
2
+ export type CaptureResultSummary = {
3
+ count: number;
4
+ isMultiLens: boolean;
5
+ thumbnailPath: string;
6
+ };
7
+ export declare function getCaptureResultSummary(result: CaptureResult): CaptureResultSummary | null;
@@ -0,0 +1,17 @@
1
+ export function getCaptureResultSummary(result) {
2
+ if (!result || typeof result === 'boolean') return null;
3
+ if ('items' in result) {
4
+ const firstItem = result.items[0];
5
+ if (!firstItem) return null;
6
+ return {
7
+ count: result.items.length,
8
+ isMultiLens: true,
9
+ thumbnailPath: firstItem.tempImagePath || firstItem.thumbPath || firstItem.filePath
10
+ };
11
+ }
12
+ return {
13
+ count: 1,
14
+ isMultiLens: false,
15
+ thumbnailPath: result.tempImagePath || result.thumbPath || result.filePath
16
+ };
17
+ }
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import './captureResult.less';
3
+ type CaptureKind = 'record' | 'screenshot';
4
+ type Props = {
5
+ brandColor: string;
6
+ count: number;
7
+ kind: CaptureKind;
8
+ onCheck: () => void;
9
+ thumbnailPath: string;
10
+ };
11
+ export declare function CaptureResultToast({ brandColor, count, kind, onCheck, thumbnailPath }: Props): React.JSX.Element;
12
+ export {};
@@ -0,0 +1,64 @@
1
+ import "core-js/modules/esnext.iterator.constructor.js";
2
+ import "core-js/modules/esnext.iterator.map.js";
3
+ import React from 'react';
4
+ import { Image, Text, View } from '@ray-js/ray';
5
+ import clsx from 'clsx';
6
+ import Strings from '../../i18n';
7
+ import './captureResult.less';
8
+ const THUMBNAIL_LAYERS = ['back', 'middle', 'front'];
9
+ const TEXT_KEYS = {
10
+ record: {
11
+ check: 'ipc_player_record_success_check',
12
+ multi: 'ipc_player_record_multi_success_tip',
13
+ single: 'ipc_player_record_success_tip'
14
+ },
15
+ screenshot: {
16
+ check: 'ipc_player_shot_success_check',
17
+ multi: 'ipc_player_screenshot_multi_success_tip',
18
+ single: 'ipc_player_screenshot_success_tip'
19
+ }
20
+ };
21
+ export function CaptureResultToast(_ref) {
22
+ let {
23
+ brandColor,
24
+ count,
25
+ kind,
26
+ onCheck,
27
+ thumbnailPath
28
+ } = _ref;
29
+ const isMultiLens = count > 0;
30
+ const textKeys = TEXT_KEYS[kind];
31
+ return /*#__PURE__*/React.createElement(View, {
32
+ className: "ipc-player-plugin-capture-result-toast"
33
+ }, /*#__PURE__*/React.createElement(View, {
34
+ className: clsx('ipc-player-plugin-capture-result-image-box', {
35
+ 'ipc-player-plugin-capture-result-image-box-multi': isMultiLens
36
+ })
37
+ }, isMultiLens ? THUMBNAIL_LAYERS.slice(0, count).map((layer, index) => /*#__PURE__*/React.createElement(Image, {
38
+ key: layer,
39
+ className: "ipc-player-plugin-capture-result-image",
40
+ src: thumbnailPath,
41
+ style: {
42
+ left: `${index * 5}px`,
43
+ zIndex: index + 1
44
+ }
45
+ })) : /*#__PURE__*/React.createElement(Image, {
46
+ className: "ipc-player-plugin-capture-result-image",
47
+ src: thumbnailPath
48
+ }), kind === 'record' && /*#__PURE__*/React.createElement(View, {
49
+ className: "ipc-player-plugin-capture-result-play-icon-box"
50
+ }, /*#__PURE__*/React.createElement(Text, {
51
+ className: clsx('icon-panel', 'icon-panel-record-play', 'ipc-player-plugin-capture-result-play-icon')
52
+ }))), /*#__PURE__*/React.createElement(View, {
53
+ className: "ipc-player-plugin-capture-result-title"
54
+ }, isMultiLens ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, null, Strings.getLang(textKeys.multi)), /*#__PURE__*/React.createElement(Text, {
55
+ className: "ipc-player-plugin-capture-result-count"
56
+ }, Strings.formatString(Strings.getLang('ipc_player_multi_capture_success_count'), count))) : Strings.getLang(textKeys.single)), /*#__PURE__*/React.createElement(View, {
57
+ onClick: onCheck,
58
+ className: "ipc-player-plugin-capture-result-check",
59
+ style: {
60
+ color: brandColor,
61
+ borderColor: brandColor
62
+ }
63
+ }, Strings.getLang(textKeys.check)));
64
+ }
@@ -0,0 +1,85 @@
1
+ .ipc-player-plugin-capture-result-toast {
2
+ z-index: 100;
3
+ display: flex;
4
+ flex: 1;
5
+ align-items: center;
6
+ padding: calc(8px * var(--ipc-player-size-scale, 1))
7
+ calc(12px * var(--ipc-player-size-scale, 1));
8
+ background-color: var(--shot-card-bg-color);
9
+ border-radius: calc(8px * var(--ipc-player-size-scale, 1));
10
+ }
11
+
12
+ .ipc-player-plugin-capture-result-image-box {
13
+ position: relative;
14
+ width: calc(66px * var(--ipc-player-size-scale, 1));
15
+ height: calc(66px * 0.57 * var(--ipc-player-size-scale, 1));
16
+ overflow: hidden;
17
+ border-radius: calc(4px * var(--ipc-player-size-scale, 1));
18
+ }
19
+
20
+ .ipc-player-plugin-capture-result-image {
21
+ width: 100%;
22
+ height: 100%;
23
+ object-fit: cover;
24
+ }
25
+
26
+ .ipc-player-plugin-capture-result-image-box-multi .ipc-player-plugin-capture-result-image {
27
+ position: absolute;
28
+ top: 0;
29
+ width: calc(56px * var(--ipc-player-size-scale, 1));
30
+ box-sizing: border-box;
31
+ border: 1px solid var(--shot-card-bg-color);
32
+ border-radius: calc(4px * var(--ipc-player-size-scale, 1));
33
+ }
34
+
35
+ .ipc-player-plugin-capture-result-play-icon-box {
36
+ position: absolute;
37
+ top: 50%;
38
+ left: 50%;
39
+ z-index: 4;
40
+ display: flex;
41
+ align-items: center;
42
+ justify-content: center;
43
+ width: 30px;
44
+ height: 30px;
45
+ overflow: hidden;
46
+ background-color: rgba(0, 0, 0, 0.2);
47
+ border-radius: 50%;
48
+ transform: translate(-50%, -50%);
49
+ }
50
+
51
+ .ipc-player-plugin-capture-result-play-icon {
52
+ color: #ffffff;
53
+ font-size: calc(18px * var(--ipc-player-size-scale, 1)) !important;
54
+ }
55
+
56
+ .ipc-player-plugin-capture-result-title {
57
+ display: flex;
58
+ flex: 1;
59
+ flex-direction: column;
60
+ margin-right: calc(12px * var(--ipc-player-size-scale, 1));
61
+ margin-left: calc(12px * var(--ipc-player-size-scale, 1));
62
+ color: var(--fontColor);
63
+ font-size: calc(14px * var(--ipc-player-size-scale, 1));
64
+ word-break: break-word;
65
+ }
66
+
67
+ .ipc-player-plugin-capture-result-count {
68
+ margin-top: calc(2px * var(--ipc-player-size-scale, 1));
69
+ color: var(--fontColor);
70
+ font-size: calc(12px * var(--ipc-player-size-scale, 1));
71
+ opacity: 0.5;
72
+ }
73
+
74
+ .ipc-player-plugin-capture-result-check {
75
+ min-width: calc(52px * var(--ipc-player-size-scale, 1));
76
+ max-width: calc(100px * var(--ipc-player-size-scale, 1));
77
+ padding: calc(5px * var(--ipc-player-size-scale, 1))
78
+ calc(11px * var(--ipc-player-size-scale, 1));
79
+ overflow: hidden;
80
+ font-size: calc(14px * var(--ipc-player-size-scale, 1));
81
+ white-space: nowrap;
82
+ text-overflow: ellipsis;
83
+ border: 1px solid #1989fa;
84
+ border-radius: calc(4px * var(--ipc-player-size-scale, 1));
85
+ }
@@ -1,10 +1,12 @@
1
1
  import React, { useState, useEffect, useRef, useContext } from 'react';
2
- import { View, Text, Image, getSystemInfoSync } from '@ray-js/ray';
2
+ import { View, Text, getSystemInfoSync } from '@ray-js/ray';
3
3
  import { useSetState, useUpdateEffect } from 'ahooks';
4
4
  import clsx from 'clsx';
5
5
  import { goToIpcPageNativeRoute } from '@ray-js/ray-ipc-utils';
6
6
  import Strings from '../../i18n';
7
7
  import { PlayState } from '../../interface';
8
+ import { getCaptureResultSummary } from '../../utils/captureResult';
9
+ import { CaptureResultToast } from '../captureResult/captureResult';
8
10
  import { UIEventContext } from '../../ui/context';
9
11
  import { pauseTimeToHideAllComponent, changeIgnoreHideStopPreview, widgetLabs, widgetClick } from '../../ui/constant';
10
12
  import { useStore, getDefaultStore } from '../../ctx/store';
@@ -102,20 +104,34 @@ export function RecordVideo(props) {
102
104
  const [state, setState] = useSetState({
103
105
  showTimer: false,
104
106
  showShot: false,
105
- shotUrl: ''
107
+ shotUrl: '',
108
+ shotCount: 0
106
109
  });
110
+ const showRecordResult = recordInfo => {
111
+ const summary = getCaptureResultSummary(recordInfo);
112
+ if (!summary) return;
113
+ setState({
114
+ showTimer: false,
115
+ showShot: true,
116
+ shotUrl: summary.thumbnailPath,
117
+ shotCount: summary.isMultiLens ? summary.count : 0
118
+ });
119
+ };
107
120
 
108
121
  /**
109
122
  * 针对 playState为非连接中状态时,如果处于录制中, 结束录制
110
123
  */
111
124
  useEffect(() => {
112
125
  (async () => {
113
- if (playState !== PlayState.PLAYING) {
114
- if (recording) {
126
+ if (playState !== PlayState.PLAYING && recording) {
127
+ try {
115
128
  setState({
116
129
  showTimer: false
117
130
  });
118
- await setRecording(false);
131
+ const recordInfo = await setRecording(false);
132
+ showRecordResult(recordInfo);
133
+ } catch {
134
+ // setRecording reports the native failure through the configured toast.
119
135
  }
120
136
  }
121
137
  })();
@@ -129,7 +145,8 @@ export function RecordVideo(props) {
129
145
  timer.current = setTimeout(() => {
130
146
  setState({
131
147
  showShot: false,
132
- shotUrl: ''
148
+ shotUrl: '',
149
+ shotCount: 0
133
150
  });
134
151
  deleteContent('absolute', RECORD_VIDEO_SUCCESS_TOAST_ID);
135
152
  }, CLOSE_TIME);
@@ -164,7 +181,8 @@ export function RecordVideo(props) {
164
181
  const handCheck = () => {
165
182
  setState({
166
183
  showShot: false,
167
- shotUrl: ''
184
+ shotUrl: '',
185
+ shotCount: 0
168
186
  });
169
187
  deleteContent('absolute', RECORD_VIDEO_SUCCESS_TOAST_ID);
170
188
  clearInterval(timer.current);
@@ -186,27 +204,13 @@ export function RecordVideo(props) {
186
204
  marginLeft: screenType === 'vertical' ? '0' : '-200px'
187
205
  },
188
206
  content: () => {
189
- return /*#__PURE__*/React.createElement(View, {
190
- className: "ipc-player-plugin-record-success-toast"
191
- }, /*#__PURE__*/React.createElement(View, {
192
- className: "ipc-player-plugin-record-success-toast-image-box"
193
- }, /*#__PURE__*/React.createElement(Image, {
194
- className: "ipc-player-plugin-record-success-toast-image",
195
- src: state.shotUrl
196
- }), /*#__PURE__*/React.createElement(View, {
197
- className: "ipc-player-plugin-record-success-play-icon-box"
198
- }, /*#__PURE__*/React.createElement(Text, {
199
- className: clsx('icon-panel', 'icon-panel-record-play', 'ipc-player-plugin-record-success-play-icon')
200
- }))), /*#__PURE__*/React.createElement(View, {
201
- className: "ipc-player-plugin-record-success-toast-title"
202
- }, Strings.getLang('ipc_player_record_success_tip')), /*#__PURE__*/React.createElement(View, {
203
- onClick: handCheck,
204
- className: "ipc-player-plugin-record-success-toast-bottom",
205
- style: {
206
- color: brandColor,
207
- borderColor: brandColor
208
- }
209
- }, Strings.getLang('ipc_player_record_success_check')));
207
+ return /*#__PURE__*/React.createElement(CaptureResultToast, {
208
+ brandColor: brandColor,
209
+ count: state.shotCount,
210
+ kind: "record",
211
+ onCheck: handCheck,
212
+ thumbnailPath: state.shotUrl
213
+ });
210
214
  }
211
215
  });
212
216
  timeToCloseRecordToast();
@@ -230,9 +234,8 @@ export function RecordVideo(props) {
230
234
  });
231
235
  };
232
236
  const handRecordVideo = async () => {
233
- var _props$event, _props$event2;
234
- (_props$event = props.event) !== null && _props$event !== void 0 ? _props$event : event;
235
- ((_props$event2 = props.event) !== null && _props$event2 !== void 0 ? _props$event2 : event).emit(widgetClick, {
237
+ var _props$event;
238
+ ((_props$event = props.event) !== null && _props$event !== void 0 ? _props$event : event).emit(widgetClick, {
236
239
  widgetId: widgetLabs.RECORD_VIDEO
237
240
  });
238
241
  event.emit(pauseTimeToHideAllComponent);
@@ -265,11 +268,7 @@ export function RecordVideo(props) {
265
268
  timeToResetRecordDisabled();
266
269
  } else {
267
270
  props.event ? props.event.emit(changeIgnoreHideStopPreview, false) : event.emit(changeIgnoreHideStopPreview, false);
268
- setState({
269
- showTimer: false,
270
- showShot: true,
271
- shotUrl: recordInfo.tempImagePath
272
- });
271
+ showRecordResult(recordInfo);
273
272
  }
274
273
  } catch (err) {
275
274
  props.event ? props.event.emit(changeIgnoreHideStopPreview, false) : event.emit(changeIgnoreHideStopPreview, false);
@@ -1,116 +1,59 @@
1
1
  .ipc-player-plugin-record-video-icon {
2
- font-size: calc(var(--iconPlayerSize) * var(--ipc-player-size-scale, 1)) !important;
3
2
  color: var(--iconColor);
3
+ font-size: calc(var(--iconPlayerSize) * var(--ipc-player-size-scale, 1)) !important;
4
4
  }
5
+
5
6
  .ipc-player-plugin-record-video-toast-wrap {
6
- z-index: 400;
7
7
  position: absolute;
8
8
  top: calc(7px * var(--ipc-player-size-scale, 1));
9
9
  left: 50%;
10
+ z-index: 400;
10
11
  transform: translateX(-50%);
11
12
  }
12
13
 
13
- // 计时器显示
14
14
  .ipc-player-plugin-record-video-toast-show {
15
15
  transform: translate(0, 0) !important;
16
16
  transition: transform ease-in 3s;
17
17
  }
18
- // 计时器隐藏
18
+
19
19
  .ipc-player-plugin-record-video-toast-hide {
20
20
  transform: translate(0, -110%) !important;
21
21
  transition: transform 0.3s ease-in-out;
22
22
  }
23
23
 
24
24
  .ipc-player-plugin-record-video-toast {
25
- transform: translate(0, -110%);
26
25
  display: flex;
27
- padding: calc(12px * var(--ipc-player-size-scale, 1));
28
26
  align-items: center;
29
- background-color: var(--shot-card-bg-color);
27
+ padding: calc(12px * var(--ipc-player-size-scale, 1));
28
+ background: linear-gradient(0deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0.15)),
29
+ #000000;
30
30
  border-radius: calc(8px * var(--ipc-player-size-scale, 1));
31
- // background-color: #fff;
32
- background: linear-gradient(0deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0.15)), #000000;
31
+ transform: translate(0, -110%);
32
+
33
33
  .ipc-player-plugin-record-video-toast-point {
34
34
  width: calc(6px * var(--ipc-player-size-scale, 1));
35
35
  height: calc(6px * var(--ipc-player-size-scale, 1));
36
36
  border-radius: calc(6px * var(--ipc-player-size-scale, 1));
37
37
  }
38
+
38
39
  .ipc-player-plugin-record-video-toast-time {
39
- font-size: calc(12px * var(--ipc-player-size-scale, 1));;
40
- font-weight: 700;
41
- color: var(--fontColor);
42
- margin-left: calc(8px * var(--ipc-player-size-scale, 1));;
43
40
  display: flex;
44
41
  align-items: center;
42
+ margin-left: calc(8px * var(--ipc-player-size-scale, 1));
43
+ color: var(--fontColor);
44
+ font-weight: 700;
45
+ font-size: calc(12px * var(--ipc-player-size-scale, 1));
46
+
45
47
  .ipc-player-plugin-record-video-toast-time-warp {
46
48
  text-align: center;
47
49
  }
50
+
48
51
  .ipc-player-plugin-record-video-toast-time-delimiter {
49
- margin: 0 calc(2px * var(--ipc-player-size-scale, 1));;
52
+ margin: 0 calc(2px * var(--ipc-player-size-scale, 1));
50
53
  }
51
54
  }
52
55
  }
53
56
 
54
- /** 录制成功弹窗 */
55
57
  .ipc-player-plugin-record-success-toast-wrap {
56
58
  position: absolute;
57
59
  }
58
- .ipc-player-plugin-record-success-toast {
59
- flex: 1;
60
- border-radius: calc(8px * var(--ipc-player-size-scale, 1));;
61
- background-color: var(--shot-card-bg-color);
62
- display: flex;
63
- align-items: center;
64
- padding: calc(8px * var(--ipc-player-size-scale, 1)) calc(12px * var(--ipc-player-size-scale, 1));
65
- z-index: 100;
66
- .ipc-player-plugin-record-success-toast-image-box {
67
- border-radius: calc(4px * var(--ipc-player-size-scale, 1));
68
- overflow: hidden;
69
- width: calc(66px * var(--ipc-player-size-scale, 1));
70
- height: calc(66px * 0.57 * var(--ipc-player-size-scale, 1));
71
- position: relative;
72
- }
73
- .ipc-player-plugin-record-success-toast-image {
74
- width: 100%;
75
- height: 100%;
76
- object-fit: cover;
77
- }
78
- .ipc-player-plugin-record-success-play-icon-box {
79
- width: 30px;
80
- height: 30px;
81
- background-color: rgba(0, 0, 0, 0.2);
82
- border-radius: 50%;
83
- overflow: hidden;
84
- position: absolute;
85
- top: 50%;
86
- left: 50%;
87
- transform: translate(-50%, -50%);
88
- display: flex;
89
- align-items: center;
90
- justify-content: center;
91
- }
92
- .ipc-player-plugin-record-success-play-icon {
93
- font-size: calc(18px * var(--ipc-player-size-scale, 1)) !important;
94
- color: #ffffff;
95
- }
96
- .ipc-player-plugin-record-success-toast-title {
97
- flex: 1;
98
- margin-left: calc(12px * var(--ipc-player-size-scale, 1));
99
- font-size: calc(14px * var(--ipc-player-size-scale, 1));
100
- color: var(--fontColor);
101
- margin-right: calc(12px * var(--ipc-player-size-scale, 1));
102
- word-break: break-word;
103
-
104
- }
105
- .ipc-player-plugin-record-success-toast-bottom {
106
- border: 1px solid #1989FA;
107
- min-width: calc(52px * var(--ipc-player-size-scale, 1));
108
- max-width: calc(100px * var(--ipc-player-size-scale, 1));
109
- border-radius: calc(4px * var(--ipc-player-size-scale, 1));
110
- padding: calc(5px * var(--ipc-player-size-scale, 1)) calc(11px * var(--ipc-player-size-scale, 1));
111
- font-size: calc(14px * var(--ipc-player-size-scale, 1));
112
- white-space: nowrap;
113
- overflow: hidden;
114
- text-overflow: ellipsis;
115
- }
116
- }
@@ -1,5 +1,6 @@
1
+ import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
1
2
  import React, { useContext, useRef } from 'react';
2
- import { View, Image, Text, showModal, openAppSystemSettingPage, getSystemInfoSync } from '@ray-js/ray';
3
+ import { View, Text, showModal, openAppSystemSettingPage, getSystemInfoSync } from '@ray-js/ray';
3
4
  import { useSetState, useUpdateEffect, useMemoizedFn } from 'ahooks';
4
5
  import clsx from 'clsx';
5
6
  import { goToIpcPageNativeRoute } from '@ray-js/ray-ipc-utils';
@@ -7,6 +8,8 @@ import Strings from '../../i18n';
7
8
  import { useStore } from '../../ctx/store';
8
9
  import { UIEventContext } from '../../ui/context';
9
10
  import { pauseTimeToHideAllComponent, changeIgnoreHideStopPreview, widgetLabs, widgetClick } from '../../ui/constant';
11
+ import { getCaptureResultSummary } from '../../utils/captureResult';
12
+ import { CaptureResultToast } from '../captureResult/captureResult';
10
13
  import './screenshot.less';
11
14
  const CLOSE_TIME = 5000;
12
15
  export function Screenshot(props) {
@@ -34,7 +37,8 @@ export function Screenshot(props) {
34
37
  /** 记录截屏url */
35
38
  const [state, setState] = useSetState({
36
39
  showShot: false,
37
- shotUrl: ''
40
+ shotUrl: '',
41
+ shotCount: 0
38
42
  });
39
43
  useUpdateEffect(() => {
40
44
  if (state.showShot && screenType) {
@@ -48,7 +52,8 @@ export function Screenshot(props) {
48
52
  timer.current = setTimeout(() => {
49
53
  setState({
50
54
  showShot: false,
51
- shotUrl: ''
55
+ shotUrl: '',
56
+ shotCount: 0
52
57
  });
53
58
  deleteContent('absolute', 'plugin-screenshot-toast');
54
59
  }, CLOSE_TIME);
@@ -58,7 +63,8 @@ export function Screenshot(props) {
58
63
  const handCheck = () => {
59
64
  setState({
60
65
  showShot: false,
61
- shotUrl: ''
66
+ shotUrl: '',
67
+ shotCount: 0
62
68
  });
63
69
  deleteContent('absolute', 'plugin-screenshot-toast');
64
70
  clearInterval(timer.current);
@@ -80,31 +86,20 @@ export function Screenshot(props) {
80
86
  marginLeft: screenType === 'vertical' ? '0' : '-200px'
81
87
  },
82
88
  content: () => {
83
- return /*#__PURE__*/React.createElement(View, {
84
- className: "ipc-player-plugin-screenshot-toast"
85
- }, /*#__PURE__*/React.createElement(View, {
86
- className: "ipc-player-plugin-screenshot-toast-image-box"
87
- }, /*#__PURE__*/React.createElement(Image, {
88
- className: "ipc-player-plugin-screenshot-toast-image",
89
- src: state.shotUrl
90
- })), /*#__PURE__*/React.createElement(View, {
91
- className: "ipc-player-plugin-screenshot-toast-title"
92
- }, Strings.getLang('ipc_player_screenshot_success_tip')), /*#__PURE__*/React.createElement(View, {
93
- onClick: handCheck,
94
- className: "ipc-player-plugin-screenshot-toast-bottom",
95
- style: {
96
- color: brandColor,
97
- borderColor: brandColor
98
- }
99
- }, Strings.getLang('ipc_player_shot_success_check')));
89
+ return /*#__PURE__*/React.createElement(CaptureResultToast, {
90
+ brandColor: brandColor,
91
+ count: state.shotCount,
92
+ kind: "screenshot",
93
+ onCheck: handCheck,
94
+ thumbnailPath: state.shotUrl
95
+ });
100
96
  }
101
97
  });
102
98
  timeToCloseToast();
103
99
  };
104
100
  const handClick = useMemoizedFn(() => {
105
- var _props$event, _props$event2;
106
- (_props$event = props.event) !== null && _props$event !== void 0 ? _props$event : event;
107
- ((_props$event2 = props.event) !== null && _props$event2 !== void 0 ? _props$event2 : event).emit(widgetClick, {
101
+ var _props$event;
102
+ ((_props$event = props.event) !== null && _props$event !== void 0 ? _props$event : event).emit(widgetClick, {
108
103
  widgetId: widgetLabs.SCREENSHOT
109
104
  });
110
105
  console.log('截屏!!!');
@@ -118,56 +113,53 @@ export function Screenshot(props) {
118
113
  props.event ? props.event.emit(changeIgnoreHideStopPreview, true) : event.emit(changeIgnoreHideStopPreview, true);
119
114
  }
120
115
  event.emit(pauseTimeToHideAllComponent);
116
+ const setIgnoreHideStopPreview = value => {
117
+ var _props$event2;
118
+ ((_props$event2 = props.event) !== null && _props$event2 !== void 0 ? _props$event2 : event).emit(changeIgnoreHideStopPreview, value);
119
+ };
120
+ const handleSnapshotFail = err => {
121
+ setIgnoreHideStopPreview(false);
122
+ console.log(err, 'err');
123
+ const errObj = err.innerError || err || {};
124
+ const errMsg = errObj.errorMsg || Strings.getLang('ipc_player_error_screenshot_error_tip');
125
+ toast({
126
+ title: errMsg
127
+ });
128
+ };
129
+ const takeSnapshot = () => {
130
+ IPCPlayerContext.snapshot(_objectSpread(_objectSpread({
131
+ saveToAlbum
132
+ }, props.multiLens ? {
133
+ multiLens: true
134
+ } : {}), {}, {
135
+ success: res => {
136
+ setIgnoreHideStopPreview(false);
137
+ const summary = getCaptureResultSummary(res);
138
+ if (!summary) {
139
+ handleSnapshotFail({});
140
+ return;
141
+ }
142
+ setState({
143
+ showShot: true,
144
+ shotUrl: summary.thumbnailPath,
145
+ shotCount: summary.isMultiLens ? summary.count : 0
146
+ });
147
+ },
148
+ fail: handleSnapshotFail
149
+ }));
150
+ };
121
151
  ty.authorizeStatus({
122
152
  // 相册写入权限
123
153
  scope: 'scope.writePhotosAlbum',
124
154
  success: () => {
125
- props.event ? props.event.emit(changeIgnoreHideStopPreview, false) : event.emit(changeIgnoreHideStopPreview, false);
126
- IPCPlayerContext.snapshot({
127
- saveToAlbum,
128
- // 保存到 IPC 相册
129
- success: res => {
130
- console.log(res, 'res');
131
- props.event ? props.event.emit(changeIgnoreHideStopPreview, false) : event.emit(changeIgnoreHideStopPreview, false);
132
- setState({
133
- showShot: true,
134
- shotUrl: res.tempImagePath
135
- });
136
- },
137
- fail: err => {
138
- props.event ? props.event.emit(changeIgnoreHideStopPreview, false) : event.emit(changeIgnoreHideStopPreview, false);
139
- console.log(err, 'err');
140
- const errObj = err.innerError || err || {};
141
- const errMsg = errObj.errorMsg || Strings.getLang('ipc_player_error_screenshot_error_tip');
142
- toast({
143
- title: errMsg
144
- });
145
- }
146
- });
155
+ setIgnoreHideStopPreview(false);
156
+ takeSnapshot();
147
157
  },
148
158
  fail: () => {
149
159
  ty.authorize({
150
160
  scope: 'scope.writePhotosAlbum',
151
161
  success: () => {
152
- IPCPlayerContext.snapshot({
153
- saveToAlbum,
154
- // 保存到 IPC 相册
155
- success: res => {
156
- console.log(res, 'res');
157
- setState({
158
- showShot: true,
159
- shotUrl: res.tempImagePath
160
- });
161
- },
162
- fail: err => {
163
- console.log(err, 'err');
164
- const errObj = err.innerError || err || {};
165
- const errMsg = errObj.errorMsg || Strings.getLang('ipc_player_error_screenshot_error_tip');
166
- toast({
167
- title: errMsg
168
- });
169
- }
170
- });
162
+ takeSnapshot();
171
163
  },
172
164
  fail: () => {
173
165
  showModal({
@@ -2,46 +2,7 @@
2
2
  color: var(--iconColor);
3
3
  font-size: calc(var(--iconPlayerSize) * var(--ipc-player-size-scale, 1)) !important;
4
4
  }
5
+
5
6
  .ipc-player-plugin-screenshot-toast-wrap {
6
7
  position: absolute;
7
8
  }
8
- .ipc-player-plugin-screenshot-toast {
9
- flex: 1;
10
- border-radius: calc(8px * var(--ipc-player-size-scale, 1));;
11
- background-color: var(--shot-card-bg-color);
12
- display: flex;
13
- align-items: center;
14
- padding: calc(8px * var(--ipc-player-size-scale, 1)) calc(12px * var(--ipc-player-size-scale, 1));
15
- z-index: 100;
16
- .ipc-player-plugin-screenshot-toast-image-box {
17
- border-radius: calc(4px * var(--ipc-player-size-scale, 1));
18
- overflow: hidden;
19
- width: calc(66px * var(--ipc-player-size-scale, 1));
20
- height: calc(66px * 0.57 * var(--ipc-player-size-scale, 1));
21
- }
22
- .ipc-player-plugin-screenshot-toast-image {
23
- width: 100%;
24
- height: 100%;
25
- object-fit: cover;
26
- }
27
- .ipc-player-plugin-screenshot-toast-title {
28
- flex: 1;
29
- margin-left: calc(12px * var(--ipc-player-size-scale, 1));
30
- font-size: calc(14px * var(--ipc-player-size-scale, 1));
31
- color: var(--fontColor);
32
- margin-right: calc(12px * var(--ipc-player-size-scale, 1));
33
- word-break: break-word;
34
-
35
- }
36
- .ipc-player-plugin-screenshot-toast-bottom {
37
- border: 1px solid #1989FA;
38
- min-width: calc(52px * var(--ipc-player-size-scale, 1));
39
- max-width: calc(100px * var(--ipc-player-size-scale, 1));
40
- border-radius: calc(4px * var(--ipc-player-size-scale, 1));
41
- padding: calc(5px * var(--ipc-player-size-scale, 1)) calc(11px * var(--ipc-player-size-scale, 1));
42
- font-size: calc(14px * var(--ipc-player-size-scale, 1));
43
- white-space: nowrap;
44
- overflow: hidden;
45
- text-overflow: ellipsis;
46
- }
47
- }
@@ -126,10 +126,7 @@ export const isSwitchChannelSupported = () => {
126
126
  const supported = typeof ((_ty = ty) === null || _ty === void 0 || (_ty = _ty.ipc) === null || _ty === void 0 ? void 0 : _ty.switchChannel) === 'function';
127
127
  if (!supported && !switchChannelMissingLogged) {
128
128
  switchChannelMissingLogged = true;
129
- // 只能用 log:本判定会在 render 期被调用,而 IDE 宿主下 render 期的 console.warn
130
- // 会打坏 Ray 运行时,整个面板崩在 areHookInputsEqual / TDZ —— 且症状伪装成
131
- // 「hook 顺序错位」(报 change in the order of Hooks),极难归因,勿改回 warn
132
- console.log('[zoomTurntable] 宿主不支持 ty.ipc.switchChannel,焦距转盘入口不展示');
129
+ console.warn('[zoomTurntable] 宿主不支持 ty.ipc.switchChannel,焦距转盘入口不展示');
133
130
  dumpChannelApis();
134
131
  }
135
132
  return supported;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ray-js/ipc-player-integration",
3
- "version": "0.0.51",
3
+ "version": "0.0.52-beta.0",
4
4
  "description": "IPC 融合播放器",
5
5
  "main": "lib/index",
6
6
  "files": [