mirror of https://github.com/Chocobozzz/PeerTube
Refactor playlist creation for lives
parent
06a9fdf433
commit
afb371d940
|
@ -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(),
|
||||||
|
|
|
@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
@ -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 })
|
||||||
|
|
Loading…
Reference in New Issue