UNPKG

uyem

Version:
527 lines 20 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); /****************************************************************************************** * Repository: https://github.com/kolserdav/werift-sfu-react.git * File name: record.ts * Author: Sergey Kolmiller * Email: <uyem.ru@gmail.com> * License: MIT * License text: See in LICENSE file * Copyright: kolserdav, All rights reserved (c) * Create Date: Wed Aug 24 2022 14:14:09 GMT+0700 (Krasnoyarsk Standard Time) ******************************************************************************************/ const werift = __importStar(require("werift")); // import FFmpeg from 'fluent-ffmpeg'; const path_1 = __importDefault(require("path")); const fs_1 = __importDefault(require("fs")); const date_fns_1 = require("date-fns"); const interfaces_1 = require("../types/interfaces"); const db_1 = __importDefault(require("../core/db")); const lib_1 = require("../utils/lib"); const ffmpeg_1 = __importDefault(require("../utils/ffmpeg")); class RecordVideo extends db_1.default { settings; cloudPath; videosPath; dirPath = {}; intervals = {}; mediaRecorders = {}; startTimes = {}; videoSettings = {}; seconds = {}; times = {}; rtc; ws; constructor({ settings, rtc, ws, cloudPath, prisma, }) { super({ prisma }); this.cloudPath = cloudPath; this.settings = settings; this.videosPath = (0, lib_1.getVideosDirPath)({ cloudPath }); if (!fs_1.default.existsSync(this.videosPath)) { fs_1.default.mkdirSync(this.videosPath); } this.rtc = rtc; this.ws = ws; this.setHandlers(); } getCurrentTime({ roomId }) { const d = new Date(this.times[roomId]); const date = new Date(); const diffs = (0, date_fns_1.differenceInMilliseconds)(date, new Date(date.getFullYear(), date.getMonth(), date.getDate(), d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds())); const _diffs = (diffs / 1000).toFixed(1); return parseFloat(_diffs); } setHandlers() { this.rtc.onChangeVideoTrack = ({ roomId, target }) => { if (!this.seconds[roomId]) { return; } const mediaRecorderId = this.getMediaRecorderId(target, this.startTimes[roomId]?.[target] || 0); const recorder = this.mediaRecorders[roomId][mediaRecorderId]; if (recorder) { this.stopStreamRecord({ roomId, userId: target, pathStr: recorder.path, time: this.getCurrentTime({ roomId }), eventName: 'on-change-video-track', }); } else { (0, lib_1.log)('info', 'Recorder is missing', { roomId, target, mediaRecorderId, recs: this.getMediaRecorderKeys(roomId), }); } this.startStreamRecord({ roomId, userId: target, time: this.getCurrentTime({ roomId }), eventName: 'on-change-video-track', }); }; this.rtc.onChangeMute = this.rtc.onChangeVideoTrack; this.rtc.onRoomConnect = ({ roomId, userId }) => { if (!this.seconds[roomId]) { return; } this.startStreamRecord({ roomId, userId, time: this.getCurrentTime({ roomId }), eventName: 'on-room-connect', }); }; this.rtc.onRoomDisconnect = async ({ roomId, userId, roomUsers }) => { if (!this.seconds[roomId]) { return; } const mediaRecorderId = this.getMediaRecorderId(userId, this.startTimes[roomId]?.[userId] || 0); const recorder = this.mediaRecorders[roomId][mediaRecorderId]; if (!recorder) { return; } this.stopStreamRecord({ roomId, userId, pathStr: recorder.path, time: this.getCurrentTime({ roomId }), eventName: 'on-room-disconnect', }, async () => { if (roomUsers.length === 0) { await this.stopRecord({ id: userId, roomId }); } }); }; } getConnId({ roomId, userId }) { let connId = ''; const peers = Object.keys(this.rtc.streams[roomId]); peers.forEach((item) => { const peer = item.split(this.rtc.delimiter); if (peer[0] === userId.toString() && peer[1] === '0') { // eslint-disable-next-line prefer-destructuring connId = peer[2]; } }); if (connId === '') { (0, lib_1.log)('error', 'Conn id is missing', { roomId, userId, keys: this.rtc.getKeysStreams(roomId), }); } return connId; } getVideoSettingsHandler({ id, data: { userId, width, height }, }) { if (!this.videoSettings[id]) { this.videoSettings[id] = {}; } this.videoSettings[id][userId] = { width, height }; } changeEndTime({ pathStr, time, roomId, }) { const fileName = pathStr.match(/\/\d+\.?\d*_0_[a-zA-Z0-9_\\-]+.webm/); const cleanFileName = fileName ? fileName[0].replace(/\//, '').replace(interfaces_1.EXT_WEBM, '') : ''; const fileNames = cleanFileName.split(this.rtc.delimiter); const newFileName = this.getChunkName({ startTime: fileNames[0], endTime: time, userId: fileNames[2], video: fileNames[3], audio: fileNames[4], width: fileNames[5], height: fileNames[6], }); setTimeout(() => { fs_1.default.renameSync(pathStr, path_1.default.resolve(this.dirPath[roomId], newFileName)); }, 3000); } getChunkName({ startTime, endTime, userId, video, audio, width, height, }) { const ul = this.rtc.delimiter; return `${startTime}${ul}${endTime}${ul}${userId}${ul}${video}${ul}${audio}${ul}${width}${ul}${height}${interfaces_1.EXT_WEBM}`; } stopStreamRecord({ roomId, userId, pathStr, time, eventName, }, cb) { const recorderId = this.getMediaRecorderId(userId, this.startTimes[roomId][userId]); (0, lib_1.log)('info', 'Stop stream record', { recorderId, roomId, pathStr, time, eventName }); this.mediaRecorders[roomId][recorderId].stop().then(() => { setTimeout(() => { this.changeEndTime({ pathStr, time, roomId, }); if (this.mediaRecorders[roomId]?.[recorderId]) { delete this.mediaRecorders[roomId][recorderId]; if (cb) { cb(); } } }, 1000); }); } checkIsMuted({ userId, roomId }) { const muted = this.rtc.muteds[roomId].find((item) => item === userId); const adminMuted = this.rtc.adminMuteds[roomId].find((item) => item === userId); return muted !== undefined || adminMuted !== undefined; } checkVideoPlayed({ userId, roomId, }) { const played = this.rtc.offVideo[roomId].find((item) => item === userId); return played === undefined; } getMediaRecorderId(userId, startTime) { return `${userId}${this.rtc.delimiter}${startTime}`; } getMediaRecorderKeys(roomId) { return Object.keys(this.mediaRecorders[roomId]) || []; } startStreamRecord({ roomId, userId, time, eventName, }) { const videoPlayed = this.checkVideoPlayed({ roomId, userId }); const audioPlayed = !this.checkIsMuted({ roomId, userId }); if (!videoPlayed && !audioPlayed) { return; } const connId = this.getConnId({ roomId, userId }); const peerId = this.rtc.getPeerId(userId, 0, connId); const { width, height } = this.videoSettings[roomId][userId]; const _path = path_1.default.resolve(this.dirPath[roomId], `./${this.getChunkName({ startTime: time, endTime: 0, userId, video: videoPlayed ? 1 : 0, audio: audioPlayed ? 1 : 0, width, height, })}`); const recorderId = this.getMediaRecorderId(userId, time); (0, lib_1.log)('info', 'Start stream record', { recorderId, roomId, _path, time, eventName }); this.mediaRecorders[roomId][recorderId] = new werift.MediaRecorder(_path, 2, videoPlayed ? { width, height, tracks: [], } : { width: 1, height: 1, tracks: [] }); this.rtc.streams[roomId][peerId].forEach((item) => { this.mediaRecorders[roomId][recorderId].addTrack(item); }); if (!this.startTimes[roomId]) { this.startTimes[roomId] = {}; } this.startTimes[roomId][userId] = time; this.mediaRecorders[roomId][recorderId].start(); } // TODO impl async recordVideo({ roomId, id }) { const locale = this.ws.getLocale({ userId: id }); const dir = fs_1.default.readdirSync(this.dirPath[roomId]); if (!dir.length) { (0, lib_1.log)('info', 'Stop record without files', { dir, dirPath: this.dirPath[roomId] }); fs_1.default.rmSync(this.dirPath[roomId], { recursive: true, force: true }); this.settings.sendMessage({ msg: { type: interfaces_1.MessageType.SET_RECORDING, id, connId: '', data: { time: this.seconds[roomId], command: 'stop', }, }, roomId, }); delete this.dirPath[roomId]; return; } const backgroundImagesPath = (0, lib_1.getBackgroundsDirPath)({ cloudPath: this.cloudPath }); // TODO const ffmpeg = new ffmpeg_1.default({ dirPath: this.dirPath[roomId], dir, roomId: roomId.toString(), backgroundImagePath: null, }); const { name, errorCode, time } = await new Promise((resolve) => { ffmpeg .createVideo({ loading: (procent) => { this.settings.sendMessage({ msg: { type: interfaces_1.MessageType.SET_CREATE_VIDEO, id, connId: '', data: { procent, }, }, roomId, }); }, }) .then((res) => { resolve(res); }); }); if (errorCode === 0) { fs_1.default.rmSync(this.dirPath[roomId], { recursive: true, force: true }); delete this.dirPath[roomId]; await this.videoCreate({ data: { roomId: roomId.toString(), name, time, }, }); } else { this.settings.sendMessage({ msg: { type: interfaces_1.MessageType.SET_ERROR, id, connId: '', data: { type: 'error', message: locale.serverError, code: interfaces_1.ErrorCode.serverError, }, }, roomId, }); } this.settings.sendMessage({ msg: { type: interfaces_1.MessageType.SET_RECORDING, id, connId: '', data: { time: this.seconds[roomId], command: 'stop', }, }, roomId, }); } cleanRoomRecord({ roomId }) { delete this.seconds[roomId]; delete this.times[roomId]; delete this.startTimes[roomId]; delete this.mediaRecorders[roomId]; } async stopRecord({ id, roomId }) { clearInterval(this.intervals[roomId]); await Promise.all(this.getMediaRecorderKeys(roomId).map((item) => new Promise((resolve) => { const peer = item.split(this.rtc.delimiter); const userId = peer[0]; const recorder = this.mediaRecorders[roomId][this.getMediaRecorderId(userId, this.startTimes[roomId][userId])]; if (!recorder) { (0, lib_1.log)('warn', 'Recorder is missing', { item }); return; } this.stopStreamRecord({ roomId, userId, pathStr: recorder.path, time: this.getCurrentTime({ roomId }), eventName: 'on-stop', }, () => { resolve(null); }); }))); this.cleanRoomRecord({ roomId }); // await this.recordVideo({ roomId, id }); } async handleVideoRecord(args) { const { id: roomId, data: { command, userId: id, token }, } = args; const sendStopRecord = () => { this.settings.sendMessage({ msg: { type: interfaces_1.MessageType.SET_RECORDING, id, connId: '', data: { time: 0, command: 'stop', }, }, roomId, }); }; const locale = this.ws.getLocale({ userId: id }); const { errorCode, unitId } = await this.checkTokenCb({ token }); const isDefault = (0, lib_1.checkDefaultAuth)({ unitId }); if (errorCode !== 0 && !isDefault) { this.settings.sendMessage({ msg: { type: interfaces_1.MessageType.SET_ERROR, id, connId: '', data: { type: 'warn', message: locale.forbidden, code: interfaces_1.ErrorCode.forbidden, }, }, roomId, }); sendStopRecord(); return; } if (unitId !== id.toString() && !isDefault) { this.settings.sendMessage({ msg: { type: interfaces_1.MessageType.SET_ERROR, id, connId: '', data: { type: 'warn', message: locale.notAuthorised, code: interfaces_1.ErrorCode.notAuthorised, }, }, roomId, }); sendStopRecord(); return; } const room = await this.roomFindFirst({ where: { id: roomId.toString(), }, }); if (room === undefined) { this.settings.sendMessage({ msg: { type: interfaces_1.MessageType.SET_ERROR, id, connId: '', data: { type: 'error', message: locale.serverError, code: interfaces_1.ErrorCode.serverError, }, }, roomId, }); sendStopRecord(); return; } if (room === null) { this.settings.sendMessage({ msg: { type: interfaces_1.MessageType.SET_ERROR, id, connId: '', data: { type: 'error', message: locale.notFound, code: interfaces_1.ErrorCode.notFound, }, }, roomId, }); sendStopRecord(); return; } if (unitId !== room.authorId && !isDefault) { this.settings.sendMessage({ msg: { type: interfaces_1.MessageType.SET_ERROR, id, connId: '', data: { type: 'warn', message: locale.notAuthorised, code: interfaces_1.ErrorCode.notAuthorised, }, }, roomId, }); sendStopRecord(); return; } if (!this.mediaRecorders[roomId]) { this.mediaRecorders[roomId] = {}; } switch (command) { case 'start': this.seconds[roomId] = 0; this.times[roomId] = new Date(); this.intervals[roomId] = setInterval(() => { this.seconds[roomId]++; this.settings.sendMessage({ msg: { type: interfaces_1.MessageType.SET_RECORDING, id, connId: '', data: { time: this.seconds[roomId], command, }, }, roomId, }); }, 1000); this.dirPath[roomId] = path_1.default.resolve(this.videosPath, `./${roomId}${this.rtc.delimiter}${this.times[roomId].getTime()}`); fs_1.default.mkdirSync(this.dirPath[roomId]); this.rtc.getKeysStreams(roomId).forEach((item) => { const peer = item.split(this.rtc.delimiter); const userId = peer[0]; this.startStreamRecord({ roomId, userId, time: 0, eventName: 'on-start' }); }); break; case 'stop': await this.stopRecord({ id, roomId }); break; default: } } } exports.default = RecordVideo; //# sourceMappingURL=recordVideo.js.map