Fix broken replay on live privacy change
This commit is contained in:
parent
7b06f37b22
commit
6f3b827d6c
@ -1,7 +1,7 @@
|
||||
import { LoginPage } from '../po/login.po'
|
||||
import { VideoPublishPage } from '../po/video-publish.po'
|
||||
import { VideoWatchPage } from '../po/video-watch.po'
|
||||
import { browserSleep, getScreenshotPath, isMobileDevice, isSafari, waitServerUp } from '../utils'
|
||||
import { getScreenshotPath, isMobileDevice, isSafari, waitServerUp } from '../utils'
|
||||
|
||||
describe('Publish video', () => {
|
||||
let videoPublishPage: VideoPublishPage
|
||||
|
@ -2,23 +2,42 @@
|
||||
|
||||
import { HttpStatusCode, LiveVideoCreate, VideoPrivacy } from '@peertube/peertube-models'
|
||||
import {
|
||||
cleanupTests, createSingleServer, makeRawRequest,
|
||||
cleanupTests,
|
||||
createSingleServer,
|
||||
findExternalSavedVideo,
|
||||
makeRawRequest,
|
||||
PeerTubeServer,
|
||||
setAccessTokensToServers,
|
||||
setDefaultVideoChannel,
|
||||
stopFfmpeg,
|
||||
waitJobs,
|
||||
waitUntilLivePublishedOnAllServers,
|
||||
waitUntilLiveReplacedByReplayOnAllServers
|
||||
waitUntilLiveReplacedByReplayOnAllServers,
|
||||
waitUntilLiveWaitingOnAllServers
|
||||
} from '@peertube/peertube-server-commands'
|
||||
import { expect } from 'chai'
|
||||
|
||||
async function testVideoFiles (options: {
|
||||
server: PeerTubeServer
|
||||
uuid: string
|
||||
isPrivate: boolean
|
||||
}) {
|
||||
const { server, uuid, isPrivate } = options
|
||||
|
||||
async function testVideoFiles (server: PeerTubeServer, uuid: string) {
|
||||
const video = await server.videos.getWithToken({ id: uuid })
|
||||
const playlist = video.streamingPlaylists[0]
|
||||
|
||||
const expectedStatus = HttpStatusCode.OK_200
|
||||
const urls = [ playlist.playlistUrl, playlist.segmentsSha256Url ]
|
||||
|
||||
await makeRawRequest({ url: video.streamingPlaylists[0].playlistUrl, token: server.accessToken, expectedStatus })
|
||||
await makeRawRequest({ url: video.streamingPlaylists[0].segmentsSha256Url, token: server.accessToken, expectedStatus })
|
||||
for (const url of urls) {
|
||||
await makeRawRequest({ url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
|
||||
|
||||
if (isPrivate) {
|
||||
expect(url).to.not.include('/private/')
|
||||
} else {
|
||||
expect(url).to.include('/private/')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('Live privacy update', function () {
|
||||
@ -43,7 +62,7 @@ describe('Live privacy update', function () {
|
||||
this.timeout(120000)
|
||||
|
||||
const fields: LiveVideoCreate = {
|
||||
name: 'live',
|
||||
name: 'normal live',
|
||||
privacy: VideoPrivacy.PUBLIC,
|
||||
permanentLive: false,
|
||||
replaySettings: { privacy: VideoPrivacy.PRIVATE },
|
||||
@ -61,7 +80,7 @@ describe('Live privacy update', function () {
|
||||
await waitUntilLiveReplacedByReplayOnAllServers([ server ], uuid)
|
||||
await waitJobs([ server ])
|
||||
|
||||
await testVideoFiles(server, uuid)
|
||||
await testVideoFiles({ server, uuid, isPrivate: false })
|
||||
})
|
||||
|
||||
it('Should update the replay to public and re-update it to private', async function () {
|
||||
@ -69,11 +88,44 @@ describe('Live privacy update', function () {
|
||||
|
||||
await server.videos.update({ id: uuid, attributes: { privacy: VideoPrivacy.PUBLIC } })
|
||||
await waitJobs([ server ])
|
||||
await testVideoFiles(server, uuid)
|
||||
await testVideoFiles({ server, uuid, isPrivate: true })
|
||||
|
||||
await server.videos.update({ id: uuid, attributes: { privacy: VideoPrivacy.PRIVATE } })
|
||||
await waitJobs([ server ])
|
||||
await testVideoFiles(server, uuid)
|
||||
await testVideoFiles({ server, uuid, isPrivate: false })
|
||||
})
|
||||
})
|
||||
|
||||
describe('Permanent live', function () {
|
||||
let liveUUID: string
|
||||
|
||||
it('Should update the permanent live privacy but still process the replay', async function () {
|
||||
this.timeout(120000)
|
||||
|
||||
const fields: LiveVideoCreate = {
|
||||
name: 'permanent live',
|
||||
privacy: VideoPrivacy.PUBLIC,
|
||||
permanentLive: true,
|
||||
replaySettings: { privacy: VideoPrivacy.PUBLIC },
|
||||
saveReplay: true,
|
||||
channelId: server.store.channel.id
|
||||
}
|
||||
|
||||
const video = await server.live.create({ fields })
|
||||
liveUUID = video.uuid
|
||||
|
||||
const ffmpegCommand = await server.live.sendRTMPStreamInVideo({ videoId: liveUUID })
|
||||
await waitUntilLivePublishedOnAllServers([ server ], liveUUID)
|
||||
await stopFfmpeg(ffmpegCommand)
|
||||
await waitUntilLiveWaitingOnAllServers([ server ], liveUUID)
|
||||
|
||||
await server.videos.update({ id: liveUUID, attributes: { privacy: VideoPrivacy.PRIVATE } })
|
||||
await waitJobs([ server ])
|
||||
|
||||
const replay = await findExternalSavedVideo(server, liveUUID)
|
||||
expect(replay).to.exist
|
||||
|
||||
await testVideoFiles({ server, uuid: replay.uuid, isPrivate: true })
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -102,7 +102,7 @@ describe('Test videos files', function () {
|
||||
await waitJobs(servers)
|
||||
})
|
||||
|
||||
it('Shoulde delete a web video file', async function () {
|
||||
it('Should delete a web video file', async function () {
|
||||
this.timeout(30_000)
|
||||
|
||||
const video = await servers[0].videos.get({ id: webVideoId })
|
||||
|
@ -39,13 +39,13 @@ import {
|
||||
import { Job } from 'bullmq'
|
||||
import { pathExists, remove } from 'fs-extra/esm'
|
||||
import { readdir } from 'fs/promises'
|
||||
import { join } from 'path'
|
||||
import { isAbsolute, join } from 'path'
|
||||
import { logger, loggerTagsFactory } from '../../../helpers/logger.js'
|
||||
import { JobQueue } from '../job-queue.js'
|
||||
|
||||
const lTags = loggerTagsFactory('live', 'job')
|
||||
|
||||
async function processVideoLiveEnding (job: Job) {
|
||||
export async function processVideoLiveEnding (job: Job) {
|
||||
const payload = job.data as VideoLiveEndingPayload
|
||||
|
||||
logger.info('Processing video live ending for %s.', payload.videoId, { payload, ...lTags() })
|
||||
@ -72,38 +72,47 @@ async function processVideoLiveEnding (job: Job) {
|
||||
return cleanupLiveAndFederate({ permanentLive, video, streamingPlaylistId: payload.streamingPlaylistId })
|
||||
}
|
||||
|
||||
if (await hasReplayFiles(payload.replayDirectory) !== true) {
|
||||
logger.info(`No replay files found for live ${video.uuid}, skipping video replay creation.`, { ...lTags(video.uuid) })
|
||||
let replayDirectory = payload.replayDirectory
|
||||
|
||||
return cleanupLiveAndFederate({ permanentLive, video, streamingPlaylistId: payload.streamingPlaylistId })
|
||||
// Introduced in PeerTube 7.2, allow to use the appropriate base directory even if the live privacy changed
|
||||
if (!isAbsolute(replayDirectory)) {
|
||||
replayDirectory = join(getLiveReplayBaseDirectory(video), replayDirectory)
|
||||
}
|
||||
|
||||
if (permanentLive) {
|
||||
await saveReplayToExternalVideo({
|
||||
liveVideo: video,
|
||||
liveSession,
|
||||
publishedAt: payload.publishedAt,
|
||||
replayDirectory: payload.replayDirectory
|
||||
})
|
||||
const inputFileMutexReleaser = await VideoPathManager.Instance.lockFiles(video.uuid)
|
||||
|
||||
return cleanupLiveAndFederate({ permanentLive, video, streamingPlaylistId: payload.streamingPlaylistId })
|
||||
try {
|
||||
await video.reload()
|
||||
|
||||
if (await hasReplayFiles(replayDirectory) !== true) {
|
||||
logger.info(`No replay files found for live ${video.uuid}, skipping video replay creation.`, { ...lTags(video.uuid) })
|
||||
|
||||
await cleanupLiveAndFederate({ permanentLive, video, streamingPlaylistId: payload.streamingPlaylistId })
|
||||
} else if (permanentLive) {
|
||||
await saveReplayToExternalVideo({
|
||||
liveVideo: video,
|
||||
liveSession,
|
||||
publishedAt: payload.publishedAt,
|
||||
replayDirectory
|
||||
})
|
||||
|
||||
await cleanupLiveAndFederate({ permanentLive, video, streamingPlaylistId: payload.streamingPlaylistId })
|
||||
} else {
|
||||
await replaceLiveByReplay({
|
||||
video,
|
||||
liveSession,
|
||||
live,
|
||||
permanentLive,
|
||||
replayDirectory
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
inputFileMutexReleaser()
|
||||
}
|
||||
|
||||
return replaceLiveByReplay({
|
||||
video,
|
||||
liveSession,
|
||||
live,
|
||||
permanentLive,
|
||||
replayDirectory: payload.replayDirectory
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
processVideoLiveEnding
|
||||
}
|
||||
|
||||
// Private
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function saveReplayToExternalVideo (options: {
|
||||
@ -167,16 +176,10 @@ async function saveReplayToExternalVideo (options: {
|
||||
})
|
||||
}
|
||||
|
||||
const inputFileMutexReleaser = await VideoPathManager.Instance.lockFiles(liveVideo.uuid)
|
||||
await assignReplayFilesToVideo({ video: replayVideo, replayDirectory })
|
||||
|
||||
try {
|
||||
await assignReplayFilesToVideo({ video: replayVideo, replayDirectory })
|
||||
|
||||
logger.info(`Removing replay directory ${replayDirectory}`, lTags(liveVideo.uuid))
|
||||
await remove(replayDirectory)
|
||||
} finally {
|
||||
inputFileMutexReleaser()
|
||||
}
|
||||
logger.info(`Removing replay directory ${replayDirectory}`, lTags(liveVideo.uuid))
|
||||
await remove(replayDirectory)
|
||||
|
||||
try {
|
||||
await copyOrRegenerateThumbnails({ liveVideo, replayVideo })
|
||||
@ -266,25 +269,19 @@ async function replaceLiveByReplay (options: {
|
||||
hlsPlaylist.segmentsSha256Filename = generateHlsSha256SegmentsFilename()
|
||||
await hlsPlaylist.save()
|
||||
|
||||
const inputFileMutexReleaser = await VideoPathManager.Instance.lockFiles(videoWithFiles.uuid)
|
||||
await assignReplayFilesToVideo({ video: videoWithFiles, replayDirectory })
|
||||
|
||||
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))
|
||||
|
||||
// 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))
|
||||
}
|
||||
// If the live was in another base directory, also delete it
|
||||
if (replayInAnotherDirectory) {
|
||||
await remove(getHLSDirectory(liveVideo))
|
||||
}
|
||||
} finally {
|
||||
inputFileMutexReleaser()
|
||||
}
|
||||
|
||||
// Regenerate the thumbnail & preview?
|
||||
|
@ -27,7 +27,6 @@ import { Server, createServer } from 'net'
|
||||
import context from 'node-media-server/src/node_core_ctx.js'
|
||||
import nodeMediaServerLogger from 'node-media-server/src/node_core_logger.js'
|
||||
import NodeRtmpSession from 'node-media-server/src/node_rtmp_session.js'
|
||||
import { join } from 'path'
|
||||
import { Server as ServerTLS, createServer as createServerTLS } from 'tls'
|
||||
import { federateVideoIfNeeded } from '../activitypub/videos/index.js'
|
||||
import { JobQueue } from '../job-queue/index.js'
|
||||
@ -621,7 +620,7 @@ class LiveManager {
|
||||
|
||||
if (files.length === 0) return undefined
|
||||
|
||||
return join(directory, files.sort().reverse()[0])
|
||||
return files.sort().reverse()[0]
|
||||
}
|
||||
|
||||
private buildAllResolutionsToTranscode (originResolution: number, hasAudio: boolean) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user