diff options
Diffstat (limited to 'server/lib/live')
-rw-r--r-- | server/lib/live/live-manager.ts | 34 | ||||
-rw-r--r-- | server/lib/live/shared/muxing-session.ts | 71 |
2 files changed, 53 insertions, 52 deletions
diff --git a/server/lib/live/live-manager.ts b/server/lib/live/live-manager.ts index fa4e1df07..1d5b8bf14 100644 --- a/server/lib/live/live-manager.ts +++ b/server/lib/live/live-manager.ts | |||
@@ -13,18 +13,18 @@ import { | |||
13 | } from '@server/helpers/ffmpeg' | 13 | } from '@server/helpers/ffmpeg' |
14 | import { logger, loggerTagsFactory } from '@server/helpers/logger' | 14 | import { logger, loggerTagsFactory } from '@server/helpers/logger' |
15 | import { CONFIG, registerConfigChangedHandler } from '@server/initializers/config' | 15 | import { CONFIG, registerConfigChangedHandler } from '@server/initializers/config' |
16 | import { P2P_MEDIA_LOADER_PEER_VERSION, VIDEO_LIVE } from '@server/initializers/constants' | 16 | import { VIDEO_LIVE } from '@server/initializers/constants' |
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 { VideoLiveSessionModel } from '@server/models/video/video-live-session' |
21 | import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist' | 21 | import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist' |
22 | import { MStreamingPlaylistVideo, MVideo, MVideoLiveSession, MVideoLiveVideo } from '@server/types/models' | 22 | import { MVideo, MVideoLiveSession, MVideoLiveVideo } from '@server/types/models' |
23 | import { pick, wait } from '@shared/core-utils' | 23 | import { pick, wait } from '@shared/core-utils' |
24 | import { LiveVideoError, VideoState, VideoStorage, VideoStreamingPlaylistType } from '@shared/models' | 24 | import { LiveVideoError, VideoState } from '@shared/models' |
25 | import { federateVideoIfNeeded } from '../activitypub/videos' | 25 | import { federateVideoIfNeeded } from '../activitypub/videos' |
26 | import { JobQueue } from '../job-queue' | 26 | import { JobQueue } from '../job-queue' |
27 | import { generateHLSMasterPlaylistFilename, generateHlsSha256SegmentsFilename, getLiveReplayBaseDirectory } from '../paths' | 27 | import { getLiveReplayBaseDirectory } from '../paths' |
28 | import { PeerTubeSocket } from '../peertube-socket' | 28 | import { PeerTubeSocket } from '../peertube-socket' |
29 | import { Hooks } from '../plugins/hooks' | 29 | import { Hooks } from '../plugins/hooks' |
30 | import { LiveQuotaStore } from './live-quota-store' | 30 | import { LiveQuotaStore } from './live-quota-store' |
@@ -255,13 +255,10 @@ class LiveManager { | |||
255 | { allResolutions, ...lTags(sessionId, video.uuid) } | 255 | { allResolutions, ...lTags(sessionId, video.uuid) } |
256 | ) | 256 | ) |
257 | 257 | ||
258 | const streamingPlaylist = await this.createLivePlaylist(video, allResolutions) | ||
259 | |||
260 | return this.runMuxingSession({ | 258 | return this.runMuxingSession({ |
261 | sessionId, | 259 | sessionId, |
262 | videoLive, | 260 | videoLive, |
263 | 261 | ||
264 | streamingPlaylist, | ||
265 | inputUrl, | 262 | inputUrl, |
266 | fps, | 263 | fps, |
267 | bitrate, | 264 | bitrate, |
@@ -275,7 +272,6 @@ class LiveManager { | |||
275 | sessionId: string | 272 | sessionId: string |
276 | videoLive: MVideoLiveVideo | 273 | videoLive: MVideoLiveVideo |
277 | 274 | ||
278 | streamingPlaylist: MStreamingPlaylistVideo | ||
279 | inputUrl: string | 275 | inputUrl: string |
280 | fps: number | 276 | fps: number |
281 | bitrate: number | 277 | bitrate: number |
@@ -298,7 +294,7 @@ class LiveManager { | |||
298 | videoLive, | 294 | videoLive, |
299 | user, | 295 | user, |
300 | 296 | ||
301 | ...pick(options, [ 'streamingPlaylist', 'inputUrl', 'bitrate', 'ratio', 'fps', 'allResolutions', 'hasAudio' ]) | 297 | ...pick(options, [ 'inputUrl', 'bitrate', 'ratio', 'fps', 'allResolutions', 'hasAudio' ]) |
302 | }) | 298 | }) |
303 | 299 | ||
304 | muxingSession.on('live-ready', () => this.publishAndFederateLive(videoLive, localLTags)) | 300 | muxingSession.on('live-ready', () => this.publishAndFederateLive(videoLive, localLTags)) |
@@ -474,26 +470,6 @@ class LiveManager { | |||
474 | return resolutionsEnabled | 470 | return resolutionsEnabled |
475 | } | 471 | } |
476 | 472 | ||
477 | private async createLivePlaylist (video: MVideo, allResolutions: number[]): Promise<MStreamingPlaylistVideo> { | ||
478 | const playlist = await VideoStreamingPlaylistModel.loadOrGenerate(video) | ||
479 | |||
480 | playlist.playlistFilename = generateHLSMasterPlaylistFilename(true) | ||
481 | playlist.segmentsSha256Filename = generateHlsSha256SegmentsFilename(true) | ||
482 | |||
483 | playlist.p2pMediaLoaderPeerVersion = P2P_MEDIA_LOADER_PEER_VERSION | ||
484 | playlist.type = VideoStreamingPlaylistType.HLS | ||
485 | |||
486 | playlist.storage = CONFIG.OBJECT_STORAGE.ENABLED | ||
487 | ? VideoStorage.OBJECT_STORAGE | ||
488 | : VideoStorage.FILE_SYSTEM | ||
489 | |||
490 | if (playlist.storage === VideoStorage.FILE_SYSTEM) { | ||
491 | playlist.assignP2PMediaLoaderInfoHashes(video, allResolutions) | ||
492 | } | ||
493 | |||
494 | return playlist.save() | ||
495 | } | ||
496 | |||
497 | private saveStartingSession (videoLive: MVideoLiveVideo) { | 473 | private saveStartingSession (videoLive: MVideoLiveVideo) { |
498 | const liveSession = new VideoLiveSessionModel({ | 474 | const liveSession = new VideoLiveSessionModel({ |
499 | startDate: new Date(), | 475 | startDate: new Date(), |
diff --git a/server/lib/live/shared/muxing-session.ts b/server/lib/live/shared/muxing-session.ts index 25ecf1c64..2727fc4a7 100644 --- a/server/lib/live/shared/muxing-session.ts +++ b/server/lib/live/shared/muxing-session.ts | |||
@@ -1,4 +1,3 @@ | |||
1 | |||
2 | import { mapSeries } from 'bluebird' | 1 | import { mapSeries } from 'bluebird' |
3 | import { FSWatcher, watch } from 'chokidar' | 2 | import { FSWatcher, watch } from 'chokidar' |
4 | import { FfmpegCommand } from 'fluent-ffmpeg' | 3 | import { FfmpegCommand } from 'fluent-ffmpeg' |
@@ -9,12 +8,18 @@ import { EventEmitter } from 'stream' | |||
9 | import { getLiveMuxingCommand, getLiveTranscodingCommand } from '@server/helpers/ffmpeg' | 8 | import { getLiveMuxingCommand, getLiveTranscodingCommand } from '@server/helpers/ffmpeg' |
10 | import { logger, loggerTagsFactory, LoggerTagsFn } from '@server/helpers/logger' | 9 | import { logger, loggerTagsFactory, LoggerTagsFn } from '@server/helpers/logger' |
11 | import { CONFIG } from '@server/initializers/config' | 10 | import { CONFIG } from '@server/initializers/config' |
12 | import { MEMOIZE_TTL, VIDEO_LIVE } from '@server/initializers/constants' | 11 | import { MEMOIZE_TTL, P2P_MEDIA_LOADER_PEER_VERSION, VIDEO_LIVE } from '@server/initializers/constants' |
13 | import { removeHLSFileObjectStorageByPath, storeHLSFileFromFilename, storeHLSFileFromPath } from '@server/lib/object-storage' | 12 | import { removeHLSFileObjectStorageByPath, storeHLSFileFromFilename, storeHLSFileFromPath } from '@server/lib/object-storage' |
14 | import { VideoFileModel } from '@server/models/video/video-file' | 13 | import { VideoFileModel } from '@server/models/video/video-file' |
14 | import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist' | ||
15 | import { MStreamingPlaylistVideo, MUserId, MVideoLiveVideo } from '@server/types/models' | 15 | import { MStreamingPlaylistVideo, MUserId, MVideoLiveVideo } from '@server/types/models' |
16 | import { VideoStorage } from '@shared/models' | 16 | import { VideoStorage, VideoStreamingPlaylistType } from '@shared/models' |
17 | import { getLiveDirectory, getLiveReplayBaseDirectory } from '../../paths' | 17 | import { |
18 | generateHLSMasterPlaylistFilename, | ||
19 | generateHlsSha256SegmentsFilename, | ||
20 | getLiveDirectory, | ||
21 | getLiveReplayBaseDirectory | ||
22 | } from '../../paths' | ||
18 | import { VideoTranscodingProfilesManager } from '../../transcoding/default-transcoding-profiles' | 23 | import { VideoTranscodingProfilesManager } from '../../transcoding/default-transcoding-profiles' |
19 | import { isAbleToUploadVideo } from '../../user' | 24 | import { isAbleToUploadVideo } from '../../user' |
20 | import { LiveQuotaStore } from '../live-quota-store' | 25 | import { LiveQuotaStore } from '../live-quota-store' |
@@ -53,7 +58,6 @@ class MuxingSession extends EventEmitter { | |||
53 | private readonly user: MUserId | 58 | private readonly user: MUserId |
54 | private readonly sessionId: string | 59 | private readonly sessionId: string |
55 | private readonly videoLive: MVideoLiveVideo | 60 | private readonly videoLive: MVideoLiveVideo |
56 | private readonly streamingPlaylist: MStreamingPlaylistVideo | ||
57 | private readonly inputUrl: string | 61 | private readonly inputUrl: string |
58 | private readonly fps: number | 62 | private readonly fps: number |
59 | private readonly allResolutions: number[] | 63 | private readonly allResolutions: number[] |
@@ -70,12 +74,13 @@ class MuxingSession extends EventEmitter { | |||
70 | private readonly outDirectory: string | 74 | private readonly outDirectory: string |
71 | private readonly replayDirectory: string | 75 | private readonly replayDirectory: string |
72 | 76 | ||
73 | private readonly liveSegmentShaStore: LiveSegmentShaStore | ||
74 | |||
75 | private readonly lTags: LoggerTagsFn | 77 | private readonly lTags: LoggerTagsFn |
76 | 78 | ||
77 | private segmentsToProcessPerPlaylist: { [playlistId: string]: string[] } = {} | 79 | private segmentsToProcessPerPlaylist: { [playlistId: string]: string[] } = {} |
78 | 80 | ||
81 | private streamingPlaylist: MStreamingPlaylistVideo | ||
82 | private liveSegmentShaStore: LiveSegmentShaStore | ||
83 | |||
79 | private tsWatcher: FSWatcher | 84 | private tsWatcher: FSWatcher |
80 | private masterWatcher: FSWatcher | 85 | private masterWatcher: FSWatcher |
81 | private m3u8Watcher: FSWatcher | 86 | private m3u8Watcher: FSWatcher |
@@ -98,7 +103,6 @@ class MuxingSession extends EventEmitter { | |||
98 | user: MUserId | 103 | user: MUserId |
99 | sessionId: string | 104 | sessionId: string |
100 | videoLive: MVideoLiveVideo | 105 | videoLive: MVideoLiveVideo |
101 | streamingPlaylist: MStreamingPlaylistVideo | ||
102 | inputUrl: string | 106 | inputUrl: string |
103 | fps: number | 107 | fps: number |
104 | bitrate: number | 108 | bitrate: number |
@@ -112,7 +116,6 @@ class MuxingSession extends EventEmitter { | |||
112 | this.user = options.user | 116 | this.user = options.user |
113 | this.sessionId = options.sessionId | 117 | this.sessionId = options.sessionId |
114 | this.videoLive = options.videoLive | 118 | this.videoLive = options.videoLive |
115 | this.streamingPlaylist = options.streamingPlaylist | ||
116 | this.inputUrl = options.inputUrl | 119 | this.inputUrl = options.inputUrl |
117 | this.fps = options.fps | 120 | this.fps = options.fps |
118 | 121 | ||
@@ -131,17 +134,13 @@ class MuxingSession extends EventEmitter { | |||
131 | this.outDirectory = getLiveDirectory(this.videoLive.Video) | 134 | this.outDirectory = getLiveDirectory(this.videoLive.Video) |
132 | this.replayDirectory = join(getLiveReplayBaseDirectory(this.videoLive.Video), new Date().toISOString()) | 135 | this.replayDirectory = join(getLiveReplayBaseDirectory(this.videoLive.Video), new Date().toISOString()) |
133 | 136 | ||
134 | this.liveSegmentShaStore = new LiveSegmentShaStore({ | ||
135 | videoUUID: this.videoLive.Video.uuid, | ||
136 | sha256Path: join(this.outDirectory, this.streamingPlaylist.segmentsSha256Filename), | ||
137 | streamingPlaylist: this.streamingPlaylist, | ||
138 | sendToObjectStorage: CONFIG.OBJECT_STORAGE.ENABLED | ||
139 | }) | ||
140 | |||
141 | this.lTags = loggerTagsFactory('live', this.sessionId, this.videoUUID) | 137 | this.lTags = loggerTagsFactory('live', this.sessionId, this.videoUUID) |
142 | } | 138 | } |
143 | 139 | ||
144 | async runMuxing () { | 140 | async runMuxing () { |
141 | this.streamingPlaylist = await this.createLivePlaylist() | ||
142 | |||
143 | this.createLiveShaStore() | ||
145 | this.createFiles() | 144 | this.createFiles() |
146 | 145 | ||
147 | await this.prepareDirectories() | 146 | await this.prepareDirectories() |
@@ -257,17 +256,18 @@ class MuxingSession extends EventEmitter { | |||
257 | this.masterWatcher = watch(this.outDirectory + '/' + this.streamingPlaylist.playlistFilename) | 256 | this.masterWatcher = watch(this.outDirectory + '/' + this.streamingPlaylist.playlistFilename) |
258 | 257 | ||
259 | this.masterWatcher.on('add', async () => { | 258 | this.masterWatcher.on('add', async () => { |
260 | if (this.streamingPlaylist.storage === VideoStorage.OBJECT_STORAGE) { | 259 | try { |
261 | try { | 260 | if (this.streamingPlaylist.storage === VideoStorage.OBJECT_STORAGE) { |
262 | const url = await storeHLSFileFromFilename(this.streamingPlaylist, this.streamingPlaylist.playlistFilename) | 261 | const url = await storeHLSFileFromFilename(this.streamingPlaylist, this.streamingPlaylist.playlistFilename) |
263 | 262 | ||
264 | this.streamingPlaylist.playlistUrl = url | 263 | this.streamingPlaylist.playlistUrl = url |
265 | this.streamingPlaylist.assignP2PMediaLoaderInfoHashes(this.videoLive.Video, this.allResolutions) | ||
266 | |||
267 | await this.streamingPlaylist.save() | ||
268 | } catch (err) { | ||
269 | logger.error('Cannot upload live master file to object storage.', { err, ...this.lTags() }) | ||
270 | } | 264 | } |
265 | |||
266 | this.streamingPlaylist.assignP2PMediaLoaderInfoHashes(this.videoLive.Video, this.allResolutions) | ||
267 | |||
268 | await this.streamingPlaylist.save() | ||
269 | } catch (err) { | ||
270 | logger.error('Cannot update streaming playlist.', { err, ...this.lTags() }) | ||
271 | } | 271 | } |
272 | 272 | ||
273 | this.masterPlaylistCreated = true | 273 | this.masterPlaylistCreated = true |
@@ -478,6 +478,31 @@ class MuxingSession extends EventEmitter { | |||
478 | logger.error('Cannot copy segment %s to replay directory.', segmentPath, { err, ...this.lTags() }) | 478 | logger.error('Cannot copy segment %s to replay directory.', segmentPath, { err, ...this.lTags() }) |
479 | } | 479 | } |
480 | } | 480 | } |
481 | |||
482 | private async createLivePlaylist (): Promise<MStreamingPlaylistVideo> { | ||
483 | const playlist = await VideoStreamingPlaylistModel.loadOrGenerate(this.videoLive.Video) | ||
484 | |||
485 | playlist.playlistFilename = generateHLSMasterPlaylistFilename(true) | ||
486 | playlist.segmentsSha256Filename = generateHlsSha256SegmentsFilename(true) | ||
487 | |||
488 | playlist.p2pMediaLoaderPeerVersion = P2P_MEDIA_LOADER_PEER_VERSION | ||
489 | playlist.type = VideoStreamingPlaylistType.HLS | ||
490 | |||
491 | playlist.storage = CONFIG.OBJECT_STORAGE.ENABLED | ||
492 | ? VideoStorage.OBJECT_STORAGE | ||
493 | : VideoStorage.FILE_SYSTEM | ||
494 | |||
495 | return playlist.save() | ||
496 | } | ||
497 | |||
498 | private createLiveShaStore () { | ||
499 | this.liveSegmentShaStore = new LiveSegmentShaStore({ | ||
500 | videoUUID: this.videoLive.Video.uuid, | ||
501 | sha256Path: join(this.outDirectory, this.streamingPlaylist.segmentsSha256Filename), | ||
502 | streamingPlaylist: this.streamingPlaylist, | ||
503 | sendToObjectStorage: CONFIG.OBJECT_STORAGE.ENABLED | ||
504 | }) | ||
505 | } | ||
481 | } | 506 | } |
482 | 507 | ||
483 | // --------------------------------------------------------------------------- | 508 | // --------------------------------------------------------------------------- |