diff options
author | Chocobozzz <me@florianbigard.com> | 2022-07-22 15:22:21 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2022-07-22 15:22:21 +0200 |
commit | c8fa571f32b10c083fab07f28d2ef55895ef40af (patch) | |
tree | fd50f90cc7643333984ed3b19f6a06f2e9f54feb /server | |
parent | a77c5ff3622ab75d0c22241d0ef72053deaa7926 (diff) | |
download | PeerTube-c8fa571f32b10c083fab07f28d2ef55895ef40af.tar.gz PeerTube-c8fa571f32b10c083fab07f28d2ef55895ef40af.tar.zst PeerTube-c8fa571f32b10c083fab07f28d2ef55895ef40af.zip |
Clearer live session
Get the save replay setting when the session started to prevent
inconsistent behaviour when the setting changed before the session was
processed by the live ending job
Display more information about the potential session replay in live
modal information
Diffstat (limited to 'server')
-rw-r--r-- | server/initializers/constants.ts | 2 | ||||
-rw-r--r-- | server/initializers/migrations/0720-session-ending-processed.ts | 56 | ||||
-rw-r--r-- | server/lib/job-queue/handlers/video-live-ending.ts | 77 | ||||
-rw-r--r-- | server/lib/live/live-manager.ts | 4 | ||||
-rw-r--r-- | server/models/video/video-live-session.ts | 10 | ||||
-rw-r--r-- | server/tests/api/live/live-save-replay.ts | 8 |
6 files changed, 122 insertions, 35 deletions
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 8cb4d5f4a..e3f7ceb4a 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -24,7 +24,7 @@ import { CONFIG, registerConfigChangedHandler } from './config' | |||
24 | 24 | ||
25 | // --------------------------------------------------------------------------- | 25 | // --------------------------------------------------------------------------- |
26 | 26 | ||
27 | const LAST_MIGRATION_VERSION = 715 | 27 | const LAST_MIGRATION_VERSION = 720 |
28 | 28 | ||
29 | // --------------------------------------------------------------------------- | 29 | // --------------------------------------------------------------------------- |
30 | 30 | ||
diff --git a/server/initializers/migrations/0720-session-ending-processed.ts b/server/initializers/migrations/0720-session-ending-processed.ts new file mode 100644 index 000000000..74ffb39a0 --- /dev/null +++ b/server/initializers/migrations/0720-session-ending-processed.ts | |||
@@ -0,0 +1,56 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction | ||
5 | queryInterface: Sequelize.QueryInterface | ||
6 | sequelize: Sequelize.Sequelize | ||
7 | db: any | ||
8 | }): Promise<void> { | ||
9 | const { transaction } = utils | ||
10 | |||
11 | { | ||
12 | const data = { | ||
13 | type: Sequelize.BOOLEAN, | ||
14 | defaultValue: null, | ||
15 | allowNull: true | ||
16 | } | ||
17 | await utils.queryInterface.addColumn('videoLiveSession', 'endingProcessed', data, { transaction }) | ||
18 | await utils.queryInterface.addColumn('videoLiveSession', 'saveReplay', data, { transaction }) | ||
19 | } | ||
20 | |||
21 | { | ||
22 | const query = `UPDATE "videoLiveSession" SET "saveReplay" = ( | ||
23 | SELECT "videoLive"."saveReplay" FROM "videoLive" WHERE "videoLive"."videoId" = "videoLiveSession"."liveVideoId" | ||
24 | ) WHERE "videoLiveSession"."liveVideoId" IS NOT NULL` | ||
25 | await utils.sequelize.query(query, { transaction }) | ||
26 | } | ||
27 | |||
28 | { | ||
29 | const query = `UPDATE "videoLiveSession" SET "saveReplay" = FALSE WHERE "saveReplay" IS NULL` | ||
30 | await utils.sequelize.query(query, { transaction }) | ||
31 | } | ||
32 | |||
33 | { | ||
34 | const query = `UPDATE "videoLiveSession" SET "endingProcessed" = TRUE` | ||
35 | await utils.sequelize.query(query, { transaction }) | ||
36 | } | ||
37 | |||
38 | { | ||
39 | const data = { | ||
40 | type: Sequelize.BOOLEAN, | ||
41 | defaultValue: null, | ||
42 | allowNull: false | ||
43 | } | ||
44 | await utils.queryInterface.changeColumn('videoLiveSession', 'endingProcessed', data, { transaction }) | ||
45 | await utils.queryInterface.changeColumn('videoLiveSession', 'saveReplay', data, { transaction }) | ||
46 | } | ||
47 | } | ||
48 | |||
49 | function down (options) { | ||
50 | throw new Error('Not implemented.') | ||
51 | } | ||
52 | |||
53 | export { | ||
54 | up, | ||
55 | down | ||
56 | } | ||
diff --git a/server/lib/job-queue/handlers/video-live-ending.ts b/server/lib/job-queue/handlers/video-live-ending.ts index 0e1bfb240..10507fb83 100644 --- a/server/lib/job-queue/handlers/video-live-ending.ts +++ b/server/lib/job-queue/handlers/video-live-ending.ts | |||
@@ -30,26 +30,36 @@ async function processVideoLiveEnding (job: Job) { | |||
30 | logger.warn('Video live %d does not exist anymore. Cannot process live ending.', payload.videoId, lTags()) | 30 | logger.warn('Video live %d does not exist anymore. Cannot process live ending.', payload.videoId, lTags()) |
31 | } | 31 | } |
32 | 32 | ||
33 | const liveVideo = await VideoModel.load(payload.videoId) | 33 | const video = await VideoModel.load(payload.videoId) |
34 | const live = await VideoLiveModel.loadByVideoId(payload.videoId) | 34 | const live = await VideoLiveModel.loadByVideoId(payload.videoId) |
35 | const liveSession = await VideoLiveSessionModel.load(payload.liveSessionId) | 35 | const liveSession = await VideoLiveSessionModel.load(payload.liveSessionId) |
36 | 36 | ||
37 | if (!liveVideo || !live || !liveSession) { | 37 | const permanentLive = live.permanentLive |
38 | |||
39 | if (!video || !live || !liveSession) { | ||
38 | logError() | 40 | logError() |
39 | return | 41 | return |
40 | } | 42 | } |
41 | 43 | ||
42 | if (live.saveReplay !== true) { | 44 | liveSession.endingProcessed = true |
43 | return cleanupLiveAndFederate({ live, video: liveVideo, streamingPlaylistId: payload.streamingPlaylistId }) | 45 | await liveSession.save() |
46 | |||
47 | if (liveSession.saveReplay !== true) { | ||
48 | return cleanupLiveAndFederate({ permanentLive, video, streamingPlaylistId: payload.streamingPlaylistId }) | ||
44 | } | 49 | } |
45 | 50 | ||
46 | if (live.permanentLive) { | 51 | if (permanentLive) { |
47 | await saveReplayToExternalVideo({ liveVideo, liveSession, publishedAt: payload.publishedAt, replayDirectory: payload.replayDirectory }) | 52 | await saveReplayToExternalVideo({ |
53 | liveVideo: video, | ||
54 | liveSession, | ||
55 | publishedAt: payload.publishedAt, | ||
56 | replayDirectory: payload.replayDirectory | ||
57 | }) | ||
48 | 58 | ||
49 | return cleanupLiveAndFederate({ live, video: liveVideo, streamingPlaylistId: payload.streamingPlaylistId }) | 59 | return cleanupLiveAndFederate({ permanentLive, video, streamingPlaylistId: payload.streamingPlaylistId }) |
50 | } | 60 | } |
51 | 61 | ||
52 | return replaceLiveByReplay({ liveVideo, live, liveSession, replayDirectory: payload.replayDirectory }) | 62 | return replaceLiveByReplay({ video, liveSession, live, permanentLive, replayDirectory: payload.replayDirectory }) |
53 | } | 63 | } |
54 | 64 | ||
55 | // --------------------------------------------------------------------------- | 65 | // --------------------------------------------------------------------------- |
@@ -68,7 +78,7 @@ async function saveReplayToExternalVideo (options: { | |||
68 | }) { | 78 | }) { |
69 | const { liveVideo, liveSession, publishedAt, replayDirectory } = options | 79 | const { liveVideo, liveSession, publishedAt, replayDirectory } = options |
70 | 80 | ||
71 | const video = new VideoModel({ | 81 | const replayVideo = new VideoModel({ |
72 | name: `${liveVideo.name} - ${new Date(publishedAt).toLocaleString()}`, | 82 | name: `${liveVideo.name} - ${new Date(publishedAt).toLocaleString()}`, |
73 | isLive: false, | 83 | isLive: false, |
74 | state: VideoState.TO_TRANSCODE, | 84 | state: VideoState.TO_TRANSCODE, |
@@ -88,63 +98,64 @@ async function saveReplayToExternalVideo (options: { | |||
88 | channelId: liveVideo.channelId | 98 | channelId: liveVideo.channelId |
89 | }) as MVideoWithAllFiles | 99 | }) as MVideoWithAllFiles |
90 | 100 | ||
91 | video.Thumbnails = [] | 101 | replayVideo.Thumbnails = [] |
92 | video.VideoFiles = [] | 102 | replayVideo.VideoFiles = [] |
93 | video.VideoStreamingPlaylists = [] | 103 | replayVideo.VideoStreamingPlaylists = [] |
94 | 104 | ||
95 | video.url = getLocalVideoActivityPubUrl(video) | 105 | replayVideo.url = getLocalVideoActivityPubUrl(replayVideo) |
96 | 106 | ||
97 | await video.save() | 107 | await replayVideo.save() |
98 | 108 | ||
99 | liveSession.replayVideoId = video.id | 109 | liveSession.replayVideoId = replayVideo.id |
100 | await liveSession.save() | 110 | await liveSession.save() |
101 | 111 | ||
102 | // If live is blacklisted, also blacklist the replay | 112 | // If live is blacklisted, also blacklist the replay |
103 | const blacklist = await VideoBlacklistModel.loadByVideoId(liveVideo.id) | 113 | const blacklist = await VideoBlacklistModel.loadByVideoId(liveVideo.id) |
104 | if (blacklist) { | 114 | if (blacklist) { |
105 | await VideoBlacklistModel.create({ | 115 | await VideoBlacklistModel.create({ |
106 | videoId: video.id, | 116 | videoId: replayVideo.id, |
107 | unfederated: blacklist.unfederated, | 117 | unfederated: blacklist.unfederated, |
108 | reason: blacklist.reason, | 118 | reason: blacklist.reason, |
109 | type: blacklist.type | 119 | type: blacklist.type |
110 | }) | 120 | }) |
111 | } | 121 | } |
112 | 122 | ||
113 | await assignReplayFilesToVideo({ video, replayDirectory }) | 123 | await assignReplayFilesToVideo({ video: replayVideo, replayDirectory }) |
114 | 124 | ||
115 | await remove(replayDirectory) | 125 | await remove(replayDirectory) |
116 | 126 | ||
117 | for (const type of [ ThumbnailType.MINIATURE, ThumbnailType.PREVIEW ]) { | 127 | for (const type of [ ThumbnailType.MINIATURE, ThumbnailType.PREVIEW ]) { |
118 | const image = await generateVideoMiniature({ video, videoFile: video.getMaxQualityFile(), type }) | 128 | const image = await generateVideoMiniature({ video: replayVideo, videoFile: replayVideo.getMaxQualityFile(), type }) |
119 | await video.addAndSaveThumbnail(image) | 129 | await replayVideo.addAndSaveThumbnail(image) |
120 | } | 130 | } |
121 | 131 | ||
122 | await moveToNextState({ video, isNewVideo: true }) | 132 | await moveToNextState({ video: replayVideo, isNewVideo: true }) |
123 | } | 133 | } |
124 | 134 | ||
125 | async function replaceLiveByReplay (options: { | 135 | async function replaceLiveByReplay (options: { |
126 | liveVideo: MVideo | 136 | video: MVideo |
127 | liveSession: MVideoLiveSession | 137 | liveSession: MVideoLiveSession |
128 | live: MVideoLive | 138 | live: MVideoLive |
139 | permanentLive: boolean | ||
129 | replayDirectory: string | 140 | replayDirectory: string |
130 | }) { | 141 | }) { |
131 | const { liveVideo, liveSession, live, replayDirectory } = options | 142 | const { video, liveSession, live, permanentLive, replayDirectory } = options |
132 | 143 | ||
133 | await cleanupTMPLiveFiles(liveVideo) | 144 | await cleanupTMPLiveFiles(video) |
134 | 145 | ||
135 | await live.destroy() | 146 | await live.destroy() |
136 | 147 | ||
137 | liveVideo.isLive = false | 148 | video.isLive = false |
138 | liveVideo.waitTranscoding = true | 149 | video.waitTranscoding = true |
139 | liveVideo.state = VideoState.TO_TRANSCODE | 150 | video.state = VideoState.TO_TRANSCODE |
140 | 151 | ||
141 | await liveVideo.save() | 152 | await video.save() |
142 | 153 | ||
143 | liveSession.replayVideoId = liveVideo.id | 154 | liveSession.replayVideoId = video.id |
144 | await liveSession.save() | 155 | await liveSession.save() |
145 | 156 | ||
146 | // Remove old HLS playlist video files | 157 | // Remove old HLS playlist video files |
147 | const videoWithFiles = await VideoModel.loadFull(liveVideo.id) | 158 | const videoWithFiles = await VideoModel.loadFull(video.id) |
148 | 159 | ||
149 | const hlsPlaylist = videoWithFiles.getHLSPlaylist() | 160 | const hlsPlaylist = videoWithFiles.getHLSPlaylist() |
150 | await VideoFileModel.removeHLSFilesOfVideoId(hlsPlaylist.id) | 161 | await VideoFileModel.removeHLSFilesOfVideoId(hlsPlaylist.id) |
@@ -157,7 +168,7 @@ async function replaceLiveByReplay (options: { | |||
157 | 168 | ||
158 | await assignReplayFilesToVideo({ video: videoWithFiles, replayDirectory }) | 169 | await assignReplayFilesToVideo({ video: videoWithFiles, replayDirectory }) |
159 | 170 | ||
160 | if (live.permanentLive) { // Remove session replay | 171 | if (permanentLive) { // Remove session replay |
161 | await remove(replayDirectory) | 172 | await remove(replayDirectory) |
162 | } else { // We won't stream again in this live, we can delete the base replay directory | 173 | } else { // We won't stream again in this live, we can delete the base replay directory |
163 | await remove(getLiveReplayBaseDirectory(videoWithFiles)) | 174 | await remove(getLiveReplayBaseDirectory(videoWithFiles)) |
@@ -224,16 +235,16 @@ async function assignReplayFilesToVideo (options: { | |||
224 | } | 235 | } |
225 | 236 | ||
226 | async function cleanupLiveAndFederate (options: { | 237 | async function cleanupLiveAndFederate (options: { |
227 | live: MVideoLive | ||
228 | video: MVideo | 238 | video: MVideo |
239 | permanentLive: boolean | ||
229 | streamingPlaylistId: number | 240 | streamingPlaylistId: number |
230 | }) { | 241 | }) { |
231 | const { live, video, streamingPlaylistId } = options | 242 | const { permanentLive, video, streamingPlaylistId } = options |
232 | 243 | ||
233 | const streamingPlaylist = await VideoStreamingPlaylistModel.loadWithVideo(streamingPlaylistId) | 244 | const streamingPlaylist = await VideoStreamingPlaylistModel.loadWithVideo(streamingPlaylistId) |
234 | 245 | ||
235 | if (streamingPlaylist) { | 246 | if (streamingPlaylist) { |
236 | if (live.permanentLive) { | 247 | if (permanentLive) { |
237 | await cleanupPermanentLive(video, streamingPlaylist) | 248 | await cleanupPermanentLive(video, streamingPlaylist) |
238 | } else { | 249 | } else { |
239 | await cleanupUnsavedNormalLive(video, streamingPlaylist) | 250 | await cleanupUnsavedNormalLive(video, streamingPlaylist) |
diff --git a/server/lib/live/live-manager.ts b/server/lib/live/live-manager.ts index 649ad5195..41f89a2a4 100644 --- a/server/lib/live/live-manager.ts +++ b/server/lib/live/live-manager.ts | |||
@@ -475,7 +475,9 @@ class LiveManager { | |||
475 | private saveStartingSession (videoLive: MVideoLiveVideo) { | 475 | private saveStartingSession (videoLive: MVideoLiveVideo) { |
476 | const liveSession = new VideoLiveSessionModel({ | 476 | const liveSession = new VideoLiveSessionModel({ |
477 | startDate: new Date(), | 477 | startDate: new Date(), |
478 | liveVideoId: videoLive.videoId | 478 | liveVideoId: videoLive.videoId, |
479 | saveReplay: videoLive.saveReplay, | ||
480 | endingProcessed: false | ||
479 | }) | 481 | }) |
480 | 482 | ||
481 | return liveSession.save() | 483 | return liveSession.save() |
diff --git a/server/models/video/video-live-session.ts b/server/models/video/video-live-session.ts index 758906a42..ed386052b 100644 --- a/server/models/video/video-live-session.ts +++ b/server/models/video/video-live-session.ts | |||
@@ -53,6 +53,14 @@ export class VideoLiveSessionModel extends Model<Partial<AttributesOnly<VideoLiv | |||
53 | @Column | 53 | @Column |
54 | error: LiveVideoError | 54 | error: LiveVideoError |
55 | 55 | ||
56 | @AllowNull(false) | ||
57 | @Column | ||
58 | saveReplay: boolean | ||
59 | |||
60 | @AllowNull(false) | ||
61 | @Column | ||
62 | endingProcessed: boolean | ||
63 | |||
56 | @ForeignKey(() => VideoModel) | 64 | @ForeignKey(() => VideoModel) |
57 | @Column | 65 | @Column |
58 | replayVideoId: number | 66 | replayVideoId: number |
@@ -144,6 +152,8 @@ export class VideoLiveSessionModel extends Model<Partial<AttributesOnly<VideoLiv | |||
144 | endDate: this.endDate | 152 | endDate: this.endDate |
145 | ? this.endDate.toISOString() | 153 | ? this.endDate.toISOString() |
146 | : null, | 154 | : null, |
155 | endingProcessed: this.endingProcessed, | ||
156 | saveReplay: this.saveReplay, | ||
147 | replayVideo, | 157 | replayVideo, |
148 | error: this.error | 158 | error: this.error |
149 | } | 159 | } |
diff --git a/server/tests/api/live/live-save-replay.ts b/server/tests/api/live/live-save-replay.ts index 99ad3b2e1..b89aed85a 100644 --- a/server/tests/api/live/live-save-replay.ts +++ b/server/tests/api/live/live-save-replay.ts | |||
@@ -206,6 +206,7 @@ describe('Save replay setting', function () { | |||
206 | expect(session.endDate).to.exist | 206 | expect(session.endDate).to.exist |
207 | expect(new Date(session.endDate)).to.be.above(sessionEndDateMin) | 207 | expect(new Date(session.endDate)).to.be.above(sessionEndDateMin) |
208 | 208 | ||
209 | expect(session.saveReplay).to.be.false | ||
209 | expect(session.error).to.not.exist | 210 | expect(session.error).to.not.exist |
210 | expect(session.replayVideo).to.not.exist | 211 | expect(session.replayVideo).to.not.exist |
211 | }) | 212 | }) |
@@ -272,6 +273,11 @@ describe('Save replay setting', function () { | |||
272 | it('Should correctly have saved the live and federated it after the streaming', async function () { | 273 | it('Should correctly have saved the live and federated it after the streaming', async function () { |
273 | this.timeout(30000) | 274 | this.timeout(30000) |
274 | 275 | ||
276 | const session = await servers[0].live.findLatestSession({ videoId: liveVideoUUID }) | ||
277 | expect(session.endDate).to.not.exist | ||
278 | expect(session.endingProcessed).to.be.false | ||
279 | expect(session.saveReplay).to.be.true | ||
280 | |||
275 | await stopFfmpeg(ffmpegCommand) | 281 | await stopFfmpeg(ffmpegCommand) |
276 | 282 | ||
277 | await waitUntilLiveReplacedByReplayOnAllServers(servers, liveVideoUUID) | 283 | await waitUntilLiveReplacedByReplayOnAllServers(servers, liveVideoUUID) |
@@ -291,6 +297,8 @@ describe('Save replay setting', function () { | |||
291 | expect(session.endDate).to.exist | 297 | expect(session.endDate).to.exist |
292 | 298 | ||
293 | expect(session.error).to.not.exist | 299 | expect(session.error).to.not.exist |
300 | expect(session.saveReplay).to.be.true | ||
301 | expect(session.endingProcessed).to.be.true | ||
294 | 302 | ||
295 | expect(session.replayVideo).to.exist | 303 | expect(session.replayVideo).to.exist |
296 | expect(session.replayVideo.id).to.exist | 304 | expect(session.replayVideo.id).to.exist |