From 543fbd1ffec55434911c3e9be6c169057c591512 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 30 Oct 2023 11:57:39 +0100 Subject: [PATCH] Correctly display broken muxing session Can happen when we stream an audio stream only --- .../live-stream-information.component.ts | 4 +- .../src/videos/live/live-video-error.enum.ts | 4 +- server/core/lib/live/live-manager.ts | 43 ++++++++++++++----- server/core/lib/live/shared/muxing-session.ts | 21 ++++++--- .../live-rtmp-hls-transcoding-job-handler.ts | 2 +- server/core/lib/video-blacklist.ts | 2 +- server/core/models/video/video.ts | 2 +- 7 files changed, 58 insertions(+), 20 deletions(-) diff --git a/client/src/app/shared/shared-video-live/live-stream-information.component.ts b/client/src/app/shared/shared-video-live/live-stream-information.component.ts index 4089c88fb..eb0d84b0a 100644 --- a/client/src/app/shared/shared-video-live/live-stream-information.component.ts +++ b/client/src/app/shared/shared-video-live/live-stream-information.component.ts @@ -45,7 +45,9 @@ export class LiveStreamInformationComponent { [LiveVideoError.FFMPEG_ERROR]: $localize`Server error`, [LiveVideoError.QUOTA_EXCEEDED]: $localize`Quota exceeded`, [LiveVideoError.RUNNER_JOB_CANCEL]: $localize`Runner job cancelled`, - [LiveVideoError.RUNNER_JOB_ERROR]: $localize`Error in runner job` + [LiveVideoError.RUNNER_JOB_ERROR]: $localize`Error in runner job`, + [LiveVideoError.UNKNOWN_ERROR]: $localize`Unknown error`, + [LiveVideoError.INVALID_INPUT_VIDEO_STREAM]: $localize`Invalid input video stream` } return errors[session.error] diff --git a/packages/models/src/videos/live/live-video-error.enum.ts b/packages/models/src/videos/live/live-video-error.enum.ts index cd92a1cff..5836da66e 100644 --- a/packages/models/src/videos/live/live-video-error.enum.ts +++ b/packages/models/src/videos/live/live-video-error.enum.ts @@ -5,7 +5,9 @@ export const LiveVideoError = { FFMPEG_ERROR: 4, BLACKLISTED: 5, RUNNER_JOB_ERROR: 6, - RUNNER_JOB_CANCEL: 7 + RUNNER_JOB_CANCEL: 7, + UNKNOWN_ERROR: 8, + INVALID_INPUT_VIDEO_STREAM: 9 } as const export type LiveVideoErrorType = typeof LiveVideoError[keyof typeof LiveVideoError] diff --git a/server/core/lib/live/live-manager.ts b/server/core/lib/live/live-manager.ts index 6c8456f7a..de035ed9f 100644 --- a/server/core/lib/live/live-manager.ts +++ b/server/core/lib/live/live-manager.ts @@ -187,7 +187,13 @@ class LiveManager { return this.getContext().sessions.has(sessionId) } - stopSessionOf (videoUUID: string, error: LiveVideoErrorType | null) { + stopSessionOf (options: { + videoUUID: string + error: LiveVideoErrorType | null + errorOnReplay?: boolean + }) { + const { videoUUID, error } = options + const sessionId = this.videoSessions.get(videoUUID) if (!sessionId) { logger.debug('No live session to stop for video %s', videoUUID, lTags(sessionId, videoUUID)) @@ -196,7 +202,7 @@ class LiveManager { logger.info('Stopping live session of video %s', videoUUID, { error, ...lTags(sessionId, videoUUID) }) - this.saveEndingSession(videoUUID, error) + this.saveEndingSession(options) .catch(err => logger.error('Cannot save ending session.', { err, ...lTags(sessionId, videoUUID) })) this.videoSessions.delete(videoUUID) @@ -338,23 +344,23 @@ class LiveManager { localLTags ) - this.stopSessionOf(videoUUID, LiveVideoError.BAD_SOCKET_HEALTH) + this.stopSessionOf({ videoUUID, error: LiveVideoError.BAD_SOCKET_HEALTH }) }) muxingSession.on('duration-exceeded', ({ videoUUID }) => { logger.info('Stopping session of %s: max duration exceeded.', videoUUID, localLTags) - this.stopSessionOf(videoUUID, LiveVideoError.DURATION_EXCEEDED) + this.stopSessionOf({ videoUUID, error: LiveVideoError.DURATION_EXCEEDED }) }) muxingSession.on('quota-exceeded', ({ videoUUID }) => { logger.info('Stopping session of %s: user quota exceeded.', videoUUID, localLTags) - this.stopSessionOf(videoUUID, LiveVideoError.QUOTA_EXCEEDED) + this.stopSessionOf({ videoUUID, error: LiveVideoError.QUOTA_EXCEEDED }) }) muxingSession.on('transcoding-error', ({ videoUUID }) => { - this.stopSessionOf(videoUUID, LiveVideoError.FFMPEG_ERROR) + this.stopSessionOf({ videoUUID, error: LiveVideoError.FFMPEG_ERROR }) }) muxingSession.on('transcoding-end', ({ videoUUID }) => { @@ -377,8 +383,15 @@ class LiveManager { muxingSession.runMuxing() .catch(err => { logger.error('Cannot run muxing.', { err, ...localLTags }) - this.abortSession(sessionId) - this.videoSessions.delete(videoUUID) + + this.muxingSessions.delete(sessionId) + muxingSession.destroy() + + this.stopSessionOf({ + videoUUID, + error: err.liveVideoErrorCode || LiveVideoError.UNKNOWN_ERROR, + errorOnReplay: true // Replay cannot be processed as muxing session failed directly + }) }) } @@ -418,7 +431,7 @@ class LiveManager { this.videoSessions.delete(videoUUID) - this.saveEndingSession(videoUUID, null) + this.saveEndingSession({ videoUUID, error: null }) .catch(err => logger.error('Cannot save ending session.', { err, ...lTags(sessionId) })) } @@ -536,13 +549,23 @@ class LiveManager { }) } - private async saveEndingSession (videoUUID: string, error: LiveVideoErrorType | null) { + private async saveEndingSession (options: { + videoUUID: string + error: LiveVideoErrorType | null + errorOnReplay?: boolean + }) { + const { videoUUID, error, errorOnReplay } = options + const liveSession = await VideoLiveSessionModel.findCurrentSessionOf(videoUUID) if (!liveSession) return liveSession.endDate = new Date() liveSession.error = error + if (errorOnReplay === true) { + liveSession.endingProcessed = true + } + return liveSession.save() } diff --git a/server/core/lib/live/shared/muxing-session.ts b/server/core/lib/live/shared/muxing-session.ts index 85ba42bac..f5343b98f 100644 --- a/server/core/lib/live/shared/muxing-session.ts +++ b/server/core/lib/live/shared/muxing-session.ts @@ -14,7 +14,7 @@ import { removeHLSFileObjectStorageByPath, storeHLSFileFromContent, storeHLSFile import { VideoFileModel } from '@server/models/video/video-file.js' import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist.js' import { MStreamingPlaylistVideo, MUserId, MVideoLiveVideo } from '@server/types/models/index.js' -import { VideoStorage, VideoStreamingPlaylistType } from '@peertube/peertube-models' +import { LiveVideoError, VideoStorage, VideoStreamingPlaylistType } from '@peertube/peertube-models' import { generateHLSMasterPlaylistFilename, generateHlsSha256SegmentsFilename, @@ -490,10 +490,21 @@ class MuxingSession extends EventEmitter { inputLocalUrl: this.inputLocalUrl, inputPublicUrl: this.inputPublicUrl, - toTranscode: this.allResolutions.map(resolution => ({ - resolution, - fps: computeOutputFPS({ inputFPS: this.fps, resolution }) - })), + toTranscode: this.allResolutions.map(resolution => { + let toTranscodeFPS: number + + try { + toTranscodeFPS = computeOutputFPS({ inputFPS: this.fps, resolution }) + } catch (err) { + err.liveVideoErrorCode = LiveVideoError.INVALID_INPUT_VIDEO_STREAM + throw err + } + + return { + resolution, + fps: toTranscodeFPS + } + }), fps: this.fps, bitrate: this.bitrate, diff --git a/server/core/lib/runners/job-handlers/live-rtmp-hls-transcoding-job-handler.ts b/server/core/lib/runners/job-handlers/live-rtmp-hls-transcoding-job-handler.ts index 1cbd6d302..f35d2f586 100644 --- a/server/core/lib/runners/job-handlers/live-rtmp-hls-transcoding-job-handler.ts +++ b/server/core/lib/runners/job-handlers/live-rtmp-hls-transcoding-job-handler.ts @@ -165,7 +165,7 @@ export class LiveRTMPHLSTranscodingJobHandler extends AbstractJobHandler>> { logger.info('Stopping live of video %s after video deletion.', instance.uuid) - LiveManager.Instance.stopSessionOf(instance.uuid, null) + LiveManager.Instance.stopSessionOf({ videoUUID: instance.uuid, error: null }) } @BeforeDestroy