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<CreateO
       cancelled: LiveVideoError.RUNNER_JOB_CANCEL
     }
 
-    LiveManager.Instance.stopSessionOf(privatePayload.videoUUID, errorType[type])
+    LiveManager.Instance.stopSessionOf({ videoUUID: privatePayload.videoUUID, error: errorType[type] })
 
     logger.info('Runner live RTMP to HLS job %s for video %s %s.', runnerJob.uuid, videoUUID, type, this.lTags(runnerJob.uuid, videoUUID))
   }
diff --git a/server/core/lib/video-blacklist.ts b/server/core/lib/video-blacklist.ts
index 3fd0a59ed..311f6e208 100644
--- a/server/core/lib/video-blacklist.ts
+++ b/server/core/lib/video-blacklist.ts
@@ -81,7 +81,7 @@ async function blacklistVideo (videoInstance: MVideoAccountLight, options: Video
   }
 
   if (videoInstance.isLive) {
-    LiveManager.Instance.stopSessionOf(videoInstance.uuid, LiveVideoError.BLACKLISTED)
+    LiveManager.Instance.stopSessionOf({ videoUUID: videoInstance.uuid, error: LiveVideoError.BLACKLISTED })
   }
 
   Notifier.Instance.notifyOnVideoBlacklist(blacklist)
diff --git a/server/core/models/video/video.ts b/server/core/models/video/video.ts
index f418ee6c1..300667d4e 100644
--- a/server/core/models/video/video.ts
+++ b/server/core/models/video/video.ts
@@ -808,7 +808,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
 
     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