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