--- /dev/null
+/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
+
+import { HttpStatusCode, LiveVideoCreate, VideoPrivacy } from '@peertube/peertube-models'
+import {
+ cleanupTests, createSingleServer, makeRawRequest,
+ PeerTubeServer,
+ setAccessTokensToServers,
+ setDefaultVideoChannel,
+ stopFfmpeg,
+ waitJobs,
+ waitUntilLivePublishedOnAllServers,
+ waitUntilLiveReplacedByReplayOnAllServers
+} from '@peertube/peertube-server-commands'
+
+async function testVideoFiles (server: PeerTubeServer, uuid: string) {
+ const video = await server.videos.getWithToken({ id: uuid })
+
+ const expectedStatus = HttpStatusCode.OK_200
+
+ await makeRawRequest({ url: video.streamingPlaylists[0].playlistUrl, token: server.accessToken, expectedStatus })
+ await makeRawRequest({ url: video.streamingPlaylists[0].segmentsSha256Url, token: server.accessToken, expectedStatus })
+}
+
+describe('Live privacy update', function () {
+ let server: PeerTubeServer
+
+ before(async function () {
+ this.timeout(120000)
+
+ server = await createSingleServer(1)
+
+ await setAccessTokensToServers([ server ])
+ await setDefaultVideoChannel([ server ])
+
+ await server.config.enableMinimumTranscoding()
+ await server.config.enableLive({ allowReplay: true, transcoding: true, resolutions: 'min' })
+ })
+
+ describe('Normal live', function () {
+ let uuid: string
+
+ it('Should create a public live with private replay', async function () {
+ this.timeout(120000)
+
+ const fields: LiveVideoCreate = {
+ name: 'live',
+ privacy: VideoPrivacy.PUBLIC,
+ permanentLive: false,
+ replaySettings: { privacy: VideoPrivacy.PRIVATE },
+ saveReplay: true,
+ channelId: server.store.channel.id
+ }
+
+ const video = await server.live.create({ fields })
+ uuid = video.uuid
+
+ const ffmpegCommand = await server.live.sendRTMPStreamInVideo({ videoId: uuid })
+ await waitUntilLivePublishedOnAllServers([ server ], uuid)
+ await stopFfmpeg(ffmpegCommand)
+
+ await waitUntilLiveReplacedByReplayOnAllServers([ server ], uuid)
+ await waitJobs([ server ])
+
+ await testVideoFiles(server, uuid)
+ })
+
+ it('Should update the replay to public and re-update it to private', async function () {
+ this.timeout(120000)
+
+ await server.videos.update({ id: uuid, attributes: { privacy: VideoPrivacy.PUBLIC } })
+ await waitJobs([ server ])
+ await testVideoFiles(server, uuid)
+
+ await server.videos.update({ id: uuid, attributes: { privacy: VideoPrivacy.PRIVATE } })
+ await waitJobs([ server ])
+ await testVideoFiles(server, uuid)
+ })
+ })
+
+ after(async function () {
+ await cleanupTests([ server ])
+ })
+})
import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url.js'
import { federateVideoIfNeeded } from '@server/lib/activitypub/videos/index.js'
import { cleanupAndDestroyPermanentLive, cleanupTMPLiveFiles, cleanupUnsavedNormalLive } from '@server/lib/live/index.js'
-import { generateHLSMasterPlaylistFilename, generateHlsSha256SegmentsFilename, getLiveReplayBaseDirectory } from '@server/lib/paths.js'
+import {
+ generateHLSMasterPlaylistFilename,
+ generateHlsSha256SegmentsFilename,
+ getHLSDirectory,
+ getLiveReplayBaseDirectory
+} from '@server/lib/paths.js'
import { generateLocalVideoMiniature, regenerateMiniaturesIfNeeded } from '@server/lib/thumbnail.js'
import { generateHlsPlaylistResolutionFromTS } from '@server/lib/transcoding/hls-transcoding.js'
import { VideoPathManager } from '@server/lib/video-path-manager.js'
import { ffprobePromise, getAudioStream, getVideoStreamDimensionsInfo, getVideoStreamFPS } from '@peertube/peertube-ffmpeg'
import { logger, loggerTagsFactory } from '../../../helpers/logger.js'
import { JobQueue } from '../job-queue.js'
+import { isVideoInPublicDirectory } from '@server/lib/video-privacy.js'
const lTags = loggerTagsFactory('live', 'job')
})
}
- await assignReplayFilesToVideo({ video: replayVideo, replayDirectory })
+ const inputFileMutexReleaser = await VideoPathManager.Instance.lockFiles(liveVideo.uuid)
- await remove(replayDirectory)
+ try {
+ await assignReplayFilesToVideo({ video: replayVideo, replayDirectory })
+
+ await remove(replayDirectory)
+ } finally {
+ inputFileMutexReleaser()
+ }
for (const type of [ ThumbnailType.MINIATURE, ThumbnailType.PREVIEW ]) {
const image = await generateLocalVideoMiniature({ video: replayVideo, videoFile: replayVideo.getMaxQualityFile(), type })
permanentLive: boolean
replayDirectory: string
}) {
- const { video, liveSession, live, permanentLive, replayDirectory } = options
+ const { video: liveVideo, liveSession, live, permanentLive, replayDirectory } = options
const replaySettings = await VideoLiveReplaySettingModel.load(liveSession.replaySettingId)
- const videoWithFiles = await VideoModel.loadFull(video.id)
+ const videoWithFiles = await VideoModel.loadFull(liveVideo.id)
const hlsPlaylist = videoWithFiles.getHLSPlaylist()
+ const replayInAnotherDirectory = isVideoInPublicDirectory(liveVideo.privacy) !== isVideoInPublicDirectory(replaySettings.privacy)
+
+ logger.info(`Replacing live ${liveVideo.uuid} by replay ${replayDirectory}.`, { replayInAnotherDirectory, ...lTags(liveVideo.uuid) })
await cleanupTMPLiveFiles(videoWithFiles, hlsPlaylist)
hlsPlaylist.segmentsSha256Filename = generateHlsSha256SegmentsFilename()
await hlsPlaylist.save()
- await assignReplayFilesToVideo({ video: videoWithFiles, replayDirectory })
+ const inputFileMutexReleaser = await VideoPathManager.Instance.lockFiles(videoWithFiles.uuid)
- // Should not happen in this function, but we keep the code if in the future we can replace the permanent live by a replay
- if (permanentLive) { // Remove session replay
- await remove(replayDirectory)
- } else { // We won't stream again in this live, we can delete the base replay directory
- await remove(getLiveReplayBaseDirectory(videoWithFiles))
+ try {
+ await assignReplayFilesToVideo({ video: videoWithFiles, replayDirectory })
+
+ // Should not happen in this function, but we keep the code if in the future we can replace the permanent live by a replay
+ if (permanentLive) { // Remove session replay
+ await remove(replayDirectory)
+ } else {
+ // We won't stream again in this live, we can delete the base replay directory
+ await remove(getLiveReplayBaseDirectory(liveVideo))
+
+ // If the live was in another base directory, also delete it
+ if (replayInAnotherDirectory) {
+ await remove(getHLSDirectory(liveVideo))
+ }
+ }
+ } finally {
+ inputFileMutexReleaser()
}
// Regenerate the thumbnail & preview?
const concatenatedTsFiles = await readdir(replayDirectory)
+ logger.info(`Assigning replays ${replayDirectory} to video ${video.uuid}.`, { concatenatedTsFiles, ...lTags(video.uuid) })
+
for (const concatenatedTsFile of concatenatedTsFiles) {
- const inputFileMutexReleaser = await VideoPathManager.Instance.lockFiles(video.uuid)
+ // Generating hls playlist can be long, reload the video in this case
await video.reload()
const concatenatedTsFilePath = join(replayDirectory, concatenatedTsFile)
try {
await generateHlsPlaylistResolutionFromTS({
video,
- inputFileMutexReleaser,
+ inputFileMutexReleaser: null, // Already locked in parent
concatenatedTsFilePath,
resolution,
fps,
isAAC: audioStream?.codec_name === 'aac'
})
+
+ logger.error('coucou')
} catch (err) {
logger.error('Cannot generate HLS playlist resolution from TS files.', { err })
}
-
- inputFileMutexReleaser()
}
return video
videoFile: MVideoFile
videoOutputPath: string
m3u8OutputPath: string
+ filesLockedInParent?: boolean // default false
}) {
- const { video, videoFile, videoOutputPath, m3u8OutputPath } = options
+ const { video, videoFile, videoOutputPath, m3u8OutputPath, filesLockedInParent = false } = options
// Create or update the playlist
const playlist = await retryTransactionWrapper(() => {
})
videoFile.videoStreamingPlaylistId = playlist.id
- const mutexReleaser = await VideoPathManager.Instance.lockFiles(video.uuid)
+ const mutexReleaser = !filesLockedInParent
+ ? await VideoPathManager.Instance.lockFiles(video.uuid)
+ : null
try {
await video.reload()
return { resolutionPlaylistPath, videoFile: savedVideoFile }
} finally {
- mutexReleaser()
+ if (mutexReleaser) mutexReleaser()
}
}
fps: -1
})
- await onHLSVideoFileTranscoding({ video, videoFile: newVideoFile, videoOutputPath, m3u8OutputPath })
+ await onHLSVideoFileTranscoding({
+ video,
+ videoFile: newVideoFile,
+ videoOutputPath,
+ m3u8OutputPath,
+ filesLockedInParent: !inputFileMutexReleaser
+ })
}