Refactor playlist creation for lives

pull/5573/head
Chocobozzz 2023-01-27 09:04:02 +01:00
parent 06a9fdf433
commit afb371d940
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
3 changed files with 54 additions and 53 deletions

View File

@ -13,18 +13,18 @@ import {
} from '@server/helpers/ffmpeg' } from '@server/helpers/ffmpeg'
import { logger, loggerTagsFactory } from '@server/helpers/logger' import { logger, loggerTagsFactory } from '@server/helpers/logger'
import { CONFIG, registerConfigChangedHandler } from '@server/initializers/config' import { CONFIG, registerConfigChangedHandler } from '@server/initializers/config'
import { P2P_MEDIA_LOADER_PEER_VERSION, VIDEO_LIVE } from '@server/initializers/constants' import { VIDEO_LIVE } from '@server/initializers/constants'
import { UserModel } from '@server/models/user/user' import { UserModel } from '@server/models/user/user'
import { VideoModel } from '@server/models/video/video' import { VideoModel } from '@server/models/video/video'
import { VideoLiveModel } from '@server/models/video/video-live' import { VideoLiveModel } from '@server/models/video/video-live'
import { VideoLiveSessionModel } from '@server/models/video/video-live-session' import { VideoLiveSessionModel } from '@server/models/video/video-live-session'
import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist' import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist'
import { MStreamingPlaylistVideo, MVideo, MVideoLiveSession, MVideoLiveVideo } from '@server/types/models' import { MVideo, MVideoLiveSession, MVideoLiveVideo } from '@server/types/models'
import { pick, wait } from '@shared/core-utils' import { pick, wait } from '@shared/core-utils'
import { LiveVideoError, VideoState, VideoStorage, VideoStreamingPlaylistType } from '@shared/models' import { LiveVideoError, VideoState } from '@shared/models'
import { federateVideoIfNeeded } from '../activitypub/videos' import { federateVideoIfNeeded } from '../activitypub/videos'
import { JobQueue } from '../job-queue' import { JobQueue } from '../job-queue'
import { generateHLSMasterPlaylistFilename, generateHlsSha256SegmentsFilename, getLiveReplayBaseDirectory } from '../paths' import { getLiveReplayBaseDirectory } from '../paths'
import { PeerTubeSocket } from '../peertube-socket' import { PeerTubeSocket } from '../peertube-socket'
import { Hooks } from '../plugins/hooks' import { Hooks } from '../plugins/hooks'
import { LiveQuotaStore } from './live-quota-store' import { LiveQuotaStore } from './live-quota-store'
@ -255,13 +255,10 @@ class LiveManager {
{ allResolutions, ...lTags(sessionId, video.uuid) } { allResolutions, ...lTags(sessionId, video.uuid) }
) )
const streamingPlaylist = await this.createLivePlaylist(video, allResolutions)
return this.runMuxingSession({ return this.runMuxingSession({
sessionId, sessionId,
videoLive, videoLive,
streamingPlaylist,
inputUrl, inputUrl,
fps, fps,
bitrate, bitrate,
@ -275,7 +272,6 @@ class LiveManager {
sessionId: string sessionId: string
videoLive: MVideoLiveVideo videoLive: MVideoLiveVideo
streamingPlaylist: MStreamingPlaylistVideo
inputUrl: string inputUrl: string
fps: number fps: number
bitrate: number bitrate: number
@ -298,7 +294,7 @@ class LiveManager {
videoLive, videoLive,
user, user,
...pick(options, [ 'streamingPlaylist', 'inputUrl', 'bitrate', 'ratio', 'fps', 'allResolutions', 'hasAudio' ]) ...pick(options, [ 'inputUrl', 'bitrate', 'ratio', 'fps', 'allResolutions', 'hasAudio' ])
}) })
muxingSession.on('live-ready', () => this.publishAndFederateLive(videoLive, localLTags)) muxingSession.on('live-ready', () => this.publishAndFederateLive(videoLive, localLTags))
@ -474,26 +470,6 @@ class LiveManager {
return resolutionsEnabled return resolutionsEnabled
} }
private async createLivePlaylist (video: MVideo, allResolutions: number[]): Promise<MStreamingPlaylistVideo> {
const playlist = await VideoStreamingPlaylistModel.loadOrGenerate(video)
playlist.playlistFilename = generateHLSMasterPlaylistFilename(true)
playlist.segmentsSha256Filename = generateHlsSha256SegmentsFilename(true)
playlist.p2pMediaLoaderPeerVersion = P2P_MEDIA_LOADER_PEER_VERSION
playlist.type = VideoStreamingPlaylistType.HLS
playlist.storage = CONFIG.OBJECT_STORAGE.ENABLED
? VideoStorage.OBJECT_STORAGE
: VideoStorage.FILE_SYSTEM
if (playlist.storage === VideoStorage.FILE_SYSTEM) {
playlist.assignP2PMediaLoaderInfoHashes(video, allResolutions)
}
return playlist.save()
}
private saveStartingSession (videoLive: MVideoLiveVideo) { private saveStartingSession (videoLive: MVideoLiveVideo) {
const liveSession = new VideoLiveSessionModel({ const liveSession = new VideoLiveSessionModel({
startDate: new Date(), startDate: new Date(),

View File

@ -1,4 +1,3 @@
import { mapSeries } from 'bluebird' import { mapSeries } from 'bluebird'
import { FSWatcher, watch } from 'chokidar' import { FSWatcher, watch } from 'chokidar'
import { FfmpegCommand } from 'fluent-ffmpeg' import { FfmpegCommand } from 'fluent-ffmpeg'
@ -9,12 +8,18 @@ import { EventEmitter } from 'stream'
import { getLiveMuxingCommand, getLiveTranscodingCommand } from '@server/helpers/ffmpeg' import { getLiveMuxingCommand, getLiveTranscodingCommand } from '@server/helpers/ffmpeg'
import { logger, loggerTagsFactory, LoggerTagsFn } from '@server/helpers/logger' import { logger, loggerTagsFactory, LoggerTagsFn } from '@server/helpers/logger'
import { CONFIG } from '@server/initializers/config' import { CONFIG } from '@server/initializers/config'
import { MEMOIZE_TTL, VIDEO_LIVE } from '@server/initializers/constants' import { MEMOIZE_TTL, P2P_MEDIA_LOADER_PEER_VERSION, VIDEO_LIVE } from '@server/initializers/constants'
import { removeHLSFileObjectStorageByPath, storeHLSFileFromFilename, storeHLSFileFromPath } from '@server/lib/object-storage' import { removeHLSFileObjectStorageByPath, storeHLSFileFromFilename, storeHLSFileFromPath } from '@server/lib/object-storage'
import { VideoFileModel } from '@server/models/video/video-file' import { VideoFileModel } from '@server/models/video/video-file'
import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist'
import { MStreamingPlaylistVideo, MUserId, MVideoLiveVideo } from '@server/types/models' import { MStreamingPlaylistVideo, MUserId, MVideoLiveVideo } from '@server/types/models'
import { VideoStorage } from '@shared/models' import { VideoStorage, VideoStreamingPlaylistType } from '@shared/models'
import { getLiveDirectory, getLiveReplayBaseDirectory } from '../../paths' import {
generateHLSMasterPlaylistFilename,
generateHlsSha256SegmentsFilename,
getLiveDirectory,
getLiveReplayBaseDirectory
} from '../../paths'
import { VideoTranscodingProfilesManager } from '../../transcoding/default-transcoding-profiles' import { VideoTranscodingProfilesManager } from '../../transcoding/default-transcoding-profiles'
import { isAbleToUploadVideo } from '../../user' import { isAbleToUploadVideo } from '../../user'
import { LiveQuotaStore } from '../live-quota-store' import { LiveQuotaStore } from '../live-quota-store'
@ -53,7 +58,6 @@ class MuxingSession extends EventEmitter {
private readonly user: MUserId private readonly user: MUserId
private readonly sessionId: string private readonly sessionId: string
private readonly videoLive: MVideoLiveVideo private readonly videoLive: MVideoLiveVideo
private readonly streamingPlaylist: MStreamingPlaylistVideo
private readonly inputUrl: string private readonly inputUrl: string
private readonly fps: number private readonly fps: number
private readonly allResolutions: number[] private readonly allResolutions: number[]
@ -70,12 +74,13 @@ class MuxingSession extends EventEmitter {
private readonly outDirectory: string private readonly outDirectory: string
private readonly replayDirectory: string private readonly replayDirectory: string
private readonly liveSegmentShaStore: LiveSegmentShaStore
private readonly lTags: LoggerTagsFn private readonly lTags: LoggerTagsFn
private segmentsToProcessPerPlaylist: { [playlistId: string]: string[] } = {} private segmentsToProcessPerPlaylist: { [playlistId: string]: string[] } = {}
private streamingPlaylist: MStreamingPlaylistVideo
private liveSegmentShaStore: LiveSegmentShaStore
private tsWatcher: FSWatcher private tsWatcher: FSWatcher
private masterWatcher: FSWatcher private masterWatcher: FSWatcher
private m3u8Watcher: FSWatcher private m3u8Watcher: FSWatcher
@ -98,7 +103,6 @@ class MuxingSession extends EventEmitter {
user: MUserId user: MUserId
sessionId: string sessionId: string
videoLive: MVideoLiveVideo videoLive: MVideoLiveVideo
streamingPlaylist: MStreamingPlaylistVideo
inputUrl: string inputUrl: string
fps: number fps: number
bitrate: number bitrate: number
@ -112,7 +116,6 @@ class MuxingSession extends EventEmitter {
this.user = options.user this.user = options.user
this.sessionId = options.sessionId this.sessionId = options.sessionId
this.videoLive = options.videoLive this.videoLive = options.videoLive
this.streamingPlaylist = options.streamingPlaylist
this.inputUrl = options.inputUrl this.inputUrl = options.inputUrl
this.fps = options.fps this.fps = options.fps
@ -131,17 +134,13 @@ class MuxingSession extends EventEmitter {
this.outDirectory = getLiveDirectory(this.videoLive.Video) this.outDirectory = getLiveDirectory(this.videoLive.Video)
this.replayDirectory = join(getLiveReplayBaseDirectory(this.videoLive.Video), new Date().toISOString()) this.replayDirectory = join(getLiveReplayBaseDirectory(this.videoLive.Video), new Date().toISOString())
this.liveSegmentShaStore = new LiveSegmentShaStore({
videoUUID: this.videoLive.Video.uuid,
sha256Path: join(this.outDirectory, this.streamingPlaylist.segmentsSha256Filename),
streamingPlaylist: this.streamingPlaylist,
sendToObjectStorage: CONFIG.OBJECT_STORAGE.ENABLED
})
this.lTags = loggerTagsFactory('live', this.sessionId, this.videoUUID) this.lTags = loggerTagsFactory('live', this.sessionId, this.videoUUID)
} }
async runMuxing () { async runMuxing () {
this.streamingPlaylist = await this.createLivePlaylist()
this.createLiveShaStore()
this.createFiles() this.createFiles()
await this.prepareDirectories() await this.prepareDirectories()
@ -257,17 +256,18 @@ class MuxingSession extends EventEmitter {
this.masterWatcher = watch(this.outDirectory + '/' + this.streamingPlaylist.playlistFilename) this.masterWatcher = watch(this.outDirectory + '/' + this.streamingPlaylist.playlistFilename)
this.masterWatcher.on('add', async () => { this.masterWatcher.on('add', async () => {
if (this.streamingPlaylist.storage === VideoStorage.OBJECT_STORAGE) { try {
try { if (this.streamingPlaylist.storage === VideoStorage.OBJECT_STORAGE) {
const url = await storeHLSFileFromFilename(this.streamingPlaylist, this.streamingPlaylist.playlistFilename) const url = await storeHLSFileFromFilename(this.streamingPlaylist, this.streamingPlaylist.playlistFilename)
this.streamingPlaylist.playlistUrl = url this.streamingPlaylist.playlistUrl = url
this.streamingPlaylist.assignP2PMediaLoaderInfoHashes(this.videoLive.Video, this.allResolutions)
await this.streamingPlaylist.save()
} catch (err) {
logger.error('Cannot upload live master file to object storage.', { err, ...this.lTags() })
} }
this.streamingPlaylist.assignP2PMediaLoaderInfoHashes(this.videoLive.Video, this.allResolutions)
await this.streamingPlaylist.save()
} catch (err) {
logger.error('Cannot update streaming playlist.', { err, ...this.lTags() })
} }
this.masterPlaylistCreated = true this.masterPlaylistCreated = true
@ -478,6 +478,31 @@ class MuxingSession extends EventEmitter {
logger.error('Cannot copy segment %s to replay directory.', segmentPath, { err, ...this.lTags() }) logger.error('Cannot copy segment %s to replay directory.', segmentPath, { err, ...this.lTags() })
} }
} }
private async createLivePlaylist (): Promise<MStreamingPlaylistVideo> {
const playlist = await VideoStreamingPlaylistModel.loadOrGenerate(this.videoLive.Video)
playlist.playlistFilename = generateHLSMasterPlaylistFilename(true)
playlist.segmentsSha256Filename = generateHlsSha256SegmentsFilename(true)
playlist.p2pMediaLoaderPeerVersion = P2P_MEDIA_LOADER_PEER_VERSION
playlist.type = VideoStreamingPlaylistType.HLS
playlist.storage = CONFIG.OBJECT_STORAGE.ENABLED
? VideoStorage.OBJECT_STORAGE
: VideoStorage.FILE_SYSTEM
return playlist.save()
}
private createLiveShaStore () {
this.liveSegmentShaStore = new LiveSegmentShaStore({
videoUUID: this.videoLive.Video.uuid,
sha256Path: join(this.outDirectory, this.streamingPlaylist.segmentsSha256Filename),
streamingPlaylist: this.streamingPlaylist,
sendToObjectStorage: CONFIG.OBJECT_STORAGE.ENABLED
})
}
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -95,7 +95,7 @@ describe('Test proxy', function () {
} }
it('Should succeed import with the appropriate proxy config', async function () { it('Should succeed import with the appropriate proxy config', async function () {
this.timeout(120000) this.timeout(240000)
await servers[0].kill() await servers[0].kill()
await servers[0].run({}, { env: goodEnv }) await servers[0].run({}, { env: goodEnv })