basicSimaqRecorder.ts 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import { audioConstraints } from './audioConstraints';
  2. import { videoConstraints } from './videoConstraints';
  3. import { isSupport } from './isSupport';
  4. import { getVideo } from './videoElement';
  5. import { EventEmitter } from 'eventemitter3';
  6. export type ResolutionType = '1080p' | '2k' | '4k';
  7. export interface InitConfigType extends DisplayMediaStreamConstraints {
  8. uploadUrl: '';
  9. resolution: ResolutionType;
  10. autoDownload?: boolean;
  11. debug?: boolean;
  12. }
  13. export enum RecorderStatusType {
  14. init = 0,
  15. start = 1,
  16. hold = 2,
  17. end = 3,
  18. }
  19. export class BasicSimaqRecorder extends EventEmitter {
  20. displayMediaStreamConstraints: DisplayMediaStreamConstraints = {
  21. video: videoConstraints.getValue(),
  22. audio: audioConstraints.getValue(),
  23. };
  24. private isStartRecoding = false;
  25. private stream: MediaStream;
  26. private audioInput: MediaStream;
  27. private mediaRecorder: MediaRecorder;
  28. public status: RecorderStatusType = 0;
  29. // public record = new BehaviorSubject<Blob>(null);
  30. private recordChunks: Blob[] = [];
  31. private autoDownload = false;
  32. private passiveEnd = false;
  33. constructor(arg: InitConfigType) {
  34. super();
  35. console.log('arg', arg);
  36. this.autoDownload = arg.autoDownload;
  37. videoConstraints.subscribe((value) => {
  38. console.log('subscribe', value);
  39. });
  40. }
  41. private sleep = (ms) => new Promise((r) => setTimeout(r, ms));
  42. public async startRecord(): Promise<void> {
  43. if (!this.isStartRecoding) {
  44. console.log('开始录屏!', isSupport());
  45. if (!isSupport()) {
  46. console.error('当前浏览器不支持录屏或不存在https环境');
  47. return;
  48. }
  49. const media = await this.getDisplayMedia();
  50. console.log('media', media);
  51. if (media) {
  52. this.emit('startRecord');
  53. this.isStartRecoding = true;
  54. this.status = RecorderStatusType.start;
  55. // console.log('media', media);
  56. const video: HTMLVideoElement = getVideo();
  57. if (video) {
  58. // console.log('video', video);
  59. video.srcObject = media;
  60. this.stream = media;
  61. this.createMediaRecoder();
  62. this.mediaRecorder.start();
  63. this.stream.getVideoTracks()[0].onended = () => {
  64. console.log('stop-share');
  65. this.endRecord();
  66. };
  67. }
  68. } else {
  69. this.endRecord();
  70. }
  71. }
  72. }
  73. private getDisplayMedia(): Promise<MediaStream | null> {
  74. return new Promise(async (resolve) => {
  75. try {
  76. const audioInput = await this.getDeaultAudio();
  77. if (audioInput) {
  78. this.audioInput = audioInput;
  79. }
  80. console.log('audioInput', audioInput);
  81. if (navigator.mediaDevices.getDisplayMedia) {
  82. const res = await navigator.mediaDevices.getDisplayMedia(
  83. this.displayMediaStreamConstraints,
  84. );
  85. return resolve(res);
  86. }
  87. return resolve(null);
  88. } catch (error) {
  89. return resolve(null);
  90. }
  91. });
  92. }
  93. private async getDeaultAudio(): Promise<MediaStream> {
  94. return new Promise(async (resolve) => {
  95. try {
  96. if (navigator.mediaDevices.getUserMedia) {
  97. const res = await navigator.mediaDevices.getUserMedia({
  98. audio: true,
  99. video: false,
  100. });
  101. return resolve(res);
  102. }
  103. return resolve(null);
  104. } catch (error) {
  105. return resolve(null);
  106. }
  107. });
  108. }
  109. public holdRecord(): void {
  110. this.isStartRecoding = false;
  111. this.status = RecorderStatusType.hold;
  112. this.streamStop();
  113. }
  114. public async endRecord(): Promise<Blob[]> {
  115. return new Promise<Blob[]>(async (resolve) => {
  116. try {
  117. this.streamStop();
  118. await this.sleep(1000);
  119. this.isStartRecoding = false;
  120. this.status = RecorderStatusType.end;
  121. const blobs = this.recordChunks.slice();
  122. console.log('last-dump', blobs);
  123. if (this.autoDownload) {
  124. blobs?.length && this.handleAutoDownload(blobs);
  125. }
  126. this.emit('endRecord', blobs);
  127. this.passiveEnd = false;
  128. this.recordChunks = [];
  129. resolve(blobs);
  130. } catch (error) {
  131. resolve([]);
  132. }
  133. });
  134. }
  135. private streamStop(): void {
  136. if (this.stream) {
  137. this.stream.getTracks().forEach((track) => track.stop());
  138. }
  139. if (this.audioInput) {
  140. this.audioInput.getTracks().forEach((track) => track.stop());
  141. }
  142. if (this.mediaRecorder) {
  143. this.mediaRecorder.stop();
  144. }
  145. }
  146. private createMediaRecoder(): void {
  147. console.log('video-flag', videoConstraints.value);
  148. // let mergeSteam: MediaStream;
  149. let audioTrack: MediaStreamTrack, videoTrack: MediaStreamTrack;
  150. if (this.audioInput) {
  151. [videoTrack] = this.stream.getVideoTracks();
  152. [audioTrack] = this.audioInput.getAudioTracks();
  153. this.stream = new MediaStream([videoTrack, audioTrack]);
  154. }
  155. const mediaRecorder = new MediaRecorder(this.stream, {
  156. mimeType: 'video/webm;codecs=H264,opus',
  157. audioBitsPerSecond: videoConstraints.value.audioBitsPerSecond,
  158. videoBitsPerSecond: videoConstraints.value.videoBitsPerSecond,
  159. });
  160. this.mediaRecorder = mediaRecorder;
  161. this.mediaRecorder.ondataavailable = (event) => {
  162. this.recordChunks.push(event.data);
  163. this.emit(
  164. 'record',
  165. new Blob([event.data], {
  166. type: 'video/mp4; codecs=h264',
  167. }),
  168. );
  169. };
  170. this.mediaRecorder.stop = () => {
  171. // setTimeout(() => {
  172. // this.handleAutoDownload();
  173. // }, 1000);
  174. };
  175. }
  176. private handleAutoDownload(chunks: Blob[]): void {
  177. const downloadBlob = new Blob(chunks, {
  178. type: 'video/mp4; codecs=h264',
  179. });
  180. const url = URL.createObjectURL(downloadBlob);
  181. const a: HTMLAnchorElement = document.createElement('a');
  182. document.body.appendChild(a);
  183. a.style.display = 'none';
  184. a.href = url;
  185. a.download = 'test.mp4';
  186. a.click();
  187. window.URL.revokeObjectURL(url);
  188. }
  189. private uploadToServer(): void { }
  190. }