]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/tests/shared/live.ts
Fix P2P with object storage
[github/Chocobozzz/PeerTube.git] / server / tests / shared / live.ts
1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3 import { expect } from 'chai'
4 import { pathExists, readdir } from 'fs-extra'
5 import { join } from 'path'
6 import { LiveVideo, VideoStreamingPlaylistType } from '@shared/models'
7 import { ObjectStorageCommand, PeerTubeServer } from '@shared/server-commands'
8 import { checkLiveSegmentHash, checkResolutionsInMasterPlaylist } from './streaming-playlists'
9 import { sha1 } from '@shared/extra-utils'
10
11 async function checkLiveCleanup (options: {
12 server: PeerTubeServer
13 videoUUID: string
14 permanent: boolean
15 savedResolutions?: number[]
16 }) {
17 const { server, videoUUID, permanent, savedResolutions = [] } = options
18
19 const basePath = server.servers.buildDirectory('streaming-playlists')
20 const hlsPath = join(basePath, 'hls', videoUUID)
21
22 if (permanent) {
23 if (!await pathExists(hlsPath)) return
24
25 const files = await readdir(hlsPath)
26 expect(files).to.have.lengthOf(0)
27 return
28 }
29
30 if (savedResolutions.length === 0) {
31 return checkUnsavedLiveCleanup(server, videoUUID, hlsPath)
32 }
33
34 return checkSavedLiveCleanup(hlsPath, savedResolutions)
35 }
36
37 // ---------------------------------------------------------------------------
38
39 async function testVideoResolutions (options: {
40 originServer: PeerTubeServer
41 servers: PeerTubeServer[]
42 liveVideoId: string
43 resolutions: number[]
44 transcoded: boolean
45 objectStorage: boolean
46 }) {
47 const { originServer, servers, liveVideoId, resolutions, transcoded, objectStorage } = options
48
49 for (const server of servers) {
50 const { data } = await server.videos.list()
51 expect(data.find(v => v.uuid === liveVideoId)).to.exist
52
53 const video = await server.videos.get({ id: liveVideoId })
54 expect(video.streamingPlaylists).to.have.lengthOf(1)
55
56 const hlsPlaylist = video.streamingPlaylists.find(s => s.type === VideoStreamingPlaylistType.HLS)
57 expect(hlsPlaylist).to.exist
58 expect(hlsPlaylist.files).to.have.lengthOf(0) // Only fragmented mp4 files are displayed
59
60 await checkResolutionsInMasterPlaylist({
61 server,
62 playlistUrl: hlsPlaylist.playlistUrl,
63 resolutions,
64 transcoded,
65 withRetry: objectStorage
66 })
67
68 if (objectStorage) {
69 expect(hlsPlaylist.playlistUrl).to.contain(ObjectStorageCommand.getMockPlaylistBaseUrl())
70 }
71
72 for (let i = 0; i < resolutions.length; i++) {
73 const segmentNum = 3
74 const segmentName = `${i}-00000${segmentNum}.ts`
75 await originServer.live.waitUntilSegmentGeneration({
76 server: originServer,
77 videoUUID: video.uuid,
78 playlistNumber: i,
79 segment: segmentNum,
80 objectStorage
81 })
82
83 const baseUrl = objectStorage
84 ? ObjectStorageCommand.getMockPlaylistBaseUrl() + 'hls'
85 : originServer.url + '/static/streaming-playlists/hls'
86
87 if (objectStorage) {
88 expect(hlsPlaylist.segmentsSha256Url).to.contain(ObjectStorageCommand.getMockPlaylistBaseUrl())
89 }
90
91 const subPlaylist = await originServer.streamingPlaylists.get({
92 url: `${baseUrl}/${video.uuid}/${i}.m3u8`,
93 withRetry: objectStorage // With object storage, the request may fail because of inconsistent data in S3
94 })
95
96 expect(subPlaylist).to.contain(segmentName)
97
98 await checkLiveSegmentHash({
99 server,
100 baseUrlSegment: baseUrl,
101 videoUUID: video.uuid,
102 segmentName,
103 hlsPlaylist
104 })
105
106 if (originServer.internalServerNumber === server.internalServerNumber) {
107 const infohash = sha1(`${2 + hlsPlaylist.playlistUrl}+V${i}`)
108 const dbInfohashes = await originServer.sql.getPlaylistInfohash(hlsPlaylist.id)
109
110 expect(dbInfohashes).to.include(infohash)
111 }
112 }
113 }
114 }
115
116 // ---------------------------------------------------------------------------
117
118 export {
119 checkLiveCleanup,
120 testVideoResolutions
121 }
122
123 // ---------------------------------------------------------------------------
124
125 async function checkSavedLiveCleanup (hlsPath: string, savedResolutions: number[] = []) {
126 const files = await readdir(hlsPath)
127
128 // fragmented file and playlist per resolution + master playlist + segments sha256 json file
129 expect(files).to.have.lengthOf(savedResolutions.length * 2 + 2)
130
131 for (const resolution of savedResolutions) {
132 const fragmentedFile = files.find(f => f.endsWith(`-${resolution}-fragmented.mp4`))
133 expect(fragmentedFile).to.exist
134
135 const playlistFile = files.find(f => f.endsWith(`${resolution}.m3u8`))
136 expect(playlistFile).to.exist
137 }
138
139 const masterPlaylistFile = files.find(f => f.endsWith('-master.m3u8'))
140 expect(masterPlaylistFile).to.exist
141
142 const shaFile = files.find(f => f.endsWith('-segments-sha256.json'))
143 expect(shaFile).to.exist
144 }
145
146 async function checkUnsavedLiveCleanup (server: PeerTubeServer, videoUUID: string, hlsPath: string) {
147 let live: LiveVideo
148
149 try {
150 live = await server.live.get({ videoId: videoUUID })
151 } catch {}
152
153 if (live?.permanentLive) {
154 expect(await pathExists(hlsPath)).to.be.true
155
156 const hlsFiles = await readdir(hlsPath)
157 expect(hlsFiles).to.have.lengthOf(1) // Only replays directory
158
159 const replayDir = join(hlsPath, 'replay')
160 expect(await pathExists(replayDir)).to.be.true
161
162 const replayFiles = await readdir(join(hlsPath, 'replay'))
163 expect(replayFiles).to.have.lengthOf(0)
164
165 return
166 }
167
168 expect(await pathExists(hlsPath)).to.be.false
169 }