1 import { move, remove } from 'fs-extra'
2 import { join } from 'path'
3 import { logger } from '@server/helpers/logger'
4 import { JOB_PRIORITY } from '@server/initializers/constants'
5 import { LiveManager } from '@server/lib/live'
6 import { MStreamingPlaylist, MVideo } from '@server/types/models'
7 import { MRunnerJob } from '@server/types/models/runners'
8 import { buildUUID } from '@shared/extra-utils'
10 LiveRTMPHLSTranscodingSuccess,
11 LiveRTMPHLSTranscodingUpdatePayload,
13 RunnerJobLiveRTMPHLSTranscodingPayload,
14 RunnerJobLiveRTMPHLSTranscodingPrivatePayload,
16 } from '@shared/models'
17 import { AbstractJobHandler } from './abstract-job-handler'
19 type CreateOptions = {
21 playlist: MStreamingPlaylist
31 segmentListSize: number
32 segmentDuration: number
34 outputDirectory: string
37 // eslint-disable-next-line max-len
38 export class LiveRTMPHLSTranscodingJobHandler extends AbstractJobHandler<CreateOptions, LiveRTMPHLSTranscodingUpdatePayload, LiveRTMPHLSTranscodingSuccess> {
40 async create (options: CreateOptions) {
41 const { video, rtmpUrl, toTranscode, playlist, segmentDuration, segmentListSize, outputDirectory, sessionId } = options
43 const jobUUID = buildUUID()
44 const payload: RunnerJobLiveRTMPHLSTranscodingPayload = {
55 const privatePayload: RunnerJobLiveRTMPHLSTranscodingPrivatePayload = {
56 videoUUID: video.uuid,
57 masterPlaylistName: playlist.playlistFilename,
62 const job = await this.createRunnerJob({
63 type: 'live-rtmp-hls-transcoding',
67 priority: JOB_PRIORITY.TRANSCODING
73 // ---------------------------------------------------------------------------
75 protected async specificUpdate (options: {
77 updatePayload: LiveRTMPHLSTranscodingUpdatePayload
79 const { runnerJob, updatePayload } = options
81 const privatePayload = runnerJob.privatePayload as RunnerJobLiveRTMPHLSTranscodingPrivatePayload
82 const outputDirectory = privatePayload.outputDirectory
83 const videoUUID = privatePayload.videoUUID
85 // Always process the chunk first before moving m3u8 that references this chunk
86 if (updatePayload.type === 'add-chunk') {
88 updatePayload.videoChunkFile as string,
89 join(outputDirectory, updatePayload.videoChunkFilename),
92 } else if (updatePayload.type === 'remove-chunk') {
93 await remove(join(outputDirectory, updatePayload.videoChunkFilename))
96 if (updatePayload.resolutionPlaylistFile && updatePayload.resolutionPlaylistFilename) {
98 updatePayload.resolutionPlaylistFile as string,
99 join(outputDirectory, updatePayload.resolutionPlaylistFilename),
104 if (updatePayload.masterPlaylistFile) {
105 await move(updatePayload.masterPlaylistFile as string, join(outputDirectory, privatePayload.masterPlaylistName), { overwrite: true })
109 'Runner live RTMP to HLS job %s for %s updated.',
110 runnerJob.uuid, videoUUID, { updatePayload, ...this.lTags(videoUUID, runnerJob.uuid) }
114 // ---------------------------------------------------------------------------
116 protected specificComplete (options: {
117 runnerJob: MRunnerJob
119 return this.stopLive({
120 runnerJob: options.runnerJob,
125 // ---------------------------------------------------------------------------
127 protected isAbortSupported () {
131 protected specificAbort () {
132 throw new Error('Not implemented')
135 protected specificError (options: {
136 runnerJob: MRunnerJob
137 nextState: RunnerJobState
139 return this.stopLive({
140 runnerJob: options.runnerJob,
145 protected specificCancel (options: {
146 runnerJob: MRunnerJob
148 return this.stopLive({
149 runnerJob: options.runnerJob,
154 private stopLive (options: {
155 runnerJob: MRunnerJob
156 type: 'ended' | 'errored' | 'cancelled'
158 const { runnerJob, type } = options
160 const privatePayload = runnerJob.privatePayload as RunnerJobLiveRTMPHLSTranscodingPrivatePayload
161 const videoUUID = privatePayload.videoUUID
165 errored: LiveVideoError.RUNNER_JOB_ERROR,
166 cancelled: LiveVideoError.RUNNER_JOB_CANCEL
169 LiveManager.Instance.stopSessionOf(privatePayload.videoUUID, errorType[type])
171 logger.info('Runner live RTMP to HLS job %s for video %s %s.', runnerJob.uuid, videoUUID, type, this.lTags(runnerJob.uuid, videoUUID))