import 'mocha'
import * as chai from 'chai'
+import { FfmpegCommand } from 'fluent-ffmpeg'
+import { join } from 'path'
+import { ffprobePromise, getVideoStreamFromFile } from '@server/helpers/ffprobe-utils'
import { getLiveNotificationSocket } from '@shared/extra-utils/socket/socket-io'
import { LiveVideo, LiveVideoCreate, Video, VideoDetails, VideoPrivacy, VideoState, VideoStreamingPlaylistType } from '@shared/models'
+import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
import {
addVideoToBlacklist,
+ buildServerDirectory,
checkLiveCleanup,
+ checkLiveSegmentHash,
checkResolutionsInMasterPlaylist,
cleanupTests,
createLive,
doubleFollow,
flushAndRunMultipleServers,
getLive,
+ getPlaylist,
getVideo,
getVideoIdFromUUID,
getVideosList,
+ killallServers,
makeRawRequest,
removeVideo,
+ reRunServer,
sendRTMPStream,
sendRTMPStreamInVideo,
ServerInfo,
viewVideo,
wait,
waitJobs,
- waitUntilLiveStarts
+ waitUntilLiveEnded,
+ waitUntilLivePublished,
+ waitUntilLiveSegmentGeneration,
+ waitUntilLog
} from '../../../../shared/extra-utils'
-import { FfmpegCommand } from 'fluent-ffmpeg'
const expect = chai.expect
expect(video.privacy.id).to.equal(VideoPrivacy.UNLISTED)
expect(video.nsfw).to.be.true
- await makeRawRequest(server.url + video.thumbnailPath, 200)
- await makeRawRequest(server.url + video.previewPath, 200)
+ await makeRawRequest(server.url + video.thumbnailPath, HttpStatusCode.OK_200)
+ await makeRawRequest(server.url + video.previewPath, HttpStatusCode.OK_200)
}
})
})
it('Should not be able to update a live of another server', async function () {
- await updateLive(servers[1].url, servers[1].accessToken, liveVideoUUID, { saveReplay: false }, 403)
+ await updateLive(servers[1].url, servers[1].accessToken, liveVideoUUID, { saveReplay: false }, HttpStatusCode.FORBIDDEN_403)
})
it('Should update the live', async function () {
it('Should have the live deleted', async function () {
for (const server of servers) {
- await getVideo(server.url, liveVideoUUID, 404)
- await getLive(server.url, server.accessToken, liveVideoUUID, 404)
+ await getVideo(server.url, liveVideoUUID, HttpStatusCode.NOT_FOUND_404)
+ await getLive(server.url, server.accessToken, liveVideoUUID, HttpStatusCode.NOT_FOUND_404)
}
})
})
expect(hlsPlaylist.files).to.have.lengthOf(0)
await checkResolutionsInMasterPlaylist(hlsPlaylist.playlistUrl, resolutions)
+
+ for (let i = 0; i < resolutions.length; i++) {
+ const segmentNum = 1
+ const segmentName = `${i}-00000${segmentNum}.ts`
+ await waitUntilLiveSegmentGeneration(servers[0], video.uuid, i, segmentNum)
+
+ const res = await getPlaylist(`${servers[0].url}/static/streaming-playlists/hls/${video.uuid}/${i}.m3u8`)
+ const subPlaylist = res.text
+
+ expect(subPlaylist).to.contain(segmentName)
+
+ const baseUrlAndPath = servers[0].url + '/static/streaming-playlists/hls'
+ await checkLiveSegmentHash(baseUrlAndPath, video.uuid, segmentName, hlsPlaylist)
+ }
}
}
liveVideoId = await createLiveWrapper(false)
const command = await sendRTMPStreamInVideo(servers[0].url, servers[0].accessToken, liveVideoId)
- await waitUntilLiveStarts(servers[0].url, servers[0].accessToken, liveVideoId)
+ await waitUntilLivePublished(servers[0].url, servers[0].accessToken, liveVideoId)
await waitJobs(servers)
await testVideoResolutions(liveVideoId, [ 720 ])
liveVideoId = await createLiveWrapper(false)
const command = await sendRTMPStreamInVideo(servers[0].url, servers[0].accessToken, liveVideoId)
- await waitUntilLiveStarts(servers[0].url, servers[0].accessToken, liveVideoId)
+ await waitUntilLivePublished(servers[0].url, servers[0].accessToken, liveVideoId)
await waitJobs(servers)
await testVideoResolutions(liveVideoId, resolutions)
})
it('Should enable transcoding with some resolutions and correctly save them', async function () {
- this.timeout(60000)
+ this.timeout(120000)
const resolutions = [ 240, 360, 720 ]
+
await updateConf(resolutions)
liveVideoId = await createLiveWrapper(true)
- const command = await sendRTMPStreamInVideo(servers[0].url, servers[0].accessToken, liveVideoId)
- await waitUntilLiveStarts(servers[0].url, servers[0].accessToken, liveVideoId)
+ const command = await sendRTMPStreamInVideo(servers[0].url, servers[0].accessToken, liveVideoId, 'video_short2.webm')
+ await waitUntilLivePublished(servers[0].url, servers[0].accessToken, liveVideoId)
await waitJobs(servers)
await testVideoResolutions(liveVideoId, resolutions)
await stopFfmpeg(command)
+ await waitUntilLivePublished(servers[0].url, servers[0].accessToken, liveVideoId)
await waitJobs(servers)
+ const bitrateLimits = {
+ 720: 5000 * 1000, // 60FPS
+ 360: 1100 * 1000,
+ 240: 600 * 1000
+ }
+
for (const server of servers) {
const resVideo = await getVideo(server.url, liveVideoId)
const video: VideoDetails = resVideo.body
expect(video.files).to.have.lengthOf(0)
const hlsPlaylist = video.streamingPlaylists.find(s => s.type === VideoStreamingPlaylistType.HLS)
+ await makeRawRequest(hlsPlaylist.playlistUrl, HttpStatusCode.OK_200)
+ await makeRawRequest(hlsPlaylist.segmentsSha256Url, HttpStatusCode.OK_200)
expect(hlsPlaylist.files).to.have.lengthOf(resolutions.length)
const file = hlsPlaylist.files.find(f => f.resolution.id === resolution)
expect(file).to.exist
- expect(file.fps).to.equal(25)
expect(file.size).to.be.greaterThan(1)
- await makeRawRequest(file.torrentUrl, 200)
- await makeRawRequest(file.fileUrl, 200)
+ if (resolution >= 720) {
+ expect(file.fps).to.be.approximately(60, 2)
+ } else {
+ expect(file.fps).to.be.approximately(30, 2)
+ }
+
+ const filename = `${video.uuid}-${resolution}-fragmented.mp4`
+ const segmentPath = buildServerDirectory(servers[0], join('streaming-playlists', 'hls', video.uuid, filename))
+
+ const probe = await ffprobePromise(segmentPath)
+ const videoStream = await getVideoStreamFromFile(segmentPath, probe)
+
+ expect(probe.format.bit_rate).to.be.below(bitrateLimits[videoStream.height])
+
+ await makeRawRequest(file.torrentUrl, HttpStatusCode.OK_200)
+ await makeRawRequest(file.fileUrl, HttpStatusCode.OK_200)
}
}
})
liveVideoId = res.body.video.uuid
command = await sendRTMPStreamInVideo(servers[0].url, servers[0].accessToken, liveVideoId)
- await waitUntilLiveStarts(servers[0].url, servers[0].accessToken, liveVideoId)
+ await waitUntilLivePublished(servers[0].url, servers[0].accessToken, liveVideoId)
await waitJobs(servers)
})
}
const command = await sendRTMPStreamInVideo(servers[0].url, servers[0].accessToken, liveVideoUUID)
- await waitUntilLiveStarts(servers[0].url, servers[0].accessToken, liveVideoUUID)
+
+ for (const server of servers) {
+ await waitUntilLivePublished(server.url, server.accessToken, liveVideoUUID)
+ }
+
await waitJobs(servers)
for (const stateChanges of [ localStateChanges, remoteStateChanges ]) {
- expect(stateChanges).to.have.lengthOf(1)
- expect(stateChanges[0]).to.equal(VideoState.PUBLISHED)
+ expect(stateChanges).to.have.length.at.least(1)
+ expect(stateChanges[stateChanges.length - 1]).to.equal(VideoState.PUBLISHED)
}
await stopFfmpeg(command)
+
+ for (const server of servers) {
+ await waitUntilLiveEnded(server.url, server.accessToken, liveVideoUUID)
+ }
+
await waitJobs(servers)
for (const stateChanges of [ localStateChanges, remoteStateChanges ]) {
- expect(stateChanges).to.have.lengthOf(2)
- expect(stateChanges[1]).to.equal(VideoState.LIVE_ENDED)
+ expect(stateChanges).to.have.length.at.least(2)
+ expect(stateChanges[stateChanges.length - 1]).to.equal(VideoState.LIVE_ENDED)
}
})
socket.emit('subscribe', { videoId })
const command = await sendRTMPStreamInVideo(servers[0].url, servers[0].accessToken, liveVideoUUID)
- await waitUntilLiveStarts(servers[0].url, servers[0].accessToken, liveVideoUUID)
+ await waitUntilLivePublished(servers[0].url, servers[0].accessToken, liveVideoUUID)
await waitJobs(servers)
expect(stateChanges).to.have.lengthOf(1)
})
})
+ describe('After a server restart', function () {
+ let liveVideoId: string
+ let liveVideoReplayId: string
+
+ async function createLiveWrapper (saveReplay: boolean) {
+ const liveAttributes = {
+ name: 'live video',
+ channelId: servers[0].videoChannel.id,
+ privacy: VideoPrivacy.PUBLIC,
+ saveReplay
+ }
+
+ const res = await createLive(servers[0].url, servers[0].accessToken, liveAttributes)
+ return res.body.video.uuid
+ }
+
+ before(async function () {
+ this.timeout(120000)
+
+ liveVideoId = await createLiveWrapper(false)
+ liveVideoReplayId = await createLiveWrapper(true)
+
+ await Promise.all([
+ sendRTMPStreamInVideo(servers[0].url, servers[0].accessToken, liveVideoId),
+ sendRTMPStreamInVideo(servers[0].url, servers[0].accessToken, liveVideoReplayId)
+ ])
+
+ await Promise.all([
+ waitUntilLivePublished(servers[0].url, servers[0].accessToken, liveVideoId),
+ waitUntilLivePublished(servers[0].url, servers[0].accessToken, liveVideoReplayId)
+ ])
+
+ await waitUntilLiveSegmentGeneration(servers[0], liveVideoId, 0, 2)
+ await waitUntilLiveSegmentGeneration(servers[0], liveVideoReplayId, 0, 2)
+
+ await killallServers([ servers[0] ])
+ await reRunServer(servers[0])
+
+ await wait(5000)
+ })
+
+ it('Should cleanup lives', async function () {
+ this.timeout(60000)
+
+ await waitUntilLiveEnded(servers[0].url, servers[0].accessToken, liveVideoId)
+ })
+
+ it('Should save a live replay', async function () {
+ this.timeout(120000)
+
+ await waitUntilLivePublished(servers[0].url, servers[0].accessToken, liveVideoReplayId)
+ })
+ })
+
after(async function () {
await cleanupTests(servers)
})