quickblox-react-ui-kit 0.4.2-beta.6 → 0.4.2-beta.7

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.
@@ -3,6 +3,7 @@ import '../Presentation/Views/Dialog/Dialog.scss';
3
3
  import '../Presentation/Views/Dialog/DialogHeader/DialogInfoIcon/DialogInfoIcon.scss';
4
4
  import { Tone } from 'qb-ai-rephrase/src/Tone';
5
5
  import { toast } from 'react-toastify';
6
+ import QBMediaRecorder from 'media-recorder-js';
6
7
  import useQbInitializedDataContext from '../Presentation/providers/QuickBloxUIKitProvider/useQbInitializedDataContext';
7
8
  import { DialogEntity } from '../Domain/entity/DialogEntity';
8
9
  import { DialogListViewModel } from '../Presentation/Views/DialogList/DialogListViewModel';
@@ -41,7 +42,6 @@ import { UsersListViewModel } from '../Presentation/Views/DialogInfo/UsersList/U
41
42
 
42
43
  interface QuickBloxUIKitReturn {
43
44
  constants: {
44
- mimeType: string;
45
45
  messagePerPage: number;
46
46
  maxFileSize?: number;
47
47
  maxWidthToResizing: string;
@@ -65,9 +65,9 @@ interface QuickBloxUIKitReturn {
65
65
  showDialogInformation: boolean;
66
66
  needDialogInformation: boolean;
67
67
  isRecording: boolean;
68
- stream?: MediaStream;
68
+ stream?: MediaStream | null;
69
69
  permission: boolean;
70
- resultAudioBlob?: Blob;
70
+ resultAudioBlob?: Blob | null;
71
71
  audioChunks: Blob[];
72
72
  fileToSend?: File | null;
73
73
  messageText: string;
@@ -78,7 +78,6 @@ interface QuickBloxUIKitReturn {
78
78
  defaultAITranslateWidget?: AIMessageWidget;
79
79
  defaultAIAssistWidget?: AIMessageWidget;
80
80
  rephraseTones: Tone[];
81
- mimeType: string;
82
81
  messagePerPage: number;
83
82
  enableForwarding: boolean;
84
83
  enableReplying: boolean;
@@ -146,7 +145,7 @@ export default function useQuickBloxUIKit({
146
145
  uikitHeightOffset = '0px',
147
146
  }: QuickBloxUIKitProps): QuickBloxUIKitReturn {
148
147
  // 103
149
- const mimeType = 'audio/webm;codecs=opus'; // audio/ogg audio/mpeg audio/webm audio/x-wav audio/mp4
148
+ // const mimeType = 'audio/webm;codecs=opus'; // audio/ogg audio/mpeg audio/webm audio/x-wav audio/mp4
150
149
  const messagePerPage = 47;
151
150
 
152
151
  const currentContext = useQbInitializedDataContext();
@@ -372,10 +371,14 @@ export default function useQuickBloxUIKit({
372
371
  const [fileToSend, setFileToSend] = useState<File | null>(null);
373
372
  const [isRecording, setIsRecording] = useState(false);
374
373
  const [permission, setPermission] = useState(false);
375
- const [stream, setStream] = useState<MediaStream>();
376
- const mediaRecorder = useRef<MediaRecorder>();
377
- const [resultAudioBlob, setResultAudioBlob] = useState<Blob>();
378
- const [audioChunks, setAudioChunks] = useState<Array<Blob>>([]);
374
+ //
375
+ const [stream, setStream] = useState<MediaStream | null>(null);
376
+ const mediaRecorder = useRef<QBMediaRecorder | null>(null);
377
+ const [resultAudioBlob, setResultAudioBlob] = useState<Blob | null>(null);
378
+ const [audioChunks, setAudioChunks] = useState<Blob[]>([]);
379
+ const [peerConnection, setPeerConnection] =
380
+ useState<RTCPeerConnection | null>(null);
381
+ //
379
382
  const newModal = useModal();
380
383
  const [dialogToLeave, setDialogToLeave] = useState<DialogEntity>();
381
384
  const [showDialogList, setShowDialogList] = useState<boolean>(true);
@@ -487,191 +490,158 @@ export default function useQuickBloxUIKit({
487
490
  });
488
491
  };
489
492
 
490
- const getMicrophonePermission = async () => {
491
- if (window) {
492
- try {
493
- const mediaStream = await navigator.mediaDevices.getUserMedia({
494
- audio: true,
495
- video: false,
496
- });
497
-
498
- setPermission(true);
499
- setStream(mediaStream);
500
- } catch (err) {
501
- showErrorMessage(
502
- `The MediaRecorder API throws exception ${stringifyError(err)} .`,
503
- );
504
- }
505
- } else {
506
- showErrorMessage(
507
- 'The MediaRecorder API is not supported in your browser.',
508
- );
509
- }
510
- };
511
-
512
- // eslint-disable-next-line @typescript-eslint/no-unused-vars,@typescript-eslint/require-await
513
- const startRecording = async () => {
514
- if (!stream) return;
515
-
516
- // Определение браузера
493
+ // Detect browser and set MIME type
494
+ const detectBrowserAndMimeType = () => {
517
495
  const userAgent = navigator.userAgent.toLowerCase();
518
496
  const isChrome =
519
497
  /chrome/.test(userAgent) && !/edge|opr|brave/.test(userAgent);
520
498
  const isSafari = /^((?!chrome|android).)*safari/.test(userAgent);
521
499
  const isFirefox = /firefox/.test(userAgent);
522
- const isOther = !isChrome && !isSafari && !isFirefox;
500
+ const isIOS = /iphone|ipad|ipod/.test(userAgent);
523
501
 
524
- // eslint-disable-next-line no-nested-ternary
525
502
  console.log(
526
- 'Browser is:',
527
- // eslint-disable-next-line no-nested-ternary
528
- isChrome
529
- ? 'Chrome'
530
- : // eslint-disable-next-line no-nested-ternary
531
- isSafari
532
- ? 'Safari'
533
- : isFirefox
534
- ? 'Firefox'
535
- : 'Other',
503
+ `Browser detected: ${
504
+ // eslint-disable-next-line no-nested-ternary
505
+ isChrome
506
+ ? 'Chrome'
507
+ : // eslint-disable-next-line no-nested-ternary
508
+ isSafari
509
+ ? 'Safari'
510
+ : // eslint-disable-next-line no-nested-ternary
511
+ isFirefox
512
+ ? 'Firefox'
513
+ : isIOS
514
+ ? 'iOS'
515
+ : 'Other'
516
+ }`,
536
517
  );
537
518
 
538
519
  const mimeTypes = {
539
520
  chrome: ['audio/webm;codecs=opus', 'audio/webm'],
540
- safari: ['audio/wav', 'audio/aac'],
521
+ safari: ['audio/mp4', 'audio/mp4;codecs=mp4a', 'audio/aac', 'audio/wav'],
522
+ ios: ['audio/mp4', 'audio/mp4;codecs=mp4a', 'audio/aac'],
541
523
  firefox: ['audio/ogg', 'audio/webm'],
542
- other: ['audio/mp3', 'audio/wav', 'audio/webm'],
524
+ other: ['audio/webm', 'audio/mp4', 'audio/wav'],
543
525
  };
544
526
 
545
- let mimeContent = 'audio/webm;codecs=opus';
546
-
547
- if (isChrome) {
548
- mimeContent =
549
- mimeTypes.chrome.find((type) => MediaRecorder.isTypeSupported(type)) ||
550
- 'audio/webm';
551
- } else if (isSafari) {
552
- mimeContent =
553
- mimeTypes.safari.find((type) => MediaRecorder.isTypeSupported(type)) ||
554
- 'audio/wav';
555
- } else if (isFirefox) {
556
- mimeContent =
557
- mimeTypes.firefox.find((type) => MediaRecorder.isTypeSupported(type)) ||
558
- 'audio/webm';
559
- } else if (isOther) {
560
- mimeContent =
561
- mimeTypes.other.find((type) => MediaRecorder.isTypeSupported(type)) ||
562
- 'audio/wav';
563
- }
527
+ // eslint-disable-next-line no-nested-ternary
528
+ const targetMimeTypes = isIOS
529
+ ? mimeTypes.ios
530
+ : // eslint-disable-next-line no-nested-ternary
531
+ isSafari
532
+ ? mimeTypes.safari
533
+ : // eslint-disable-next-line no-nested-ternary
534
+ isChrome
535
+ ? mimeTypes.chrome
536
+ : isFirefox
537
+ ? mimeTypes.firefox
538
+ : mimeTypes.other;
539
+
540
+ return (
541
+ targetMimeTypes.find((type) => QBMediaRecorder.isTypeSupported(type)) ||
542
+ 'audio/wav'
543
+ );
544
+ };
564
545
 
565
- console.log(`Selected MIME-type: ${mimeContent}`);
546
+ // Request microphone access and setup WebRTC
547
+ const getMicrophonePermission = async () => {
548
+ if (!window) {
549
+ showErrorMessage(
550
+ 'The MediaRecorder API is not supported in your browser.',
551
+ );
552
+
553
+ return;
554
+ }
566
555
 
567
556
  try {
568
- const media = new MediaRecorder(stream, { mimeType: mimeContent });
557
+ const mediaStream = await navigator.mediaDevices.getUserMedia({
558
+ audio: true,
559
+ });
569
560
 
570
- mediaRecorder.current = media;
571
- mediaRecorder.current.start();
561
+ // Create WebRTC peer connection
562
+ const pc = new RTCPeerConnection();
572
563
 
573
- const localAudioChunks: Blob[] = [];
564
+ mediaStream
565
+ .getTracks()
566
+ .forEach((track) => pc.addTrack(track, mediaStream));
574
567
 
575
- mediaRecorder.current.ondataavailable = (event) => {
576
- if (event.data.size > 0) {
577
- localAudioChunks.push(event.data);
578
- }
568
+ pc.ontrack = (event) => {
569
+ setStream(event.streams[0]);
579
570
  };
580
571
 
581
- setAudioChunks(localAudioChunks);
582
- } catch (error) {
583
- console.error('Ошибка при создании MediaRecorder:', error);
572
+ setPeerConnection(pc);
573
+ setStream(mediaStream);
574
+ setPermission(true);
575
+ console.log('Microphone access granted, WebRTC connection established.');
576
+ } catch (err) {
577
+ showErrorMessage(
578
+ `The MediaRecorder API throws exception ${stringifyError(err)}.`,
579
+ );
584
580
  }
585
581
  };
586
- // previous version - startRecording:
587
- // const startRecording = async () => {
588
- // if (!stream) return;
589
- // const mimeTypes = [
590
- // 'audio/aac',
591
- // 'audio/mp4',
592
- // 'audio/mpeg',
593
- // 'audio/ogg',
594
- // 'audio/wav',
595
- // 'audio/webm',
596
- // 'audio/3gpp',
597
- // 'audio/flac',
598
- // 'audio/x-aiff',
599
- // 'audio/x-m4a',
600
- // ];
601
- //
602
- // console.log('MIME TYPES: ');
603
- // mimeTypes.forEach((mType) => {
604
- // if (MediaRecorder.isTypeSupported(mimeType)) {
605
- // console.log(`${mType} is supported`);
606
- // } else {
607
- // console.log(`${mType} is not supported`);
608
- // }
609
- // });
610
- // // audio/mp4;codecs=mp4a audio/webm;codecs=opus audio/webm;codecs=vp9,opus
611
- // const mimeContent = window.MediaRecorder.isTypeSupported('audio/mp4')
612
- // ? 'audio/mp4;codecs=mp4a'
613
- // : 'audio/webm;codecs=opus';
614
- //
615
- // const media = new MediaRecorder(stream, { mimeType: mimeContent });
616
- //
617
- // mediaRecorder.current = media;
618
- // mediaRecorder.current.start();
619
- //
620
- // const localAudioChunks: any[] = [];
621
- //
622
- // mediaRecorder.current.ondataavailable = (event) => {
623
- // if (typeof event.data === 'undefined') return;
624
- // if (event.data.size === 0) return;
625
- // localAudioChunks.push(event.data);
626
- // };
627
- //
628
- // setAudioChunks(localAudioChunks);
629
- // };
630
582
 
631
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
632
- const stopRecording = () => {
633
- if (!mediaRecorder.current) return;
634
- mediaRecorder.current.stop();
583
+ // Start recording using QBMediaRecorder
584
+ // eslint-disable-next-line @typescript-eslint/require-await
585
+ const startRecording = async () => {
586
+ if (!stream) return;
635
587
 
636
- mediaRecorder.current.onstop = () => {
637
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
638
- const mimeContent = window.MediaRecorder.isTypeSupported(
639
- 'audio/mp4;codecs=mp4a',
640
- )
641
- ? 'audio/mp4;codecs=mp4a'
642
- : 'audio/webm;codecs=opus';
643
- // const audioBlob = new Blob(audioChunks, { type: mimeContent }); // mimeType
644
- // const mp4Blob = new Blob(recordedChunks, { type: 'video/mp4' });
588
+ const mimeType = detectBrowserAndMimeType();
645
589
 
646
- // const audioBlob = new Blob(audioChunks, { type: 'video/mp4' }); // mimeType
647
- // const audioBlob = new Blob(audioChunks, { type: 'audio/mp4' }); // mimeType
648
- const audioBlob = new Blob(audioChunks, { type: 'audio/mp4' });
590
+ console.log(`Selected MIME-type: ${mimeType}`);
649
591
 
650
- setResultAudioBlob(audioBlob);
592
+ const recorder = new QBMediaRecorder({
593
+ mimeType,
594
+ timeslice: 1000, // Chunks of 1 second
595
+ ignoreMutedMedia: true,
596
+ onstart: () => console.log('Recording started'),
597
+ onstop: (file) => {
598
+ console.log('Final audio file:', file);
599
+ setResultAudioBlob(file);
600
+ setAudioChunks([]); // Clear recorded chunks
601
+ },
602
+ ondataavailable: (event) => {
603
+ if (event.data.size > 0) {
604
+ setAudioChunks((prev) => [...prev, event.data]);
605
+ }
606
+ },
607
+ onerror: (error) => console.error('Recording error:', error),
608
+ });
651
609
 
652
- setAudioChunks([]);
653
- //
654
- stream?.getAudioTracks().forEach((track) => {
655
- track.stop();
656
- });
657
- setPermission(false);
658
- //
659
- };
610
+ mediaRecorder.current = recorder;
611
+ recorder.start(stream);
660
612
  };
661
613
 
662
- const blobToFile = (theBlob: Blob, fileName: string): File => {
663
- const b: any = theBlob;
614
+ // Stop recording
615
+ const stopRecording = () => {
616
+ if (!mediaRecorder.current) return;
664
617
 
665
- // A Blob() is almost a File() - it's just missing the two properties below which we will add
666
- b.lastModifiedDate = new Date();
667
- b.name = fileName;
618
+ mediaRecorder.current.stop();
668
619
 
669
- // Cast to a File() type
670
- const resultFile = theBlob as unknown as File;
620
+ // Stop WebRTC stream
621
+ if (peerConnection) {
622
+ peerConnection.close();
623
+ setPeerConnection(null);
624
+ }
625
+ };
671
626
 
672
- return resultFile;
627
+ // Convert Blob to File
628
+ const blobToFile = (blob: Blob, fileName: string): File => {
629
+ return new File([blob], fileName, { type: blob.type });
673
630
  };
674
631
 
632
+ // const blobToFile = (theBlob: Blob, fileName: string): File => {
633
+ // const b: any = theBlob;
634
+ //
635
+ // // A Blob() is almost a File() - it's just missing the two properties below which we will add
636
+ // b.lastModifiedDate = new Date();
637
+ // b.name = fileName;
638
+ //
639
+ // // Cast to a File() type
640
+ // const resultFile = theBlob as unknown as File;
641
+ //
642
+ // return resultFile;
643
+ // };
644
+
675
645
  function sendTextMessageActions(textToSend: string) {
676
646
  if (isOnline) {
677
647
  // closeReplyMessageFlowHandler
@@ -1081,7 +1051,6 @@ export default function useQuickBloxUIKit({
1081
1051
  // 972
1082
1052
  return {
1083
1053
  constants: {
1084
- mimeType,
1085
1054
  messagePerPage,
1086
1055
  maxFileSize,
1087
1056
  maxWidthToResizing,
@@ -1116,7 +1085,6 @@ export default function useQuickBloxUIKit({
1116
1085
  defaultAIRephraseWidget,
1117
1086
  defaultAITranslateWidget,
1118
1087
  defaultAIAssistWidget,
1119
- mimeType,
1120
1088
  messagePerPage,
1121
1089
  maxTokensForAIRephrase,
1122
1090
  rephraseTones,
package/tsconfig.json CHANGED
@@ -26,7 +26,8 @@
26
26
  },
27
27
  "include": [
28
28
  "src",
29
- "global.d.ts"
29
+ "global.d.ts",
30
+ "media-recorder-js.d.ts"
30
31
  ],
31
32
  "exclude": ["node_modules", "dist"]
32
33
 
package/webpack.config.js CHANGED
@@ -15,6 +15,10 @@ module.exports = {
15
15
  },
16
16
  resolve: {
17
17
  extensions: ['.ts', '.tsx'],
18
+ alias: {
19
+ './errors': path.resolve(__dirname, 'node_modules/media-recorder-js/src/errors.js'),
20
+ './mimeTypes': path.resolve(__dirname, 'node_modules/media-recorder-js/src/mimeTypes.js'),
21
+ },
18
22
  },
19
23
  externals: {
20
24
  "react": "react",
@@ -29,7 +33,16 @@ module.exports = {
29
33
  // Creates style nodes from JS strings
30
34
  'style-loader',
31
35
  // Translates CSS into CommonJS
32
- 'css-loader'
36
+ 'css-loader',
37
+ {
38
+ loader: 'sass-loader',
39
+ options: {
40
+ implementation: require('sass'),
41
+ sassOptions: {
42
+ quietDeps: true, // Отключает устаревшие предупреждения
43
+ },
44
+ },
45
+ },
33
46
  ],
34
47
  },
35
48
  {