import { audioConstraints } from './audioConstraints'; import { videoConstraints } from './videoConstraints'; import { isSupport } from './isSupport'; import { getVideo } from './videoElement'; import { EventEmitter } from 'eventemitter3'; export type ResolutionType = '1080p' | '2k' | '4k'; export interface InitConfigType extends DisplayMediaStreamConstraints { uploadUrl: ''; resolution: ResolutionType; autoDownload?: boolean; isElectron?: boolean; chromeMediaSourceId?: null; chromeAudioSourceId?: null; debug?: boolean; } export enum RecorderStatusType { init = 0, start = 1, hold = 2, end = 3, } export class BasicSimaqRecorder extends EventEmitter { displayMediaStreamConstraints: DisplayMediaStreamConstraints = { video: videoConstraints.getValue(), audio: audioConstraints.getValue(), }; private isStartRecoding = false; private stream: MediaStream; private audioInput: MediaStream; private mediaRecorder: MediaRecorder; public status: RecorderStatusType = 0; // public record = new BehaviorSubject(null); private recordChunks: Blob[] = []; private autoDownload = false; private passiveEnd = false; private isElectron: boolean; private chromeMediaSourceId: string | null; private chromeAudioSourceId: string | null; constructor(arg: InitConfigType) { super(); console.log('arg', arg); this.autoDownload = arg.autoDownload; this.isElectron = arg.isElectron; this.chromeMediaSourceId = arg.chromeMediaSourceId; this.chromeAudioSourceId = arg.chromeAudioSourceId; videoConstraints.subscribe((value) => { console.log('subscribe', value); }); } private sleep = (ms) => new Promise((r) => setTimeout(r, ms)); public async startRecord(): Promise { try { if (!this.isStartRecoding) { console.log('开始录屏!', isSupport()); if (!isSupport()) { console.error('当前浏览器不支持录屏或不存在https环境'); return; } const media = this.isElectron ? await this.getEletronDisplayMedia() : await this.getDisplayMedia(); console.log('media', media); if (media) { this.emit('startRecord'); this.isStartRecoding = true; this.status = RecorderStatusType.start; // console.log('media', media); const video: HTMLVideoElement = getVideo(); if (video) { // console.log('video', video); video.srcObject = media; this.stream = media; this.createMediaRecoder(); this.mediaRecorder.start(); this.stream.getVideoTracks()[0].onended = () => { console.log('stop-share'); this.endRecord(); }; } } else { this.streamStop(); this.isStartRecoding = false; this.status = RecorderStatusType.end; this.emit('cancelRecord'); } } } catch (error) { console.error('startRecord::', error); } } private getEletronDisplayMedia(): Promise { return new Promise(async (resolve) => { try { const audioInput = await this.getEletronDeaultAudio(); if (audioInput) { this.audioInput = audioInput; } console.log('eletron-audioInput', audioInput); if (navigator.mediaDevices.getDisplayMedia) { const res = await navigator.mediaDevices.getUserMedia({ audio: false, video: { mandatory: { chromeMediaSource: 'desktop', chromeMediaSourceId: this.chromeMediaSourceId, ...videoConstraints.getValue(), }, }, } as any as MediaStreamConstraints); return resolve(res); } return resolve(null); } catch (error) { return resolve(null); } }); } private getDisplayMedia(): Promise { return new Promise(async (resolve) => { try { const audioInput = await this.getDeaultAudio(); if (audioInput) { this.audioInput = audioInput; } console.log('audioInput', audioInput); if (navigator.mediaDevices.getDisplayMedia) { const res = await navigator.mediaDevices.getDisplayMedia( this.displayMediaStreamConstraints, ); return resolve(res); } return resolve(null); } catch (error) { return resolve(null); } }); } private async getDeaultAudio(): Promise { return new Promise(async (resolve) => { try { if (navigator.mediaDevices.getUserMedia) { const res = await navigator.mediaDevices.getUserMedia({ audio: true, video: false, }); return resolve(res); } return resolve(null); } catch (error) { return resolve(null); } }); } private async getEletronDeaultAudio(): Promise { return new Promise(async (resolve) => { try { if (navigator.mediaDevices.getUserMedia) { const res = await navigator.mediaDevices.getUserMedia({ video: false, audio: { deviceId: 'default', ...audioConstraints.getValue(), }, } as any as MediaStreamConstraints); return resolve(res); } return resolve(null); } catch (error) { return resolve(null); } }); } public holdRecord(): void { this.isStartRecoding = false; this.status = RecorderStatusType.hold; this.streamStop(); } public async endRecord(): Promise { return new Promise(async (resolve) => { try { this.streamStop(); await this.sleep(1000); this.isStartRecoding = false; this.status = RecorderStatusType.end; const blobs = this.recordChunks.slice(); console.log('last-dump', blobs); if (this.autoDownload) { blobs?.length && this.handleAutoDownload(blobs); } this.emit('endRecord', blobs); this.passiveEnd = false; this.recordChunks = []; resolve(blobs); } catch (error) { resolve([]); } }); } private streamStop(): void { if (this.stream) { this.stream.getTracks().forEach((track) => track.stop()); } if (this.audioInput) { this.audioInput.getTracks().forEach((track) => track.stop()); } if (this.mediaRecorder) { this.mediaRecorder.stop(); } } private createMediaRecoder(): void { console.log('video-flag', videoConstraints.value); // let mergeSteam: MediaStream; let audioTrack: MediaStreamTrack, videoTrack: MediaStreamTrack; if (this.audioInput) { [videoTrack] = this.stream.getVideoTracks(); [audioTrack] = this.audioInput.getAudioTracks(); this.stream = new MediaStream([videoTrack, audioTrack]); } const mediaRecorder = new MediaRecorder(this.stream, { mimeType: 'video/webm;codecs=H264,opus', audioBitsPerSecond: videoConstraints.value.audioBitsPerSecond, videoBitsPerSecond: videoConstraints.value.videoBitsPerSecond, }); this.mediaRecorder = mediaRecorder; this.mediaRecorder.ondataavailable = (event) => { this.recordChunks.push(event.data); this.emit( 'record', new Blob([event.data], { type: 'video/mp4; codecs=h264', }), ); }; this.mediaRecorder.stop = () => { // setTimeout(() => { // this.handleAutoDownload(); // }, 1000); }; } private handleAutoDownload(chunks: Blob[]): void { const downloadBlob = new Blob(chunks, { type: 'video/mp4; codecs=h264', }); const url = URL.createObjectURL(downloadBlob); const a: HTMLAnchorElement = document.createElement('a'); document.body.appendChild(a); a.style.display = 'none'; a.href = url; a.download = 'test.mp4'; a.click(); window.URL.revokeObjectURL(url); } private uploadToServer(): void { } }