diff options
author | Chocobozzz <me@florianbigard.com> | 2021-12-17 09:29:23 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2021-12-17 09:29:23 +0100 |
commit | bf54587a3e2ad9c2c186828f2a5682b91ee2cc00 (patch) | |
tree | 54b40aaf01bae210632473285c3c7571d51e4f89 /shared/server-commands/videos/live.ts | |
parent | 6b5f72beda96d8b7e4d6329c4001827334de27dd (diff) | |
download | PeerTube-bf54587a3e2ad9c2c186828f2a5682b91ee2cc00.tar.gz PeerTube-bf54587a3e2ad9c2c186828f2a5682b91ee2cc00.tar.zst PeerTube-bf54587a3e2ad9c2c186828f2a5682b91ee2cc00.zip |
shared/ typescript types dir server-commands
Diffstat (limited to 'shared/server-commands/videos/live.ts')
-rw-r--r-- | shared/server-commands/videos/live.ts | 137 |
1 files changed, 137 insertions, 0 deletions
diff --git a/shared/server-commands/videos/live.ts b/shared/server-commands/videos/live.ts new file mode 100644 index 000000000..d3665bc90 --- /dev/null +++ b/shared/server-commands/videos/live.ts | |||
@@ -0,0 +1,137 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import { expect } from 'chai' | ||
4 | import ffmpeg, { FfmpegCommand } from 'fluent-ffmpeg' | ||
5 | import { pathExists, readdir } from 'fs-extra' | ||
6 | import { join } from 'path' | ||
7 | import { buildAbsoluteFixturePath, wait } from '../miscs' | ||
8 | import { PeerTubeServer } from '../server/server' | ||
9 | |||
10 | function sendRTMPStream (options: { | ||
11 | rtmpBaseUrl: string | ||
12 | streamKey: string | ||
13 | fixtureName?: string // default video_short.mp4 | ||
14 | copyCodecs?: boolean // default false | ||
15 | }) { | ||
16 | const { rtmpBaseUrl, streamKey, fixtureName = 'video_short.mp4', copyCodecs = false } = options | ||
17 | |||
18 | const fixture = buildAbsoluteFixturePath(fixtureName) | ||
19 | |||
20 | const command = ffmpeg(fixture) | ||
21 | command.inputOption('-stream_loop -1') | ||
22 | command.inputOption('-re') | ||
23 | |||
24 | if (copyCodecs) { | ||
25 | command.outputOption('-c copy') | ||
26 | } else { | ||
27 | command.outputOption('-c:v libx264') | ||
28 | command.outputOption('-g 50') | ||
29 | command.outputOption('-keyint_min 2') | ||
30 | command.outputOption('-r 60') | ||
31 | } | ||
32 | |||
33 | command.outputOption('-f flv') | ||
34 | |||
35 | const rtmpUrl = rtmpBaseUrl + '/' + streamKey | ||
36 | command.output(rtmpUrl) | ||
37 | |||
38 | command.on('error', err => { | ||
39 | if (err?.message?.includes('Exiting normally')) return | ||
40 | |||
41 | if (process.env.DEBUG) console.error(err) | ||
42 | }) | ||
43 | |||
44 | if (process.env.DEBUG) { | ||
45 | command.on('stderr', data => console.log(data)) | ||
46 | } | ||
47 | |||
48 | command.run() | ||
49 | |||
50 | return command | ||
51 | } | ||
52 | |||
53 | function waitFfmpegUntilError (command: FfmpegCommand, successAfterMS = 10000) { | ||
54 | return new Promise<void>((res, rej) => { | ||
55 | command.on('error', err => { | ||
56 | return rej(err) | ||
57 | }) | ||
58 | |||
59 | setTimeout(() => { | ||
60 | res() | ||
61 | }, successAfterMS) | ||
62 | }) | ||
63 | } | ||
64 | |||
65 | async function testFfmpegStreamError (command: FfmpegCommand, shouldHaveError: boolean) { | ||
66 | let error: Error | ||
67 | |||
68 | try { | ||
69 | await waitFfmpegUntilError(command, 35000) | ||
70 | } catch (err) { | ||
71 | error = err | ||
72 | } | ||
73 | |||
74 | await stopFfmpeg(command) | ||
75 | |||
76 | if (shouldHaveError && !error) throw new Error('Ffmpeg did not have an error') | ||
77 | if (!shouldHaveError && error) throw error | ||
78 | } | ||
79 | |||
80 | async function stopFfmpeg (command: FfmpegCommand) { | ||
81 | command.kill('SIGINT') | ||
82 | |||
83 | await wait(500) | ||
84 | } | ||
85 | |||
86 | async function waitUntilLivePublishedOnAllServers (servers: PeerTubeServer[], videoId: string) { | ||
87 | for (const server of servers) { | ||
88 | await server.live.waitUntilPublished({ videoId }) | ||
89 | } | ||
90 | } | ||
91 | |||
92 | async function waitUntilLiveSavedOnAllServers (servers: PeerTubeServer[], videoId: string) { | ||
93 | for (const server of servers) { | ||
94 | await server.live.waitUntilSaved({ videoId }) | ||
95 | } | ||
96 | } | ||
97 | |||
98 | async function checkLiveCleanupAfterSave (server: PeerTubeServer, videoUUID: string, resolutions: number[] = []) { | ||
99 | const basePath = server.servers.buildDirectory('streaming-playlists') | ||
100 | const hlsPath = join(basePath, 'hls', videoUUID) | ||
101 | |||
102 | if (resolutions.length === 0) { | ||
103 | const result = await pathExists(hlsPath) | ||
104 | expect(result).to.be.false | ||
105 | |||
106 | return | ||
107 | } | ||
108 | |||
109 | const files = await readdir(hlsPath) | ||
110 | |||
111 | // fragmented file and playlist per resolution + master playlist + segments sha256 json file | ||
112 | expect(files).to.have.lengthOf(resolutions.length * 2 + 2) | ||
113 | |||
114 | for (const resolution of resolutions) { | ||
115 | const fragmentedFile = files.find(f => f.endsWith(`-${resolution}-fragmented.mp4`)) | ||
116 | expect(fragmentedFile).to.exist | ||
117 | |||
118 | const playlistFile = files.find(f => f.endsWith(`${resolution}.m3u8`)) | ||
119 | expect(playlistFile).to.exist | ||
120 | } | ||
121 | |||
122 | const masterPlaylistFile = files.find(f => f.endsWith('-master.m3u8')) | ||
123 | expect(masterPlaylistFile).to.exist | ||
124 | |||
125 | const shaFile = files.find(f => f.endsWith('-segments-sha256.json')) | ||
126 | expect(shaFile).to.exist | ||
127 | } | ||
128 | |||
129 | export { | ||
130 | sendRTMPStream, | ||
131 | waitFfmpegUntilError, | ||
132 | testFfmpegStreamError, | ||
133 | stopFfmpeg, | ||
134 | waitUntilLivePublishedOnAllServers, | ||
135 | waitUntilLiveSavedOnAllServers, | ||
136 | checkLiveCleanupAfterSave | ||
137 | } | ||