import * as events from 'events' ; import * as fs from 'fs' ; import * as http from 'http' ; import { inject , injectable } from 'inversify' ; import * as path from 'path' ; import * as stream from 'stream' ; import * as apid from '../../../../api' ; import DropLogFile from '../../../db/entities/DropLogFile' ; import Recorded from '../../../db/entities/Recorded' ; import RecordedHistory from '../../../db/entities/RecordedHistory' ; import Reserve from '../../../db/entities/Reserve' ; import VideoFile from '../../../db/entities/VideoFile' ; import FileUtil from '../../../util/FileUtil' ; import StrUtil from '../../../util/StrUtil' ; import IDropLogFileDB from '../../db/IDropLogFileDB' ; import IProgramDB from '../../db/IProgramDB' ; import IRecordedDB from '../../db/IRecordedDB' ; import IRecordedHistoryDB from '../../db/IRecordedHistoryDB' ; import IReserveDB from '../../db/IReserveDB' ; import IVideoFileDB from '../../db/IVideoFileDB' ; import IRecordingEvent from '../../event/IRecordingEvent' ; import IConfigFile from '../../IConfigFile' ; import IConfiguration from '../../IConfiguration' ; import ILogger from '../../ILogger' ; import ILoggerModel from '../../ILoggerModel' ; import IDropCheckerModel from './IDropCheckerModel' ; import IRecorderModel from './IRecorderModel' ; import IRecordingStreamCreator from './IRecordingStreamCreator' ; import IRecordingUtilModel , { RecFilePathInfo } from './IRecordingUtilModel' ; /**
 * Recorder
 */ @ injectable ( ) class RecorderModel implements IRecorderModel { private log : ILogger ; private config : IConfigFile ; private programDB : IProgramDB ; private reserveDB : IReserveDB ; private recordedDB : IRecordedDB ; private recordedHistoryDB : IRecordedHistoryDB ; private videoFileDB : IVideoFileDB ; private dropLogFileDB : IDropLogFileDB ; private streamCreator : IRecordingStreamCreator ; private dropChecker : IDropCheckerModel ; private recordingUtil : IRecordingUtilModel ; private recordingEvent : IRecordingEvent ; private reserve ! : Reserve ; private recordedId : apid . RecordedId | null = null ; private videoFileId : apid . VideoFileId | null = null ; private videoFileFulPath : string | null = null ; private timerId : NodeJS . Timeout | null = null ; private stream : http . IncomingMessage | null = null ; private recFile : fs . WriteStream | null = null ; private isStopPrepRec : boolean = false ; private isNeedDeleteReservation : boolean = true ; private isPrepRecording : boolean = false ; private isRecording : boolean = false ; private isPlanToDelete : boolean = false ; private isCanceledCallingFinished : boolean = false ; private eventEmitter = new events . EventEmitter ( ) ; private dropLogFileId : apid . DropLogFileId | null = null ; constructor ( @ inject ( 'ILoggerModel' ) logger , @ inject ( 'IConfiguration' ) configuration , @ inject ( 'IProgramDB' ) programDB , @ inject ( 'IReserveDB' ) reserveDB , @ inject ( 'IRecordedDB' ) recordedDB , @ inject ( 'IRecordedHistoryDB' ) recordedHistoryDB , @ inject ( 'IVideoFileDB' ) videoFileDB , @ inject ( 'IDropLogFileDB' ) dropLogFileDB , @ inject ( 'IRecordingStreamCreator' ) streamCreator , @ inject ( 'IDropCheckerModel' ) dropChecker , @ inject ( 'IRecordingUtilModel' ) recordingUtil , @ inject ( 'IRecordingEvent' ) recordingEvent ) { this . log = logger . getLogger ( ) ; this . config = configuration . getConfig ( ) ; this . programDB = programDB ; this . reserveDB = reserveDB ; this . recordedDB = recordedDB ; this . recordedHistoryDB = recordedHistoryDB ; this . videoFileDB = videoFileDB ; this . dropLogFileDB = dropLogFileDB ; this . streamCreator = streamCreator ; this . dropChecker = dropChecker ; this . recordingUtil = recordingUtil ; this . recordingEvent = recordingEvent ; } param reserve param isSuppressLog return public setTimer ( reserve , isSuppressLog ) { this . reserve = reserve ; if ( this . reserve . isSkip === true || this . reserve . isOverlap === true ) { return false ; } const now = new Date ( ) . getTime ( ) ; if ( now >= this . reserve . endAt ) { return false ; } let time = this . reserve . startAt - now - IRecordingStreamCreator . PREP_TIME ; if ( time < 0 ) { time = 0 ; } if ( this . timerId !== null ) { clearTimeout ( this . timerId ) ; } if ( isSuppressLog === false ) { this . log . system . info ( `set timer: ${ this . reserve . id }, ${ time }` ) ; } this . timerId = setTimeout ( async ( ) => { try { this . prepRecord ( ) ; } catch ( err ) { this . log . system . error ( `failed prep record: ${ this . reserve . id }` ) ; } } , time ) ; return true ; } /**
     * 録画準備
     */ private async prepRecord ( retry = 0 ) { if ( this . isStopPrepRec === true ) { this . isStopPrepRec = false ; this . isPrepRecording = false ; this . isRecording = false ; this . isPlanToDelete = false ; return ; } this . log . system . info ( `preprec: ${ this . reserve . id }` ) ; this . isPrepRecording = true ; this . isRecording = false ; this . isPlanToDelete = false ; if ( retry === 0 ) { this . recordingEvent . emitStartPrepRecording ( this . reserve ) ; } try { this . stream = await this . streamCreator . create ( this . reserve ) ; if ( ( await this . reserveDB . findId ( this . reserve . id ) ) === null ) { this . log . system . error ( `canceled preprec: ${ this . reserve . id }` ) ; this . destroyStream ( ) ; this . emitCancelEvent ( ) ; } else { await this . doRecord ( ) ; } } catch ( err ) { this . log . system . error ( `preprec failed: ${ this . reserve . id }` ) ; this . log . system . error ( err ) ; if ( ( this . isStopPrepRec as any ) === true ) { this . emitCancelEvent ( ) ; return ; } else if ( retry < 3 ) { setTimeout ( ( ) => { this . prepRecord ( retry + 1 ) ; } , 1000 * 5 ) ; } else { this . isPrepRecording = false ; this . recordingEvent . emitPrepRecordingFailed ( this . reserve ) ; } } } /**
     * 録画準備キャンセル完了時に発行するイベント
     */ private emitCancelEvent ( ) { this . isStopPrepRec = false ; this . isPrepRecording = false ; this . isRecording = false ; this . recordingEvent . emitCancelPrepRecording ( this . reserve ) ; this . eventEmitter . emit ( RecorderModel . CANCEL_EVENT ) ; } param needesUnpip private destroyStream ( needesUnpip = true ) { if ( this . stream !== null ) { try { if ( needesUnpip === true ) { this . stream . unpipe ( ) ; } this . stream . destroy ( ) ; this . stream . push ( null ) ; this . stream . removeAllListeners ( 'data' ) ; this . stream = null ; } catch ( err ) { this . log . system . error ( `destroy stream error: ${ this . reserve . id }` ) ; this . log . system . error ( err ) ; } } if ( this . recFile !== null ) { try { this . recFile . removeAllListeners ( 'error' ) ; this . recFile . end ( ) ; } catch ( err ) { this . log . system . error ( `end recFile error: ${ this . reserve . id }` ) ; this . log . system . error ( err ) ; } } if ( this . dropLogFileId !== null ) { this . dropChecker . stop ( ) . catch ( err => { this . log . system . error ( `dropChecker stop error: ${ this . reserve . id }` ) ; this . log . system . error ( err ) ; } ) ; } } /**
     * 録画処理
     */ private async doRecord ( ) { if ( this . stream === null ) { return ; } if ( this . isStopPrepRec === true ) { this . log . system . error ( `cancel recording: ${ this . reserve . id }` ) ; this . destroyStream ( ) ; this . emitCancelEvent ( ) ; return ; } this . isPrepRecording = false ; this . isRecording = true ; this . eventEmitter . emit ( RecorderModel . START_RECORDING_EVENT ) ; const recPath = await this . recordingUtil . getRecPath ( this . reserve , true ) ; this . log . system . info ( `recording: ${ this . reserve . id } ${ recPath . fullPath }` ) ; this . recFile = fs . createWriteStream ( recPath . fullPath , { flags : 'a' } ) ; this . recFile . once ( 'error' , async ( err ) => { this . log . system . error ( `recFile error reserveId: ${ this . reserve . id }, recordedId: ${ this . recordedId }` ) ; this . log . system . error ( err ) ; if ( this . stream === null ) { this . cancel ( false ) ; } else { this . isCanceledCallingFinished = true ; await this . recFailed ( err ) . catch ( err => { this . log . system . fatal ( `Unexpected recFailed error: reserveId: ${ this . reserve . id }, recordedId: ${ this . recordedId }` ) ; this . log . system . fatal ( err ) ; } ) ; } } ) ; this . stream . pipe ( this . recFile ) ; if ( this . config . isEnabledDropCheck === true ) { let dropFilePath = null ; try { await this . dropChecker . start ( this . config . dropLog , recPath . fullPath , this . stream ) ; dropFilePath = this . dropChecker . getFilePath ( ) ; } catch ( err ) { this . log . system . error ( `drop check error: ${ recPath . fullPath }` ) ; this . log . system . error ( err ) ; dropFilePath = null ; } if ( dropFilePath !== null ) { const dropLogFile = new DropLogFile ( ) ; dropLogFile . errorCnt = 0 ; dropLogFile . dropCnt = 0 ; dropLogFile . scramblingCnt = 0 ; dropLogFile . filePath = path . basename ( dropFilePath ) ; this . log . system . info ( `add drop log file: ${ dropFilePath }` ) ; try { this . dropLogFileId = await this . dropLogFileDB . insertOnce ( dropLogFile ) ; } catch ( err ) { this . dropLogFileId = null ; this . log . system . error ( `add drop log file error: ${ dropFilePath }` ) ; this . log . system . error ( err ) ; } } } return new Promise < void > ( ( resolve , reject ) => { if ( this . stream === null ) { reject ( new Error ( 'StreamIsNull' ) ) ; return ; } let isStreamTimeout = false ; const recordingTimeoutId = setTimeout ( async ( ) => { isStreamTimeout = true ; this . log . system . error ( `recording failed: ${ this . reserve . id }` ) ; if ( this . stream !== null ) { this . stream . removeListener ( 'data' , onData ) ; this . destroyStream ( ) ; await FileUtil . unlink ( recPath . fullPath ) . catch ( err => { this . log . system . error ( `delete error: ${ this . reserve . id } ${ recPath . fullPath }` ) ; this . log . system . error ( err ) ; } ) ; } reject ( new Error ( 'recordingStartError' ) ) ; } , 1000 * 5 ) ; const onData = async ( ) => { clearTimeout ( recordingTimeoutId ) ; if ( isStreamTimeout === true ) { this . log . system . error ( `stream is timeouted. reserveId: ${ this . reserve . id }` ) ; return ; } const recorded = await this . addRecorded ( recPath ) ; if ( this . stream !== null ) { this . setEndProcess ( this . stream ) ; } else { reject ( new Error ( 'StreamIsNull' ) ) ; return ; } this . recordingEvent . emitStartRecording ( this . reserve , recorded ) ; resolve ( ) ; } ; this . stream . once ( 'data' , onData ) ; } ) . catch ( err => { this . destroyStream ( ) ; this . recordingEvent . emitPrepRecordingFailed ( this . reserve ) ; throw err ; } ) ; } param recPath returns private async addRecorded ( recPath ) { this . log . system . info ( `add recorded ${ this . reserve . id } ${ recPath . fullPath }` ) ; try { const recorded = await this . createRecorded ( ) ; this . recordedId = await this . recordedDB . insertOnce ( recorded ) ; recorded . id = this . recordedId ; this . log . system . info ( `recording added reserveId: ${ this . reserve . id }, recordedId: ${ this . recordedId }` ) ; const videoFile = new VideoFile ( ) ; videoFile . parentDirectoryName = recPath . parendDir . name ; videoFile . filePath = path . join ( recPath . subDir , recPath . fileName ) ; videoFile . type = 'ts' ; videoFile . name = 'TS' ; videoFile . recordedId = this . recordedId ; this . log . system . info ( `create video file: ${ videoFile . filePath }` ) ; this . videoFileId = await this . videoFileDB . insertOnce ( videoFile ) ; this . videoFileFulPath = recPath . fullPath ; recorded . videoFiles = [ videoFile ] ; return recorded ; } catch ( err ) { this . log . system . error ( 'add recorded DB error' ) ; this . log . system . error ( err ) ; this . destroyStream ( ) ; await FileUtil . unlink ( recPath . fullPath ) . catch ( err => { this . log . system . error ( `delete error: ${ this . reserve . id } ${ recPath . fullPath }` ) ; this . log . system . error ( err ) ; } ) ; throw new Error ( 'AddRecordedDBError' ) ; } } param s returns private async setEndProcess ( s ) { this . log . system . info ( `set stream.finished: reserveId: ${ this . reserve . id } recordedId: ${ this . recordedId }` ) ; stream . finished ( s , { } , async ( err ) => { if ( this . isCanceledCallingFinished === true ) { return ; } if ( err ) { this . log . system . error ( `stream.finished error: reserveId: ${ this . reserve . id } recordedId: ${ this . recordedId }` ) ; await this . recFailed ( err ) ; } else { await this . recEnd ( ) . catch ( e => { this . log . system . fatal ( `unexpected recEnd error: reserveId: ${ this . reserve . id } recordedId: ${ this . recordedId }` ) ; this . log . system . fatal ( e ) ; } ) ; } } ) ; } param err private async recFailed ( err ) { this . destroyStream ( ) ; this . log . system . error ( `recording end error reserveId: ${ this . reserve . id } recordedId: ${ this . recordedId }` ) ; this . log . system . error ( err ) ; this . isNeedDeleteReservation = false ; await this . recEnd ( ) . catch ( e => { this . log . system . error ( `recEnd error reserveId: ${ this . reserve . id } recordedId: ${ this . recordedId }` ) ; this . log . system . error ( e ) ; } ) ; let recorded = null ; if ( this . recordedId !== null ) { try { recorded = await this . recordedDB . findId ( this . recordedId ) ; } catch ( e ) { this . log . system . error ( `reocrded is deleted: ${ this . recordedId }` ) ; recorded = null ; } } this . recordingEvent . emitRecordingFailed ( this . reserve , recorded ) ; } return private async createRecorded ( ) { const recorded = new Recorded ( ) ; if ( this . recordedId !== null ) { recorded . id = this . recordedId ; } recorded . isRecording = this . isRecording ; recorded . reserveId = this . reserve . id ; recorded . ruleId = this . reserve . ruleId ; recorded . programId = this . reserve . programId ; recorded . channelId = this . reserve . channelId ; recorded . startAt = this . reserve . startAt ; recorded . endAt = this . reserve . endAt ; recorded . duration = this . reserve . endAt - this . reserve . startAt ; if ( this . reserve . isTimeSpecified === true ) { const program = await this . programDB . findChannelIdAndTime ( this . reserve . channelId , this . reserve . startAt ) ; if ( program === null ) { this . log . system . warn ( `get program info warn channelId: ${ this . reserve . channelId }, startAt: ${ this . reserve . startAt }` ) ; recorded . name = '' ; recorded . halfWidthName = '' ; } else { recorded . name = program . name ; recorded . halfWidthName = program . halfWidthName ; recorded . description = program . description ; recorded . halfWidthDescription = program . halfWidthDescription ; recorded . extended = program . extended ; recorded . halfWidthExtended = program . halfWidthExtended ; recorded . rawExtended = program . rawExtended ; recorded . rawHalfWidthExtended = program . rawHalfWidthExtended ; recorded . genre1 = program . genre1 ; recorded . subGenre1 = program . subGenre1 ; recorded . genre2 = program . genre2 ; recorded . subGenre2 = program . subGenre2 ; recorded . genre3 = program . genre3 ; recorded . subGenre3 = program . subGenre3 ; recorded . videoType = program . videoType ; recorded . videoResolution = program . videoResolution ; recorded . videoStreamContent = program . videoStreamContent ; recorded . videoComponentType = program . videoComponentType ; recorded . audioSamplingRate = program . audioSamplingRate ; recorded . audioComponentType = program . audioComponentType ; } } else if ( this . reserve . name !== null && this . reserve . halfWidthName !== null ) { recorded . name = this . reserve . name ; recorded . halfWidthName = this . reserve . halfWidthName ; recorded . description = this . reserve . description ; recorded . halfWidthDescription = this . reserve . halfWidthDescription ; recorded . extended = this . reserve . extended ; recorded . halfWidthExtended = this . reserve . halfWidthExtended ; recorded . rawExtended = this . reserve . rawExtended ; recorded . rawHalfWidthExtended = this . reserve . rawHalfWidthExtended ; recorded . genre1 = this . reserve . genre1 ; recorded . subGenre1 = this . reserve . subGenre1 ; recorded . genre2 = this . reserve . genre2 ; recorded . subGenre2 = this . reserve . subGenre2 ; recorded . genre3 = this . reserve . genre3 ; recorded . subGenre3 = this . reserve . subGenre3 ; recorded . videoType = this . reserve . videoType ; recorded . videoResolution = this . reserve . videoResolution ; recorded . videoStreamContent = this . reserve . videoStreamContent ; recorded . videoComponentType = this . reserve . videoComponentType ; recorded . audioSamplingRate = this . reserve . audioSamplingRate ; recorded . audioComponentType = this . reserve . audioComponentType ; } else { throw new Error ( 'CreateRecordedError' ) ; } if ( this . dropLogFileId !== null ) { recorded . dropLogFileId = this . dropLogFileId ; } return recorded ; } /**
     * 録画終了処理
     */ private async recEnd ( ) { this . log . system . info ( `start recEnd reserveId: ${ this . reserve . id } recordedId: ${ this . recordedId }` ) ; this . destroyStream ( ) ; if ( this . isPlanToDelete === true ) { this . log . system . info ( `plan to delete reserveId: ${ this . reserve . id } recordedId: ${ this . recordedId }` ) ; if ( this . dropLogFileId !== null ) { await this . dropChecker . stop ( ) . catch ( err => { this . log . system . error ( `stop drop checker error: ${ this . dropLogFileId }` ) ; this . log . system . error ( err ) ; } ) ; } return ; } if ( this . recordedId !== null ) { this . log . system . info ( `remove recording flag: ${ this . recordedId }` ) ; await this . recordedDB . removeRecording ( this . recordedId ) ; this . isRecording = false ; if ( typeof this . config . recordedTmp !== 'undefined' && this . videoFileId !== null ) { try { const newVdeoFileFulPath = await this . recordingUtil . movingFromTmp ( this . reserve , this . videoFileId ) ; this . videoFileFulPath = newVdeoFileFulPath ; } catch ( err ) { this . log . system . fatal ( `movingFromTmp error: ${ this . videoFileId }` ) ; this . log . system . fatal ( err ) ; } } if ( this . videoFileId !== null && this . videoFileFulPath !== null ) { this . recordingUtil . updateVideoFileSize ( this . videoFileId ) . catch ( err => { this . log . system . error ( `update file size error: ${ this . videoFileId }` ) ; this . log . system . error ( err ) ; } ) ; } await this . updateDropFileLog ( ) . catch ( err => { this . log . system . fatal ( `updateDropFileLog error: ${ this . dropLogFileId }` ) ; this . log . stream . fatal ( err ) ; } ) ; const recorded = await this . recordedDB . findId ( this . recordedId ) ; if ( this . reserve . isTimeSpecified === false && this . reserve . ruleId !== null && this . isNeedDeleteReservation === true ) { try { if ( recorded !== null ) { this . log . system . info ( `add recorded history: ${ this . recordedId }` ) ; const history = new RecordedHistory ( ) ; history . name = StrUtil . deleteBrackets ( recorded . halfWidthName ) ; history . channelId = recorded . channelId ; history . endAt = recorded . endAt ; await this . recordedHistoryDB . insertOnce ( history ) ; } } catch ( err ) { this . log . system . error ( `add recorded history error: ${ this . recordedId }` ) ; this . log . system . error ( err ) ; } } if ( recorded !== null ) { this . log . system . info ( `emit finish recording reserveId: ${ this . reserve . id }, recordedId: ${ this . recordedId }, isNeedDeleteReservation: ${ this . isNeedDeleteReservation }` ) ; this . recordingEvent . emitFinishRecording ( this . reserve , recorded , this . isNeedDeleteReservation ) ; } } else { this . log . system . info ( 'failed to recording: recorded id is null' ) ; } this . log . system . info ( `recording finish reserveId: ${ this . reserve . id }, recordedId: ${ this . recordedId }, videoFileFulPath: ${ this . videoFileFulPath }` ) ; } return private async updateDropFileLog ( ) { if ( this . dropLogFileId === null ) { return ; } let error = 0 ; let drop = 0 ; let scrambling = 0 ; try { const dropResult = await this . dropChecker . getResult ( ) ; for ( const pid in dropResult ) { error += dropResult [ pid ] . error ; drop += dropResult [ pid ] . drop ; scrambling += dropResult [ pid ] . scrambling ; } } catch ( err ) { this . log . system . error ( `get drop result error: ${ this . dropLogFileId }` ) ; this . log . system . error ( err ) ; await this . dropChecker . stop ( ) . catch ( ( ) => { } ) ; return ; } this . log . system . info ( { recordedId : this . recordedId , error : error , drop : drop , scrambling : scrambling , } ) ; await this . dropLogFileDB . updateCnt ( { id : this . dropLogFileId , errorCnt : error , dropCnt : drop , scramblingCnt : scrambling , } ) . catch ( err => { this . log . system . error ( `update drop cnt error: ${ this . dropLogFileId }` ) ; this . log . system . error ( err ) ; } ) ; } param isPlanToDelete public async cancel ( isPlanToDelete ) { this . log . system . info ( `recording cancel reserveId: ${ this . reserve . id }, recordedId: ${ this . recordedId }, isPlanToDelete: ${ isPlanToDelete }` ) ; if ( this . isPrepRecording === false && this . isRecording === false ) { if ( this . timerId !== null ) { clearTimeout ( this . timerId ) ; } } else if ( this . isPrepRecording === true ) { this . log . system . info ( `cancel preprec: ${ this . reserve . id }` ) ; return new Promise < void > ( ( resolve , reject ) => { const timerId = setTimeout ( ( ) => { reject ( new Error ( 'PrepRecCancelTimeoutError' ) ) ; } , 60 * 1000 ) ; this . eventEmitter . once ( RecorderModel . CANCEL_EVENT , ( ) => { clearTimeout ( timerId ) ; resolve ( ) ; } ) ; this . isStopPrepRec = true ; } ) ; } else if ( this . isRecording === true ) { this . isPlanToDelete = isPlanToDelete ; this . log . system . info ( `stop recording: ${ this . reserve . id }` ) ; if ( this . stream !== null ) { this . stream . destroy ( ) ; this . stream . push ( null ) ; } this . isNeedDeleteReservation = false ; } } param newReserve param isSuppressLog public async update ( newReserve , isSuppressLog ) { if ( newReserve . isSkip === true || newReserve . isOverlap === true ) { this . log . system . info ( `cancel recording by skip or overlap reserveId: ${ this . reserve . id }, recordedId: ${ this . recordedId }` ) ; await this . cancel ( false ) . catch ( err => { this . log . system . error ( `cancel recording error: ${ newReserve . id }` ) ; this . log . system . error ( err ) ; } ) ; } else if ( this . reserve . startAt !== newReserve . startAt || this . reserve . endAt !== newReserve . endAt ) { if ( this . isPrepRecording === false && this . isRecording === false ) { this . setTimer ( newReserve , isSuppressLog ) ; } else { if ( this . reserve . startAt > newReserve . startAt ) { this . log . system . info ( `resetting recording timer reserveId: ${ this . reserve . id }, recordedId: ${ this . recordedId }` ) ; await this . cancel ( false ) . catch ( err => { this . log . system . error ( `cancel recording error: ${ newReserve . id }` ) ; this . log . system . error ( err ) ; } ) ; this . setTimer ( newReserve , isSuppressLog ) ; } else if ( this . reserve . endAt !== newReserve . endAt && this . reserve . programId === null ) { this . log . system . info ( `change recording endAt: ${ newReserve . id }` ) ; if ( this . isPrepRecording === true ) { await new Promise < void > ( ( resolve , reject ) => { this . log . system . debug ( `wait change endAt: ${ newReserve . id }` ) ; const timeoutId = setTimeout ( ( ) => { reject ( new Error ( 'ChangeEndAtTimeoutError' ) ) ; } , IRecordingStreamCreator . PREP_TIME ) ; this . eventEmitter . once ( RecorderModel . START_RECORDING_EVENT , ( ) => { clearTimeout ( timeoutId ) ; resolve ( ) ; } ) ; } ) ; } try { this . streamCreator . changeEndAt ( newReserve ) ; } catch ( err ) { this . log . system . error ( `change recording endAt: ${ newReserve . id }` ) ; this . log . system . error ( err ) ; } } } } this . reserve = newReserve ; if ( this . isRecording === true && this . recordedId !== null ) { const recorded = await this . createRecorded ( ) ; this . log . system . info ( `update reocrded: ${ this . recordedId }` ) ; this . recordedDB . updateOnce ( recorded ) ; } } return public resetTimer ( ) { if ( this . isRecording === true ) { return true ; } return this . setTimer ( this . reserve , false ) ; } } namespace RecorderModel { export const CANCEL_EVENT = 'RecordingCancelEvent' ; export const START_RECORDING_EVENT = 'StartRecordingEvent' ; } export default RecorderModel ;