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
30 segmentListSize: number
31 segmentDuration: number
33 outputDirectory: string
36 // eslint-disable-next-line max-len
37 export class LiveRTMPHLSTranscodingJobHandler extends AbstractJobHandler<CreateOptions, LiveRTMPHLSTranscodingUpdatePayload, LiveRTMPHLSTranscodingSuccess> {
39 async create (options: CreateOptions) {
40 const { video, rtmpUrl, toTranscode, playlist, segmentDuration, segmentListSize, outputDirectory } = options
42 const jobUUID = buildUUID()
43 const payload: RunnerJobLiveRTMPHLSTranscodingPayload = {
54 const privatePayload: RunnerJobLiveRTMPHLSTranscodingPrivatePayload = {
55 videoUUID: video.uuid,
56 masterPlaylistName: playlist.playlistFilename,
60 const job = await this.createRunnerJob({
61 type: 'live-rtmp-hls-transcoding',
65 priority: JOB_PRIORITY.TRANSCODING
71 // ---------------------------------------------------------------------------
73 protected async specificUpdate (options: {
75 updatePayload: LiveRTMPHLSTranscodingUpdatePayload
77 const { runnerJob, updatePayload } = options
79 const privatePayload = runnerJob.privatePayload as RunnerJobLiveRTMPHLSTranscodingPrivatePayload
80 const outputDirectory = privatePayload.outputDirectory
81 const videoUUID = privatePayload.videoUUID
83 // Always process the chunk first before moving m3u8 that references this chunk
84 if (updatePayload.type === 'add-chunk') {
86 updatePayload.videoChunkFile as string,
87 join(outputDirectory, updatePayload.videoChunkFilename),
90 } else if (updatePayload.type === 'remove-chunk') {
91 await remove(join(outputDirectory, updatePayload.videoChunkFilename))
94 if (updatePayload.resolutionPlaylistFile && updatePayload.resolutionPlaylistFilename) {
96 updatePayload.resolutionPlaylistFile as string,
97 join(outputDirectory, updatePayload.resolutionPlaylistFilename),
102 if (updatePayload.masterPlaylistFile) {
103 await move(updatePayload.masterPlaylistFile as string, join(outputDirectory, privatePayload.masterPlaylistName), { overwrite: true })
107 'Runner live RTMP to HLS job %s for %s updated.',
108 runnerJob.uuid, videoUUID, { updatePayload, ...this.lTags(videoUUID, runnerJob.uuid) }
112 // ---------------------------------------------------------------------------
114 protected specificComplete (options: {
115 runnerJob: MRunnerJob
117 return this.stopLive({
118 runnerJob: options.runnerJob,
123 // ---------------------------------------------------------------------------
125 protected isAbortSupported () {
129 protected specificAbort () {
130 throw new Error('Not implemented')
133 protected specificError (options: {
134 runnerJob: MRunnerJob
135 nextState: RunnerJobState
137 return this.stopLive({
138 runnerJob: options.runnerJob,
143 protected specificCancel (options: {
144 runnerJob: MRunnerJob
146 return this.stopLive({
147 runnerJob: options.runnerJob,
152 private stopLive (options: {
153 runnerJob: MRunnerJob
154 type: 'ended' | 'errored' | 'cancelled'
156 const { runnerJob, type } = options
158 const privatePayload = runnerJob.privatePayload as RunnerJobLiveRTMPHLSTranscodingPrivatePayload
159 const videoUUID = privatePayload.videoUUID
163 errored: LiveVideoError.RUNNER_JOB_ERROR,
164 cancelled: LiveVideoError.RUNNER_JOB_CANCEL
167 LiveManager.Instance.stopSessionOf(privatePayload.videoUUID, errorType[type])
169 logger.info('Runner live RTMP to HLS job %s for video %s %s.', runnerJob.uuid, videoUUID, type, this.lTags(runnerJob.uuid, videoUUID))