aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib
diff options
context:
space:
mode:
Diffstat (limited to 'server/lib')
-rw-r--r--server/lib/activitypub/videos.ts6
-rw-r--r--server/lib/job-queue/handlers/video-live-ending.ts33
-rw-r--r--server/lib/live-manager.ts42
3 files changed, 50 insertions, 31 deletions
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts
index ea1e6a38f..ab4aac0a1 100644
--- a/server/lib/activitypub/videos.ts
+++ b/server/lib/activitypub/videos.ts
@@ -85,7 +85,7 @@ async function federateVideoIfNeeded (videoArg: MVideoAPWithoutCaption, isNewVid
85 // Check this is not a blacklisted video, or unfederated blacklisted video 85 // Check this is not a blacklisted video, or unfederated blacklisted video
86 (video.isBlacklisted() === false || (isNewVideo === false && video.VideoBlacklist.unfederated === false)) && 86 (video.isBlacklisted() === false || (isNewVideo === false && video.VideoBlacklist.unfederated === false)) &&
87 // Check the video is public/unlisted and published 87 // Check the video is public/unlisted and published
88 video.hasPrivacyForFederation() && (video.state === VideoState.PUBLISHED || video.state === VideoState.WAITING_FOR_LIVE) 88 video.hasPrivacyForFederation() && video.hasStateForFederation()
89 ) { 89 ) {
90 // Fetch more attributes that we will need to serialize in AP object 90 // Fetch more attributes that we will need to serialize in AP object
91 if (isArray(video.VideoCaptions) === false) { 91 if (isArray(video.VideoCaptions) === false) {
@@ -302,7 +302,7 @@ async function updateVideoFromAP (options: {
302}) { 302}) {
303 const { video, videoObject, account, channel, overrideTo } = options 303 const { video, videoObject, account, channel, overrideTo } = options
304 304
305 logger.debug('Updating remote video "%s".', options.videoObject.uuid, { account, channel }) 305 logger.debug('Updating remote video "%s".', options.videoObject.uuid, { videoObject: options.videoObject, account, channel })
306 306
307 let videoFieldsSave: any 307 let videoFieldsSave: any
308 const wasPrivateVideo = video.privacy === VideoPrivacy.PRIVATE 308 const wasPrivateVideo = video.privacy === VideoPrivacy.PRIVATE
@@ -562,6 +562,8 @@ function isAPHashTagObject (url: any): url is ActivityHashTagObject {
562 return url && url.type === 'Hashtag' 562 return url && url.type === 'Hashtag'
563} 563}
564 564
565
566
565async function createVideo (videoObject: VideoObject, channel: MChannelAccountLight, waitThumbnail = false) { 567async function createVideo (videoObject: VideoObject, channel: MChannelAccountLight, waitThumbnail = false) {
566 logger.debug('Adding remote video %s.', videoObject.id) 568 logger.debug('Adding remote video %s.', videoObject.id)
567 569
diff --git a/server/lib/job-queue/handlers/video-live-ending.ts b/server/lib/job-queue/handlers/video-live-ending.ts
index 1e964726e..2b900998a 100644
--- a/server/lib/job-queue/handlers/video-live-ending.ts
+++ b/server/lib/job-queue/handlers/video-live-ending.ts
@@ -6,22 +6,31 @@ import { publishAndFederateIfNeeded } from '@server/lib/video'
6import { getHLSDirectory } from '@server/lib/video-paths' 6import { getHLSDirectory } from '@server/lib/video-paths'
7import { generateHlsPlaylist } from '@server/lib/video-transcoding' 7import { generateHlsPlaylist } from '@server/lib/video-transcoding'
8import { VideoModel } from '@server/models/video/video' 8import { VideoModel } from '@server/models/video/video'
9import { VideoFileModel } from '@server/models/video/video-file'
9import { VideoLiveModel } from '@server/models/video/video-live' 10import { VideoLiveModel } from '@server/models/video/video-live'
10import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist' 11import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist'
11import { MStreamingPlaylist, MVideo, MVideoLive, MVideoWithFile } from '@server/types/models' 12import { MStreamingPlaylist, MVideo, MVideoLive } from '@server/types/models'
12import { VideoLiveEndingPayload, VideoState } from '@shared/models' 13import { VideoLiveEndingPayload, VideoState } from '@shared/models'
13import { logger } from '../../../helpers/logger' 14import { logger } from '../../../helpers/logger'
14import { VideoFileModel } from '@server/models/video/video-file'
15 15
16async function processVideoLiveEnding (job: Bull.Job) { 16async function processVideoLiveEnding (job: Bull.Job) {
17 const payload = job.data as VideoLiveEndingPayload 17 const payload = job.data as VideoLiveEndingPayload
18 18
19 function logError () {
20 logger.warn('Video live %d does not exist anymore. Cannot process live ending.', payload.videoId)
21 }
22
19 const video = await VideoModel.load(payload.videoId) 23 const video = await VideoModel.load(payload.videoId)
20 const live = await VideoLiveModel.loadByVideoId(payload.videoId) 24 const live = await VideoLiveModel.loadByVideoId(payload.videoId)
21 25
26 if (!video || !live) {
27 logError()
28 return
29 }
30
22 const streamingPlaylist = await VideoStreamingPlaylistModel.loadHLSPlaylistByVideo(video.id) 31 const streamingPlaylist = await VideoStreamingPlaylistModel.loadHLSPlaylistByVideo(video.id)
23 if (!video || !streamingPlaylist || !live) { 32 if (!streamingPlaylist) {
24 logger.warn('Video live %d does not exist anymore. Cannot process live ending.', payload.videoId) 33 logError()
25 return 34 return
26 } 35 }
27 36
@@ -52,21 +61,21 @@ async function saveLive (video: MVideo, live: MVideoLive) {
52 const playlistPath = join(hlsDirectory, playlistFile) 61 const playlistPath = join(hlsDirectory, playlistFile)
53 const { videoFileResolution } = await getVideoFileResolution(playlistPath) 62 const { videoFileResolution } = await getVideoFileResolution(playlistPath)
54 63
55 const mp4TmpName = buildMP4TmpName(videoFileResolution) 64 const mp4TmpPath = buildMP4TmpPath(hlsDirectory, videoFileResolution)
56 65
57 // Playlist name is for example 3.m3u8 66 // Playlist name is for example 3.m3u8
58 // Segments names are 3-0.ts 3-1.ts etc 67 // Segments names are 3-0.ts 3-1.ts etc
59 const shouldStartWith = playlistFile.replace(/\.m3u8$/, '') + '-' 68 const shouldStartWith = playlistFile.replace(/\.m3u8$/, '') + '-'
60 69
61 const segmentFiles = files.filter(f => f.startsWith(shouldStartWith) && f.endsWith('.ts')) 70 const segmentFiles = files.filter(f => f.startsWith(shouldStartWith) && f.endsWith('.ts'))
62 await hlsPlaylistToFragmentedMP4(hlsDirectory, segmentFiles, mp4TmpName) 71 await hlsPlaylistToFragmentedMP4(hlsDirectory, segmentFiles, mp4TmpPath)
63 72
64 for (const file of segmentFiles) { 73 for (const file of segmentFiles) {
65 await remove(join(hlsDirectory, file)) 74 await remove(join(hlsDirectory, file))
66 } 75 }
67 76
68 if (!duration) { 77 if (!duration) {
69 duration = await getDurationFromVideoFile(mp4TmpName) 78 duration = await getDurationFromVideoFile(mp4TmpPath)
70 } 79 }
71 80
72 resolutions.push(videoFileResolution) 81 resolutions.push(videoFileResolution)
@@ -90,7 +99,7 @@ async function saveLive (video: MVideo, live: MVideoLive) {
90 hlsPlaylist.VideoFiles = [] 99 hlsPlaylist.VideoFiles = []
91 100
92 for (const resolution of resolutions) { 101 for (const resolution of resolutions) {
93 const videoInputPath = buildMP4TmpName(resolution) 102 const videoInputPath = buildMP4TmpPath(hlsDirectory, resolution)
94 const { isPortraitMode } = await getVideoFileResolution(videoInputPath) 103 const { isPortraitMode } = await getVideoFileResolution(videoInputPath)
95 104
96 await generateHlsPlaylist({ 105 await generateHlsPlaylist({
@@ -101,7 +110,7 @@ async function saveLive (video: MVideo, live: MVideoLive) {
101 isPortraitMode 110 isPortraitMode
102 }) 111 })
103 112
104 await remove(join(hlsDirectory, videoInputPath)) 113 await remove(videoInputPath)
105 } 114 }
106 115
107 await publishAndFederateIfNeeded(video, true) 116 await publishAndFederateIfNeeded(video, true)
@@ -110,7 +119,7 @@ async function saveLive (video: MVideo, live: MVideoLive) {
110async function cleanupLive (video: MVideo, streamingPlaylist: MStreamingPlaylist) { 119async function cleanupLive (video: MVideo, streamingPlaylist: MStreamingPlaylist) {
111 const hlsDirectory = getHLSDirectory(video, false) 120 const hlsDirectory = getHLSDirectory(video, false)
112 121
113 await cleanupLiveFiles(hlsDirectory) 122 await remove(hlsDirectory)
114 123
115 streamingPlaylist.destroy() 124 streamingPlaylist.destroy()
116 .catch(err => logger.error('Cannot remove live streaming playlist.', { err })) 125 .catch(err => logger.error('Cannot remove live streaming playlist.', { err }))
@@ -135,6 +144,6 @@ async function cleanupLiveFiles (hlsDirectory: string) {
135 } 144 }
136} 145}
137 146
138function buildMP4TmpName (resolution: number) { 147function buildMP4TmpPath (basePath: string, resolution: number) {
139 return resolution + '-tmp.mp4' 148 return join(basePath, resolution + '-tmp.mp4')
140} 149}
diff --git a/server/lib/live-manager.ts b/server/lib/live-manager.ts
index 2d8f906e9..6eb05c9d6 100644
--- a/server/lib/live-manager.ts
+++ b/server/lib/live-manager.ts
@@ -4,7 +4,7 @@ import * as chokidar from 'chokidar'
4import { FfmpegCommand } from 'fluent-ffmpeg' 4import { FfmpegCommand } from 'fluent-ffmpeg'
5import { ensureDir, stat } from 'fs-extra' 5import { ensureDir, stat } from 'fs-extra'
6import { basename } from 'path' 6import { basename } from 'path'
7import { computeResolutionsToTranscode, runLiveMuxing, runLiveTranscoding } from '@server/helpers/ffmpeg-utils' 7import { computeResolutionsToTranscode, getVideoFileFPS, getVideoFileResolution, getVideoStreamCodec, getVideoStreamSize, runLiveMuxing, runLiveTranscoding } from '@server/helpers/ffmpeg-utils'
8import { logger } from '@server/helpers/logger' 8import { logger } from '@server/helpers/logger'
9import { CONFIG, registerConfigChangedHandler } from '@server/initializers/config' 9import { CONFIG, registerConfigChangedHandler } from '@server/initializers/config'
10import { MEMOIZE_TTL, P2P_MEDIA_LOADER_PEER_VERSION, VIDEO_LIVE, WEBSERVER } from '@server/initializers/constants' 10import { MEMOIZE_TTL, P2P_MEDIA_LOADER_PEER_VERSION, VIDEO_LIVE, WEBSERVER } from '@server/initializers/constants'
@@ -137,6 +137,13 @@ class LiveManager {
137 this.abortSession(sessionId) 137 this.abortSession(sessionId)
138 } 138 }
139 139
140 getLiveQuotaUsedByUser (userId: number) {
141 const currentLives = this.livesPerUser.get(userId)
142 if (!currentLives) return 0
143
144 return currentLives.reduce((sum, obj) => sum + obj.size, 0)
145 }
146
140 private getContext () { 147 private getContext () {
141 return context 148 return context
142 } 149 }
@@ -173,8 +180,15 @@ class LiveManager {
173 const playlistUrl = WEBSERVER.URL + VideoStreamingPlaylistModel.getHlsMasterPlaylistStaticPath(video.uuid) 180 const playlistUrl = WEBSERVER.URL + VideoStreamingPlaylistModel.getHlsMasterPlaylistStaticPath(video.uuid)
174 181
175 const session = this.getContext().sessions.get(sessionId) 182 const session = this.getContext().sessions.get(sessionId)
183 const rtmpUrl = 'rtmp://127.0.0.1:' + config.rtmp.port + streamPath
184
185 const [ resolutionResult, fps ] = await Promise.all([
186 getVideoFileResolution(rtmpUrl),
187 getVideoFileFPS(rtmpUrl)
188 ])
189
176 const resolutionsEnabled = CONFIG.LIVE.TRANSCODING.ENABLED 190 const resolutionsEnabled = CONFIG.LIVE.TRANSCODING.ENABLED
177 ? computeResolutionsToTranscode(session.videoHeight, 'live') 191 ? computeResolutionsToTranscode(resolutionResult.videoFileResolution, 'live')
178 : [] 192 : []
179 193
180 logger.info('Will mux/transcode live video of original resolution %d.', session.videoHeight, { resolutionsEnabled }) 194 logger.info('Will mux/transcode live video of original resolution %d.', session.videoHeight, { resolutionsEnabled })
@@ -193,8 +207,9 @@ class LiveManager {
193 sessionId, 207 sessionId,
194 videoLive, 208 videoLive,
195 playlist: videoStreamingPlaylist, 209 playlist: videoStreamingPlaylist,
196 streamPath,
197 originalResolution: session.videoHeight, 210 originalResolution: session.videoHeight,
211 rtmpUrl,
212 fps,
198 resolutionsEnabled 213 resolutionsEnabled
199 }) 214 })
200 } 215 }
@@ -203,11 +218,12 @@ class LiveManager {
203 sessionId: string 218 sessionId: string
204 videoLive: MVideoLiveVideo 219 videoLive: MVideoLiveVideo
205 playlist: MStreamingPlaylist 220 playlist: MStreamingPlaylist
206 streamPath: string 221 rtmpUrl: string
222 fps: number
207 resolutionsEnabled: number[] 223 resolutionsEnabled: number[]
208 originalResolution: number 224 originalResolution: number
209 }) { 225 }) {
210 const { sessionId, videoLive, playlist, streamPath, resolutionsEnabled, originalResolution } = options 226 const { sessionId, videoLive, playlist, resolutionsEnabled, originalResolution, fps, rtmpUrl } = options
211 const startStreamDateTime = new Date().getTime() 227 const startStreamDateTime = new Date().getTime()
212 const allResolutions = resolutionsEnabled.concat([ originalResolution ]) 228 const allResolutions = resolutionsEnabled.concat([ originalResolution ])
213 229
@@ -238,17 +254,16 @@ class LiveManager {
238 const outPath = getHLSDirectory(videoLive.Video) 254 const outPath = getHLSDirectory(videoLive.Video)
239 await ensureDir(outPath) 255 await ensureDir(outPath)
240 256
257 const videoUUID = videoLive.Video.uuid
241 const deleteSegments = videoLive.saveReplay === false 258 const deleteSegments = videoLive.saveReplay === false
242 259
243 const rtmpUrl = 'rtmp://127.0.0.1:' + config.rtmp.port + streamPath
244 const ffmpegExec = CONFIG.LIVE.TRANSCODING.ENABLED 260 const ffmpegExec = CONFIG.LIVE.TRANSCODING.ENABLED
245 ? runLiveTranscoding(rtmpUrl, outPath, allResolutions, deleteSegments) 261 ? runLiveTranscoding(rtmpUrl, outPath, allResolutions, fps, deleteSegments)
246 : runLiveMuxing(rtmpUrl, outPath, deleteSegments) 262 : runLiveMuxing(rtmpUrl, outPath, deleteSegments)
247 263
248 logger.info('Running live muxing/transcoding.') 264 logger.info('Running live muxing/transcoding for %s.', videoUUID)
249 this.transSessions.set(sessionId, ffmpegExec) 265 this.transSessions.set(sessionId, ffmpegExec)
250 266
251 const videoUUID = videoLive.Video.uuid
252 const tsWatcher = chokidar.watch(outPath + '/*.ts') 267 const tsWatcher = chokidar.watch(outPath + '/*.ts')
253 268
254 const updateSegment = segmentPath => this.segmentsSha256Queue.push({ operation: 'update', segmentPath, videoUUID }) 269 const updateSegment = segmentPath => this.segmentsSha256Queue.push({ operation: 'update', segmentPath, videoUUID })
@@ -307,7 +322,7 @@ class LiveManager {
307 }) 322 })
308 323
309 const onFFmpegEnded = () => { 324 const onFFmpegEnded = () => {
310 logger.info('RTMP transmuxing for video %s ended. Scheduling cleanup', streamPath) 325 logger.info('RTMP transmuxing for video %s ended. Scheduling cleanup', rtmpUrl)
311 326
312 this.transSessions.delete(sessionId) 327 this.transSessions.delete(sessionId)
313 328
@@ -332,13 +347,6 @@ class LiveManager {
332 ffmpegExec.on('end', () => onFFmpegEnded()) 347 ffmpegExec.on('end', () => onFFmpegEnded())
333 } 348 }
334 349
335 getLiveQuotaUsedByUser (userId: number) {
336 const currentLives = this.livesPerUser.get(userId)
337 if (!currentLives) return 0
338
339 return currentLives.reduce((sum, obj) => sum + obj.size, 0)
340 }
341
342 private async onEndTransmuxing (videoId: number, cleanupNow = false) { 350 private async onEndTransmuxing (videoId: number, cleanupNow = false) {
343 try { 351 try {
344 const fullVideo = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) 352 const fullVideo = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId)