diff options
Diffstat (limited to 'server/lib')
-rw-r--r-- | server/lib/activitypub/videos.ts | 2 | ||||
-rw-r--r-- | server/lib/job-queue/handlers/video-live-ending.ts | 27 | ||||
-rw-r--r-- | server/lib/live-manager.ts | 40 |
3 files changed, 51 insertions, 18 deletions
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index 4053f487c..04f0bfc23 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts | |||
@@ -429,6 +429,7 @@ async function updateVideoFromAP (options: { | |||
429 | if (video.isLive) { | 429 | if (video.isLive) { |
430 | const [ videoLive ] = await VideoLiveModel.upsert({ | 430 | const [ videoLive ] = await VideoLiveModel.upsert({ |
431 | saveReplay: videoObject.liveSaveReplay, | 431 | saveReplay: videoObject.liveSaveReplay, |
432 | permanentLive: videoObject.permanentLive, | ||
432 | videoId: video.id | 433 | videoId: video.id |
433 | }, { transaction: t, returning: true }) | 434 | }, { transaction: t, returning: true }) |
434 | 435 | ||
@@ -631,6 +632,7 @@ async function createVideo (videoObject: VideoObject, channel: MChannelAccountLi | |||
631 | const videoLive = new VideoLiveModel({ | 632 | const videoLive = new VideoLiveModel({ |
632 | streamKey: null, | 633 | streamKey: null, |
633 | saveReplay: videoObject.liveSaveReplay, | 634 | saveReplay: videoObject.liveSaveReplay, |
635 | permanentLive: videoObject.permanentLive, | ||
634 | videoId: videoCreated.id | 636 | videoId: videoCreated.id |
635 | }) | 637 | }) |
636 | 638 | ||
diff --git a/server/lib/job-queue/handlers/video-live-ending.ts b/server/lib/job-queue/handlers/video-live-ending.ts index d3c84ce75..e3c11caa2 100644 --- a/server/lib/job-queue/handlers/video-live-ending.ts +++ b/server/lib/job-queue/handlers/video-live-ending.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as Bull from 'bull' | 1 | import * as Bull from 'bull' |
2 | import { copy, readdir, remove } from 'fs-extra' | 2 | import { copy, pathExists, readdir, remove } from 'fs-extra' |
3 | import { join } from 'path' | 3 | import { join } from 'path' |
4 | import { getDurationFromVideoFile, getVideoFileResolution } from '@server/helpers/ffprobe-utils' | 4 | import { getDurationFromVideoFile, getVideoFileResolution } from '@server/helpers/ffprobe-utils' |
5 | import { VIDEO_LIVE } from '@server/initializers/constants' | 5 | import { VIDEO_LIVE } from '@server/initializers/constants' |
@@ -14,6 +14,7 @@ import { VideoStreamingPlaylistModel } from '@server/models/video/video-streamin | |||
14 | import { MStreamingPlaylist, MVideo, MVideoLive } from '@server/types/models' | 14 | import { MStreamingPlaylist, MVideo, MVideoLive } from '@server/types/models' |
15 | import { ThumbnailType, VideoLiveEndingPayload, VideoState } from '@shared/models' | 15 | import { ThumbnailType, VideoLiveEndingPayload, VideoState } from '@shared/models' |
16 | import { logger } from '../../../helpers/logger' | 16 | import { logger } from '../../../helpers/logger' |
17 | import { LiveManager } from '@server/lib/live-manager' | ||
17 | 18 | ||
18 | async function processVideoLiveEnding (job: Bull.Job) { | 19 | async function processVideoLiveEnding (job: Bull.Job) { |
19 | const payload = job.data as VideoLiveEndingPayload | 20 | const payload = job.data as VideoLiveEndingPayload |
@@ -36,6 +37,8 @@ async function processVideoLiveEnding (job: Bull.Job) { | |||
36 | return | 37 | return |
37 | } | 38 | } |
38 | 39 | ||
40 | LiveManager.Instance.cleanupShaSegments(video.uuid) | ||
41 | |||
39 | if (live.saveReplay !== true) { | 42 | if (live.saveReplay !== true) { |
40 | return cleanupLive(video, streamingPlaylist) | 43 | return cleanupLive(video, streamingPlaylist) |
41 | } | 44 | } |
@@ -43,10 +46,19 @@ async function processVideoLiveEnding (job: Bull.Job) { | |||
43 | return saveLive(video, live) | 46 | return saveLive(video, live) |
44 | } | 47 | } |
45 | 48 | ||
49 | async function cleanupLive (video: MVideo, streamingPlaylist: MStreamingPlaylist) { | ||
50 | const hlsDirectory = getHLSDirectory(video) | ||
51 | |||
52 | await remove(hlsDirectory) | ||
53 | |||
54 | await streamingPlaylist.destroy() | ||
55 | } | ||
56 | |||
46 | // --------------------------------------------------------------------------- | 57 | // --------------------------------------------------------------------------- |
47 | 58 | ||
48 | export { | 59 | export { |
49 | processVideoLiveEnding | 60 | processVideoLiveEnding, |
61 | cleanupLive | ||
50 | } | 62 | } |
51 | 63 | ||
52 | // --------------------------------------------------------------------------- | 64 | // --------------------------------------------------------------------------- |
@@ -131,16 +143,9 @@ async function saveLive (video: MVideo, live: MVideoLive) { | |||
131 | await publishAndFederateIfNeeded(videoWithFiles, true) | 143 | await publishAndFederateIfNeeded(videoWithFiles, true) |
132 | } | 144 | } |
133 | 145 | ||
134 | async function cleanupLive (video: MVideo, streamingPlaylist: MStreamingPlaylist) { | ||
135 | const hlsDirectory = getHLSDirectory(video) | ||
136 | |||
137 | await remove(hlsDirectory) | ||
138 | |||
139 | streamingPlaylist.destroy() | ||
140 | .catch(err => logger.error('Cannot remove live streaming playlist.', { err })) | ||
141 | } | ||
142 | |||
143 | async function cleanupLiveFiles (hlsDirectory: string) { | 146 | async function cleanupLiveFiles (hlsDirectory: string) { |
147 | if (!await pathExists(hlsDirectory)) return | ||
148 | |||
144 | const files = await readdir(hlsDirectory) | 149 | const files = await readdir(hlsDirectory) |
145 | 150 | ||
146 | for (const filename of files) { | 151 | for (const filename of files) { |
diff --git a/server/lib/live-manager.ts b/server/lib/live-manager.ts index 4f45ce530..dcf016169 100644 --- a/server/lib/live-manager.ts +++ b/server/lib/live-manager.ts | |||
@@ -19,6 +19,7 @@ import { VideoState, VideoStreamingPlaylistType } from '@shared/models' | |||
19 | import { federateVideoIfNeeded } from './activitypub/videos' | 19 | import { federateVideoIfNeeded } from './activitypub/videos' |
20 | import { buildSha256Segment } from './hls' | 20 | import { buildSha256Segment } from './hls' |
21 | import { JobQueue } from './job-queue' | 21 | import { JobQueue } from './job-queue' |
22 | import { cleanupLive } from './job-queue/handlers/video-live-ending' | ||
22 | import { PeerTubeSocket } from './peertube-socket' | 23 | import { PeerTubeSocket } from './peertube-socket' |
23 | import { isAbleToUploadVideo } from './user' | 24 | import { isAbleToUploadVideo } from './user' |
24 | import { getHLSDirectory } from './video-paths' | 25 | import { getHLSDirectory } from './video-paths' |
@@ -153,6 +154,10 @@ class LiveManager { | |||
153 | watchers.push(new Date().getTime()) | 154 | watchers.push(new Date().getTime()) |
154 | } | 155 | } |
155 | 156 | ||
157 | cleanupShaSegments (videoUUID: string) { | ||
158 | this.segmentsSha256.delete(videoUUID) | ||
159 | } | ||
160 | |||
156 | private getContext () { | 161 | private getContext () { |
157 | return context | 162 | return context |
158 | } | 163 | } |
@@ -184,6 +189,14 @@ class LiveManager { | |||
184 | return this.abortSession(sessionId) | 189 | return this.abortSession(sessionId) |
185 | } | 190 | } |
186 | 191 | ||
192 | // Cleanup old potential live files (could happen with a permanent live) | ||
193 | this.cleanupShaSegments(video.uuid) | ||
194 | |||
195 | const oldStreamingPlaylist = await VideoStreamingPlaylistModel.loadHLSPlaylistByVideo(video.id) | ||
196 | if (oldStreamingPlaylist) { | ||
197 | await cleanupLive(video, oldStreamingPlaylist) | ||
198 | } | ||
199 | |||
187 | this.videoSessions.set(video.id, sessionId) | 200 | this.videoSessions.set(video.id, sessionId) |
188 | 201 | ||
189 | const playlistUrl = WEBSERVER.URL + VideoStreamingPlaylistModel.getHlsMasterPlaylistStaticPath(video.uuid) | 202 | const playlistUrl = WEBSERVER.URL + VideoStreamingPlaylistModel.getHlsMasterPlaylistStaticPath(video.uuid) |
@@ -372,7 +385,13 @@ class LiveManager { | |||
372 | logger.info('RTMP transmuxing for video %s ended. Scheduling cleanup', rtmpUrl) | 385 | logger.info('RTMP transmuxing for video %s ended. Scheduling cleanup', rtmpUrl) |
373 | 386 | ||
374 | this.transSessions.delete(sessionId) | 387 | this.transSessions.delete(sessionId) |
388 | |||
375 | this.watchersPerVideo.delete(videoLive.videoId) | 389 | this.watchersPerVideo.delete(videoLive.videoId) |
390 | this.videoSessions.delete(videoLive.videoId) | ||
391 | |||
392 | const newLivesPerUser = this.livesPerUser.get(user.id) | ||
393 | .filter(o => o.liveId !== videoLive.id) | ||
394 | this.livesPerUser.set(user.id, newLivesPerUser) | ||
376 | 395 | ||
377 | setTimeout(() => { | 396 | setTimeout(() => { |
378 | // Wait latest segments generation, and close watchers | 397 | // Wait latest segments generation, and close watchers |
@@ -412,14 +431,21 @@ class LiveManager { | |||
412 | const fullVideo = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) | 431 | const fullVideo = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) |
413 | if (!fullVideo) return | 432 | if (!fullVideo) return |
414 | 433 | ||
415 | JobQueue.Instance.createJob({ | 434 | const live = await VideoLiveModel.loadByVideoId(videoId) |
416 | type: 'video-live-ending', | 435 | |
417 | payload: { | 436 | if (!live.permanentLive) { |
418 | videoId: fullVideo.id | 437 | JobQueue.Instance.createJob({ |
419 | } | 438 | type: 'video-live-ending', |
420 | }, { delay: cleanupNow ? 0 : VIDEO_LIVE.CLEANUP_DELAY }) | 439 | payload: { |
440 | videoId: fullVideo.id | ||
441 | } | ||
442 | }, { delay: cleanupNow ? 0 : VIDEO_LIVE.CLEANUP_DELAY }) | ||
443 | |||
444 | fullVideo.state = VideoState.LIVE_ENDED | ||
445 | } else { | ||
446 | fullVideo.state = VideoState.WAITING_FOR_LIVE | ||
447 | } | ||
421 | 448 | ||
422 | fullVideo.state = VideoState.LIVE_ENDED | ||
423 | await fullVideo.save() | 449 | await fullVideo.save() |
424 | 450 | ||
425 | PeerTubeSocket.Instance.sendVideoLiveNewState(fullVideo) | 451 | PeerTubeSocket.Instance.sendVideoLiveNewState(fullVideo) |