diff options
author | Chocobozzz <me@florianbigard.com> | 2022-05-03 11:38:07 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2022-05-03 14:49:15 +0200 |
commit | 26e3e98ff0e222a9fb9226938ac6902af77921bd (patch) | |
tree | 73d1c6f2524e380862d3365f12043fc319d40841 /server/lib | |
parent | 86c5229b4d726202378ef46854383bcafca22310 (diff) | |
download | PeerTube-26e3e98ff0e222a9fb9226938ac6902af77921bd.tar.gz PeerTube-26e3e98ff0e222a9fb9226938ac6902af77921bd.tar.zst PeerTube-26e3e98ff0e222a9fb9226938ac6902af77921bd.zip |
Support live session in server
Diffstat (limited to 'server/lib')
-rw-r--r-- | server/lib/job-queue/handlers/video-live-ending.ts | 87 | ||||
-rw-r--r-- | server/lib/live/live-manager.ts | 64 | ||||
-rw-r--r-- | server/lib/live/shared/muxing-session.ts | 10 | ||||
-rw-r--r-- | server/lib/video-blacklist.ts | 4 | ||||
-rw-r--r-- | server/lib/video-state.ts | 4 |
5 files changed, 123 insertions, 46 deletions
diff --git a/server/lib/job-queue/handlers/video-live-ending.ts b/server/lib/job-queue/handlers/video-live-ending.ts index 1e290338c..55fd09344 100644 --- a/server/lib/job-queue/handlers/video-live-ending.ts +++ b/server/lib/job-queue/handlers/video-live-ending.ts | |||
@@ -15,13 +15,14 @@ import { generateVideoMiniature } from '@server/lib/thumbnail' | |||
15 | import { generateHlsPlaylistResolutionFromTS } from '@server/lib/transcoding/transcoding' | 15 | import { generateHlsPlaylistResolutionFromTS } from '@server/lib/transcoding/transcoding' |
16 | import { moveToNextState } from '@server/lib/video-state' | 16 | import { moveToNextState } from '@server/lib/video-state' |
17 | import { VideoModel } from '@server/models/video/video' | 17 | import { VideoModel } from '@server/models/video/video' |
18 | import { VideoBlacklistModel } from '@server/models/video/video-blacklist' | ||
18 | import { VideoFileModel } from '@server/models/video/video-file' | 19 | import { VideoFileModel } from '@server/models/video/video-file' |
19 | import { VideoLiveModel } from '@server/models/video/video-live' | 20 | import { VideoLiveModel } from '@server/models/video/video-live' |
21 | import { VideoLiveSessionModel } from '@server/models/video/video-live-session' | ||
20 | import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist' | 22 | import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist' |
21 | import { MVideo, MVideoLive, MVideoWithAllFiles } from '@server/types/models' | 23 | import { MVideo, MVideoLive, MVideoLiveSession, MVideoWithAllFiles } from '@server/types/models' |
22 | import { ThumbnailType, VideoLiveEndingPayload, VideoState } from '@shared/models' | 24 | import { ThumbnailType, VideoLiveEndingPayload, VideoState } from '@shared/models' |
23 | import { logger } from '../../../helpers/logger' | 25 | import { logger } from '../../../helpers/logger' |
24 | import { VideoBlacklistModel } from '@server/models/video/video-blacklist' | ||
25 | 26 | ||
26 | async function processVideoLiveEnding (job: Job) { | 27 | async function processVideoLiveEnding (job: Job) { |
27 | const payload = job.data as VideoLiveEndingPayload | 28 | const payload = job.data as VideoLiveEndingPayload |
@@ -32,27 +33,28 @@ async function processVideoLiveEnding (job: Job) { | |||
32 | logger.warn('Video live %d does not exist anymore. Cannot process live ending.', payload.videoId) | 33 | logger.warn('Video live %d does not exist anymore. Cannot process live ending.', payload.videoId) |
33 | } | 34 | } |
34 | 35 | ||
35 | const video = await VideoModel.load(payload.videoId) | 36 | const liveVideo = await VideoModel.load(payload.videoId) |
36 | const live = await VideoLiveModel.loadByVideoId(payload.videoId) | 37 | const live = await VideoLiveModel.loadByVideoId(payload.videoId) |
38 | const liveSession = await VideoLiveSessionModel.load(payload.liveSessionId) | ||
37 | 39 | ||
38 | if (!video || !live) { | 40 | if (!liveVideo || !live || !liveSession) { |
39 | logError() | 41 | logError() |
40 | return | 42 | return |
41 | } | 43 | } |
42 | 44 | ||
43 | LiveSegmentShaStore.Instance.cleanupShaSegments(video.uuid) | 45 | LiveSegmentShaStore.Instance.cleanupShaSegments(liveVideo.uuid) |
44 | 46 | ||
45 | if (live.saveReplay !== true) { | 47 | if (live.saveReplay !== true) { |
46 | return cleanupLiveAndFederate(video) | 48 | return cleanupLiveAndFederate({ liveVideo }) |
47 | } | 49 | } |
48 | 50 | ||
49 | if (live.permanentLive) { | 51 | if (live.permanentLive) { |
50 | await saveReplayToExternalVideo(video, payload.publishedAt, payload.replayDirectory) | 52 | await saveReplayToExternalVideo({ liveVideo, liveSession, publishedAt: payload.publishedAt, replayDirectory: payload.replayDirectory }) |
51 | 53 | ||
52 | return cleanupLiveAndFederate(video) | 54 | return cleanupLiveAndFederate({ liveVideo }) |
53 | } | 55 | } |
54 | 56 | ||
55 | return replaceLiveByReplay(video, live, payload.replayDirectory) | 57 | return replaceLiveByReplay({ liveVideo, live, liveSession, replayDirectory: payload.replayDirectory }) |
56 | } | 58 | } |
57 | 59 | ||
58 | // --------------------------------------------------------------------------- | 60 | // --------------------------------------------------------------------------- |
@@ -63,7 +65,14 @@ export { | |||
63 | 65 | ||
64 | // --------------------------------------------------------------------------- | 66 | // --------------------------------------------------------------------------- |
65 | 67 | ||
66 | async function saveReplayToExternalVideo (liveVideo: MVideo, publishedAt: string, replayDirectory: string) { | 68 | async function saveReplayToExternalVideo (options: { |
69 | liveVideo: MVideo | ||
70 | liveSession: MVideoLiveSession | ||
71 | publishedAt: string | ||
72 | replayDirectory: string | ||
73 | }) { | ||
74 | const { liveVideo, liveSession, publishedAt, replayDirectory } = options | ||
75 | |||
67 | await cleanupTMPLiveFiles(getLiveDirectory(liveVideo)) | 76 | await cleanupTMPLiveFiles(getLiveDirectory(liveVideo)) |
68 | 77 | ||
69 | const video = new VideoModel({ | 78 | const video = new VideoModel({ |
@@ -78,7 +87,7 @@ async function saveReplayToExternalVideo (liveVideo: MVideo, publishedAt: string | |||
78 | language: liveVideo.language, | 87 | language: liveVideo.language, |
79 | commentsEnabled: liveVideo.commentsEnabled, | 88 | commentsEnabled: liveVideo.commentsEnabled, |
80 | downloadEnabled: liveVideo.downloadEnabled, | 89 | downloadEnabled: liveVideo.downloadEnabled, |
81 | waitTranscoding: liveVideo.waitTranscoding, | 90 | waitTranscoding: true, |
82 | nsfw: liveVideo.nsfw, | 91 | nsfw: liveVideo.nsfw, |
83 | description: liveVideo.description, | 92 | description: liveVideo.description, |
84 | support: liveVideo.support, | 93 | support: liveVideo.support, |
@@ -94,6 +103,9 @@ async function saveReplayToExternalVideo (liveVideo: MVideo, publishedAt: string | |||
94 | 103 | ||
95 | await video.save() | 104 | await video.save() |
96 | 105 | ||
106 | liveSession.replayVideoId = video.id | ||
107 | await liveSession.save() | ||
108 | |||
97 | // If live is blacklisted, also blacklist the replay | 109 | // If live is blacklisted, also blacklist the replay |
98 | const blacklist = await VideoBlacklistModel.loadByVideoId(liveVideo.id) | 110 | const blacklist = await VideoBlacklistModel.loadByVideoId(liveVideo.id) |
99 | if (blacklist) { | 111 | if (blacklist) { |
@@ -105,7 +117,7 @@ async function saveReplayToExternalVideo (liveVideo: MVideo, publishedAt: string | |||
105 | }) | 117 | }) |
106 | } | 118 | } |
107 | 119 | ||
108 | await assignReplaysToVideo(video, replayDirectory) | 120 | await assignReplayFilesToVideo({ video, replayDirectory }) |
109 | 121 | ||
110 | await remove(replayDirectory) | 122 | await remove(replayDirectory) |
111 | 123 | ||
@@ -117,18 +129,29 @@ async function saveReplayToExternalVideo (liveVideo: MVideo, publishedAt: string | |||
117 | await moveToNextState({ video, isNewVideo: true }) | 129 | await moveToNextState({ video, isNewVideo: true }) |
118 | } | 130 | } |
119 | 131 | ||
120 | async function replaceLiveByReplay (video: MVideo, live: MVideoLive, replayDirectory: string) { | 132 | async function replaceLiveByReplay (options: { |
121 | await cleanupTMPLiveFiles(getLiveDirectory(video)) | 133 | liveVideo: MVideo |
134 | liveSession: MVideoLiveSession | ||
135 | live: MVideoLive | ||
136 | replayDirectory: string | ||
137 | }) { | ||
138 | const { liveVideo, liveSession, live, replayDirectory } = options | ||
139 | |||
140 | await cleanupTMPLiveFiles(getLiveDirectory(liveVideo)) | ||
122 | 141 | ||
123 | await live.destroy() | 142 | await live.destroy() |
124 | 143 | ||
125 | video.isLive = false | 144 | liveVideo.isLive = false |
126 | video.state = VideoState.TO_TRANSCODE | 145 | liveVideo.waitTranscoding = true |
146 | liveVideo.state = VideoState.TO_TRANSCODE | ||
127 | 147 | ||
128 | await video.save() | 148 | await liveVideo.save() |
149 | |||
150 | liveSession.replayVideoId = liveVideo.id | ||
151 | await liveSession.save() | ||
129 | 152 | ||
130 | // Remove old HLS playlist video files | 153 | // Remove old HLS playlist video files |
131 | const videoWithFiles = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.id) | 154 | const videoWithFiles = await VideoModel.loadAndPopulateAccountAndServerAndTags(liveVideo.id) |
132 | 155 | ||
133 | const hlsPlaylist = videoWithFiles.getHLSPlaylist() | 156 | const hlsPlaylist = videoWithFiles.getHLSPlaylist() |
134 | await VideoFileModel.removeHLSFilesOfVideoId(hlsPlaylist.id) | 157 | await VideoFileModel.removeHLSFilesOfVideoId(hlsPlaylist.id) |
@@ -139,7 +162,7 @@ async function replaceLiveByReplay (video: MVideo, live: MVideoLive, replayDirec | |||
139 | hlsPlaylist.segmentsSha256Filename = generateHlsSha256SegmentsFilename() | 162 | hlsPlaylist.segmentsSha256Filename = generateHlsSha256SegmentsFilename() |
140 | await hlsPlaylist.save() | 163 | await hlsPlaylist.save() |
141 | 164 | ||
142 | await assignReplaysToVideo(videoWithFiles, replayDirectory) | 165 | await assignReplayFilesToVideo({ video: videoWithFiles, replayDirectory }) |
143 | 166 | ||
144 | await remove(getLiveReplayBaseDirectory(videoWithFiles)) | 167 | await remove(getLiveReplayBaseDirectory(videoWithFiles)) |
145 | 168 | ||
@@ -150,7 +173,7 @@ async function replaceLiveByReplay (video: MVideo, live: MVideoLive, replayDirec | |||
150 | videoFile: videoWithFiles.getMaxQualityFile(), | 173 | videoFile: videoWithFiles.getMaxQualityFile(), |
151 | type: ThumbnailType.MINIATURE | 174 | type: ThumbnailType.MINIATURE |
152 | }) | 175 | }) |
153 | await video.addAndSaveThumbnail(miniature) | 176 | await videoWithFiles.addAndSaveThumbnail(miniature) |
154 | } | 177 | } |
155 | 178 | ||
156 | if (videoWithFiles.getPreview().automaticallyGenerated === true) { | 179 | if (videoWithFiles.getPreview().automaticallyGenerated === true) { |
@@ -159,13 +182,19 @@ async function replaceLiveByReplay (video: MVideo, live: MVideoLive, replayDirec | |||
159 | videoFile: videoWithFiles.getMaxQualityFile(), | 182 | videoFile: videoWithFiles.getMaxQualityFile(), |
160 | type: ThumbnailType.PREVIEW | 183 | type: ThumbnailType.PREVIEW |
161 | }) | 184 | }) |
162 | await video.addAndSaveThumbnail(preview) | 185 | await videoWithFiles.addAndSaveThumbnail(preview) |
163 | } | 186 | } |
164 | 187 | ||
165 | await moveToNextState({ video: videoWithFiles, isNewVideo: false }) | 188 | // We consider this is a new video |
189 | await moveToNextState({ video: videoWithFiles, isNewVideo: true }) | ||
166 | } | 190 | } |
167 | 191 | ||
168 | async function assignReplaysToVideo (video: MVideo, replayDirectory: string) { | 192 | async function assignReplayFilesToVideo (options: { |
193 | video: MVideo | ||
194 | replayDirectory: string | ||
195 | }) { | ||
196 | const { video, replayDirectory } = options | ||
197 | |||
169 | let durationDone = false | 198 | let durationDone = false |
170 | 199 | ||
171 | const concatenatedTsFiles = await readdir(replayDirectory) | 200 | const concatenatedTsFiles = await readdir(replayDirectory) |
@@ -197,11 +226,15 @@ async function assignReplaysToVideo (video: MVideo, replayDirectory: string) { | |||
197 | return video | 226 | return video |
198 | } | 227 | } |
199 | 228 | ||
200 | async function cleanupLiveAndFederate (video: MVideo) { | 229 | async function cleanupLiveAndFederate (options: { |
201 | const streamingPlaylist = await VideoStreamingPlaylistModel.loadHLSPlaylistByVideo(video.id) | 230 | liveVideo: MVideo |
202 | await cleanupLive(video, streamingPlaylist) | 231 | }) { |
232 | const { liveVideo } = options | ||
233 | |||
234 | const streamingPlaylist = await VideoStreamingPlaylistModel.loadHLSPlaylistByVideo(liveVideo.id) | ||
235 | await cleanupLive(liveVideo, streamingPlaylist) | ||
203 | 236 | ||
204 | const fullVideo = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.id) | 237 | const fullVideo = await VideoModel.loadAndPopulateAccountAndServerAndTags(liveVideo.id) |
205 | return federateVideoIfNeeded(fullVideo, false, undefined) | 238 | return federateVideoIfNeeded(fullVideo, false, undefined) |
206 | } | 239 | } |
207 | 240 | ||
diff --git a/server/lib/live/live-manager.ts b/server/lib/live/live-manager.ts index da09aa05c..df2804a0e 100644 --- a/server/lib/live/live-manager.ts +++ b/server/lib/live/live-manager.ts | |||
@@ -17,10 +17,11 @@ import { P2P_MEDIA_LOADER_PEER_VERSION, VIDEO_LIVE } from '@server/initializers/ | |||
17 | import { UserModel } from '@server/models/user/user' | 17 | import { UserModel } from '@server/models/user/user' |
18 | import { VideoModel } from '@server/models/video/video' | 18 | import { VideoModel } from '@server/models/video/video' |
19 | import { VideoLiveModel } from '@server/models/video/video-live' | 19 | import { VideoLiveModel } from '@server/models/video/video-live' |
20 | import { VideoLiveSessionModel } from '@server/models/video/video-live-session' | ||
20 | import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist' | 21 | import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist' |
21 | import { MStreamingPlaylistVideo, MVideo, MVideoLiveVideo } from '@server/types/models' | 22 | import { MStreamingPlaylistVideo, MVideo, MVideoLiveSession, MVideoLiveVideo } from '@server/types/models' |
22 | import { wait } from '@shared/core-utils' | 23 | import { wait } from '@shared/core-utils' |
23 | import { VideoState, VideoStreamingPlaylistType } from '@shared/models' | 24 | import { LiveVideoError, VideoState, VideoStreamingPlaylistType } from '@shared/models' |
24 | import { federateVideoIfNeeded } from '../activitypub/videos' | 25 | import { federateVideoIfNeeded } from '../activitypub/videos' |
25 | import { JobQueue } from '../job-queue' | 26 | import { JobQueue } from '../job-queue' |
26 | import { generateHLSMasterPlaylistFilename, generateHlsSha256SegmentsFilename, getLiveReplayBaseDirectory } from '../paths' | 27 | import { generateHLSMasterPlaylistFilename, generateHlsSha256SegmentsFilename, getLiveReplayBaseDirectory } from '../paths' |
@@ -174,10 +175,13 @@ class LiveManager { | |||
174 | return !!this.rtmpServer | 175 | return !!this.rtmpServer |
175 | } | 176 | } |
176 | 177 | ||
177 | stopSessionOf (videoId: number) { | 178 | stopSessionOf (videoId: number, error: LiveVideoError | null) { |
178 | const sessionId = this.videoSessions.get(videoId) | 179 | const sessionId = this.videoSessions.get(videoId) |
179 | if (!sessionId) return | 180 | if (!sessionId) return |
180 | 181 | ||
182 | this.saveEndingSession(videoId, error) | ||
183 | .catch(err => logger.error('Cannot save ending session.', { err, ...lTags(sessionId) })) | ||
184 | |||
181 | this.videoSessions.delete(videoId) | 185 | this.videoSessions.delete(videoId) |
182 | this.abortSession(sessionId) | 186 | this.abortSession(sessionId) |
183 | } | 187 | } |
@@ -274,6 +278,8 @@ class LiveManager { | |||
274 | const videoUUID = videoLive.Video.uuid | 278 | const videoUUID = videoLive.Video.uuid |
275 | const localLTags = lTags(sessionId, videoUUID) | 279 | const localLTags = lTags(sessionId, videoUUID) |
276 | 280 | ||
281 | const liveSession = await this.saveStartingSession(videoLive) | ||
282 | |||
277 | const user = await UserModel.loadByLiveId(videoLive.id) | 283 | const user = await UserModel.loadByLiveId(videoLive.id) |
278 | LiveQuotaStore.Instance.addNewLive(user.id, videoLive.id) | 284 | LiveQuotaStore.Instance.addNewLive(user.id, videoLive.id) |
279 | 285 | ||
@@ -299,24 +305,27 @@ class LiveManager { | |||
299 | localLTags | 305 | localLTags |
300 | ) | 306 | ) |
301 | 307 | ||
302 | this.stopSessionOf(videoId) | 308 | this.stopSessionOf(videoId, LiveVideoError.BAD_SOCKET_HEALTH) |
303 | }) | 309 | }) |
304 | 310 | ||
305 | muxingSession.on('duration-exceeded', ({ videoId }) => { | 311 | muxingSession.on('duration-exceeded', ({ videoId }) => { |
306 | logger.info('Stopping session of %s: max duration exceeded.', videoUUID, localLTags) | 312 | logger.info('Stopping session of %s: max duration exceeded.', videoUUID, localLTags) |
307 | 313 | ||
308 | this.stopSessionOf(videoId) | 314 | this.stopSessionOf(videoId, LiveVideoError.DURATION_EXCEEDED) |
309 | }) | 315 | }) |
310 | 316 | ||
311 | muxingSession.on('quota-exceeded', ({ videoId }) => { | 317 | muxingSession.on('quota-exceeded', ({ videoId }) => { |
312 | logger.info('Stopping session of %s: user quota exceeded.', videoUUID, localLTags) | 318 | logger.info('Stopping session of %s: user quota exceeded.', videoUUID, localLTags) |
313 | 319 | ||
314 | this.stopSessionOf(videoId) | 320 | this.stopSessionOf(videoId, LiveVideoError.QUOTA_EXCEEDED) |
321 | }) | ||
322 | |||
323 | muxingSession.on('ffmpeg-error', ({ videoId }) => { | ||
324 | this.stopSessionOf(videoId, LiveVideoError.FFMPEG_ERROR) | ||
315 | }) | 325 | }) |
316 | 326 | ||
317 | muxingSession.on('ffmpeg-error', ({ sessionId }) => this.abortSession(sessionId)) | ||
318 | muxingSession.on('ffmpeg-end', ({ videoId }) => { | 327 | muxingSession.on('ffmpeg-end', ({ videoId }) => { |
319 | this.onMuxingFFmpegEnd(videoId) | 328 | this.onMuxingFFmpegEnd(videoId, sessionId) |
320 | }) | 329 | }) |
321 | 330 | ||
322 | muxingSession.on('after-cleanup', ({ videoId }) => { | 331 | muxingSession.on('after-cleanup', ({ videoId }) => { |
@@ -324,7 +333,7 @@ class LiveManager { | |||
324 | 333 | ||
325 | muxingSession.destroy() | 334 | muxingSession.destroy() |
326 | 335 | ||
327 | return this.onAfterMuxingCleanup({ videoId }) | 336 | return this.onAfterMuxingCleanup({ videoId, liveSession }) |
328 | .catch(err => logger.error('Error in end transmuxing.', { err, ...localLTags })) | 337 | .catch(err => logger.error('Error in end transmuxing.', { err, ...localLTags })) |
329 | }) | 338 | }) |
330 | 339 | ||
@@ -365,15 +374,19 @@ class LiveManager { | |||
365 | } | 374 | } |
366 | } | 375 | } |
367 | 376 | ||
368 | private onMuxingFFmpegEnd (videoId: number) { | 377 | private onMuxingFFmpegEnd (videoId: number, sessionId: string) { |
369 | this.videoSessions.delete(videoId) | 378 | this.videoSessions.delete(videoId) |
379 | |||
380 | this.saveEndingSession(videoId, null) | ||
381 | .catch(err => logger.error('Cannot save ending session.', { err, ...lTags(sessionId) })) | ||
370 | } | 382 | } |
371 | 383 | ||
372 | private async onAfterMuxingCleanup (options: { | 384 | private async onAfterMuxingCleanup (options: { |
373 | videoId: number | string | 385 | videoId: number | string |
386 | liveSession?: MVideoLiveSession | ||
374 | cleanupNow?: boolean // Default false | 387 | cleanupNow?: boolean // Default false |
375 | }) { | 388 | }) { |
376 | const { videoId, cleanupNow = false } = options | 389 | const { videoId, liveSession: liveSessionArg, cleanupNow = false } = options |
377 | 390 | ||
378 | try { | 391 | try { |
379 | const fullVideo = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) | 392 | const fullVideo = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) |
@@ -381,13 +394,25 @@ class LiveManager { | |||
381 | 394 | ||
382 | const live = await VideoLiveModel.loadByVideoId(fullVideo.id) | 395 | const live = await VideoLiveModel.loadByVideoId(fullVideo.id) |
383 | 396 | ||
397 | const liveSession = liveSessionArg ?? await VideoLiveSessionModel.findCurrentSessionOf(fullVideo.id) | ||
398 | |||
399 | // On server restart during a live | ||
400 | if (!liveSession.endDate) { | ||
401 | liveSession.endDate = new Date() | ||
402 | await liveSession.save() | ||
403 | } | ||
404 | |||
384 | JobQueue.Instance.createJob({ | 405 | JobQueue.Instance.createJob({ |
385 | type: 'video-live-ending', | 406 | type: 'video-live-ending', |
386 | payload: { | 407 | payload: { |
387 | videoId: fullVideo.id, | 408 | videoId: fullVideo.id, |
409 | |||
388 | replayDirectory: live.saveReplay | 410 | replayDirectory: live.saveReplay |
389 | ? await this.findReplayDirectory(fullVideo) | 411 | ? await this.findReplayDirectory(fullVideo) |
390 | : undefined, | 412 | : undefined, |
413 | |||
414 | liveSessionId: liveSession.id, | ||
415 | |||
391 | publishedAt: fullVideo.publishedAt.toISOString() | 416 | publishedAt: fullVideo.publishedAt.toISOString() |
392 | } | 417 | } |
393 | }, { delay: cleanupNow ? 0 : VIDEO_LIVE.CLEANUP_DELAY }) | 418 | }, { delay: cleanupNow ? 0 : VIDEO_LIVE.CLEANUP_DELAY }) |
@@ -445,6 +470,23 @@ class LiveManager { | |||
445 | return playlist.save() | 470 | return playlist.save() |
446 | } | 471 | } |
447 | 472 | ||
473 | private saveStartingSession (videoLive: MVideoLiveVideo) { | ||
474 | const liveSession = new VideoLiveSessionModel({ | ||
475 | startDate: new Date(), | ||
476 | liveVideoId: videoLive.videoId | ||
477 | }) | ||
478 | |||
479 | return liveSession.save() | ||
480 | } | ||
481 | |||
482 | private async saveEndingSession (videoId: number, error: LiveVideoError | null) { | ||
483 | const liveSession = await VideoLiveSessionModel.findCurrentSessionOf(videoId) | ||
484 | liveSession.endDate = new Date() | ||
485 | liveSession.error = error | ||
486 | |||
487 | return liveSession.save() | ||
488 | } | ||
489 | |||
448 | static get Instance () { | 490 | static get Instance () { |
449 | return this.instance || (this.instance = new this()) | 491 | return this.instance || (this.instance = new this()) |
450 | } | 492 | } |
diff --git a/server/lib/live/shared/muxing-session.ts b/server/lib/live/shared/muxing-session.ts index 588ee8749..1ee9b430f 100644 --- a/server/lib/live/shared/muxing-session.ts +++ b/server/lib/live/shared/muxing-session.ts | |||
@@ -28,7 +28,7 @@ interface MuxingSessionEvents { | |||
28 | 'quota-exceeded': ({ videoId: number }) => void | 28 | 'quota-exceeded': ({ videoId: number }) => void |
29 | 29 | ||
30 | 'ffmpeg-end': ({ videoId: number }) => void | 30 | 'ffmpeg-end': ({ videoId: number }) => void |
31 | 'ffmpeg-error': ({ sessionId: string }) => void | 31 | 'ffmpeg-error': ({ videoId: string }) => void |
32 | 32 | ||
33 | 'after-cleanup': ({ videoId: number }) => void | 33 | 'after-cleanup': ({ videoId: number }) => void |
34 | } | 34 | } |
@@ -164,7 +164,11 @@ class MuxingSession extends EventEmitter { | |||
164 | this.onFFmpegError({ err, stdout, stderr, outPath: this.outDirectory, ffmpegShellCommand }) | 164 | this.onFFmpegError({ err, stdout, stderr, outPath: this.outDirectory, ffmpegShellCommand }) |
165 | }) | 165 | }) |
166 | 166 | ||
167 | this.ffmpegCommand.on('end', () => this.onFFmpegEnded(this.outDirectory)) | 167 | this.ffmpegCommand.on('end', () => { |
168 | this.emit('ffmpeg-end', ({ videoId: this.videoId })) | ||
169 | |||
170 | this.onFFmpegEnded(this.outDirectory) | ||
171 | }) | ||
168 | 172 | ||
169 | this.ffmpegCommand.run() | 173 | this.ffmpegCommand.run() |
170 | } | 174 | } |
@@ -197,7 +201,7 @@ class MuxingSession extends EventEmitter { | |||
197 | 201 | ||
198 | logger.error('Live transcoding error.', { err, stdout, stderr, ffmpegShellCommand, ...this.lTags() }) | 202 | logger.error('Live transcoding error.', { err, stdout, stderr, ffmpegShellCommand, ...this.lTags() }) |
199 | 203 | ||
200 | this.emit('ffmpeg-error', ({ sessionId: this.sessionId })) | 204 | this.emit('ffmpeg-error', ({ videoId: this.videoId })) |
201 | } | 205 | } |
202 | 206 | ||
203 | private onFFmpegEnded (outPath: string) { | 207 | private onFFmpegEnded (outPath: string) { |
diff --git a/server/lib/video-blacklist.ts b/server/lib/video-blacklist.ts index 91f44cb11..fd5837a3a 100644 --- a/server/lib/video-blacklist.ts +++ b/server/lib/video-blacklist.ts | |||
@@ -9,7 +9,7 @@ import { | |||
9 | MVideoFullLight, | 9 | MVideoFullLight, |
10 | MVideoWithBlacklistLight | 10 | MVideoWithBlacklistLight |
11 | } from '@server/types/models' | 11 | } from '@server/types/models' |
12 | import { UserRight, VideoBlacklistCreate, VideoBlacklistType } from '../../shared/models' | 12 | import { LiveVideoError, UserRight, VideoBlacklistCreate, VideoBlacklistType } from '../../shared/models' |
13 | import { UserAdminFlag } from '../../shared/models/users/user-flag.model' | 13 | import { UserAdminFlag } from '../../shared/models/users/user-flag.model' |
14 | import { logger, loggerTagsFactory } from '../helpers/logger' | 14 | import { logger, loggerTagsFactory } from '../helpers/logger' |
15 | import { CONFIG } from '../initializers/config' | 15 | import { CONFIG } from '../initializers/config' |
@@ -81,7 +81,7 @@ async function blacklistVideo (videoInstance: MVideoAccountLight, options: Video | |||
81 | } | 81 | } |
82 | 82 | ||
83 | if (videoInstance.isLive) { | 83 | if (videoInstance.isLive) { |
84 | LiveManager.Instance.stopSessionOf(videoInstance.id) | 84 | LiveManager.Instance.stopSessionOf(videoInstance.id, LiveVideoError.BLACKLISTED) |
85 | } | 85 | } |
86 | 86 | ||
87 | Notifier.Instance.notifyOnVideoBlacklist(blacklist) | 87 | Notifier.Instance.notifyOnVideoBlacklist(blacklist) |
diff --git a/server/lib/video-state.ts b/server/lib/video-state.ts index 7b207eb87..ae2725d65 100644 --- a/server/lib/video-state.ts +++ b/server/lib/video-state.ts | |||
@@ -126,12 +126,10 @@ async function moveToPublishedState (options: { | |||
126 | const { video, isNewVideo, transaction, previousVideoState } = options | 126 | const { video, isNewVideo, transaction, previousVideoState } = options |
127 | const previousState = previousVideoState ?? video.state | 127 | const previousState = previousVideoState ?? video.state |
128 | 128 | ||
129 | logger.info('Publishing video %s.', video.uuid, { previousState, tags: [ video.uuid ] }) | 129 | logger.info('Publishing video %s.', video.uuid, { isNewVideo, previousState, tags: [ video.uuid ] }) |
130 | 130 | ||
131 | await video.setNewState(VideoState.PUBLISHED, isNewVideo, transaction) | 131 | await video.setNewState(VideoState.PUBLISHED, isNewVideo, transaction) |
132 | 132 | ||
133 | // If the video was not published, we consider it is a new one for other instances | ||
134 | // Live videos are always federated, so it's not a new video | ||
135 | await federateVideoIfNeeded(video, isNewVideo, transaction) | 133 | await federateVideoIfNeeded(video, isNewVideo, transaction) |
136 | 134 | ||
137 | if (previousState === VideoState.TO_EDIT) { | 135 | if (previousState === VideoState.TO_EDIT) { |