aboutsummaryrefslogtreecommitdiffhomepage
path: root/shared
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2021-07-08 10:18:40 +0200
committerChocobozzz <me@florianbigard.com>2021-07-20 15:27:17 +0200
commit4f2199144e428c16460750305f737b890c1ac322 (patch)
treea19c5c0f254ab0b32d6c8838af33a1ba260e4877 /shared
parent2c27e70471120c92e0bc8c8114141fbb31ff98ac (diff)
downloadPeerTube-4f2199144e428c16460750305f737b890c1ac322.tar.gz
PeerTube-4f2199144e428c16460750305f737b890c1ac322.tar.zst
PeerTube-4f2199144e428c16460750305f737b890c1ac322.zip
Introduce live command
Diffstat (limited to 'shared')
-rw-r--r--shared/extra-utils/index.ts16
-rw-r--r--shared/extra-utils/requests/index.ts3
-rw-r--r--shared/extra-utils/server/servers.ts3
-rw-r--r--shared/extra-utils/shared/abstract-command.ts32
-rw-r--r--shared/extra-utils/users/index.ts1
-rw-r--r--shared/extra-utils/videos/index.ts13
-rw-r--r--shared/extra-utils/videos/live-command.ts156
-rw-r--r--shared/extra-utils/videos/live.ts139
8 files changed, 214 insertions, 149 deletions
diff --git a/shared/extra-utils/index.ts b/shared/extra-utils/index.ts
index 68900af26..4b3636d06 100644
--- a/shared/extra-utils/index.ts
+++ b/shared/extra-utils/index.ts
@@ -7,21 +7,9 @@ export * from './miscs'
7export * from './mock-servers' 7export * from './mock-servers'
8export * from './moderation' 8export * from './moderation'
9export * from './overviews' 9export * from './overviews'
10export * from './requests'
10export * from './search' 11export * from './search'
11export * from './server' 12export * from './server'
12export * from './socket' 13export * from './socket'
13export * from './users' 14export * from './users'
14 15export * from './videos'
15export * from './requests/check-api-params'
16export * from './requests/requests'
17
18export * from './videos/live'
19export * from './videos/services'
20export * from './videos/video-blacklist'
21export * from './videos/video-captions'
22export * from './videos/video-change-ownership'
23export * from './videos/video-channels'
24export * from './videos/video-comments'
25export * from './videos/video-playlists'
26export * from './videos/video-streaming-playlists'
27export * from './videos/videos'
diff --git a/shared/extra-utils/requests/index.ts b/shared/extra-utils/requests/index.ts
new file mode 100644
index 000000000..501163f92
--- /dev/null
+++ b/shared/extra-utils/requests/index.ts
@@ -0,0 +1,3 @@
1// Don't include activitypub that import stuff from server
2export * from './check-api-params'
3export * from './requests'
diff --git a/shared/extra-utils/server/servers.ts b/shared/extra-utils/server/servers.ts
index 57b37728a..eca0689aa 100644
--- a/shared/extra-utils/server/servers.ts
+++ b/shared/extra-utils/server/servers.ts
@@ -18,6 +18,7 @@ import { makeGetRequest } from '../requests/requests'
18import { SearchCommand } from '../search' 18import { SearchCommand } from '../search'
19import { SocketIOCommand } from '../socket' 19import { SocketIOCommand } from '../socket'
20import { AccountsCommand, BlocklistCommand, SubscriptionsCommand } from '../users' 20import { AccountsCommand, BlocklistCommand, SubscriptionsCommand } from '../users'
21import { LiveCommand } from '../videos'
21import { ConfigCommand } from './config-command' 22import { ConfigCommand } from './config-command'
22import { ContactFormCommand } from './contact-form-command' 23import { ContactFormCommand } from './contact-form-command'
23import { DebugCommand } from './debug-command' 24import { DebugCommand } from './debug-command'
@@ -99,6 +100,7 @@ interface ServerInfo {
99 accountsCommand?: AccountsCommand 100 accountsCommand?: AccountsCommand
100 blocklistCommand?: BlocklistCommand 101 blocklistCommand?: BlocklistCommand
101 subscriptionsCommand?: SubscriptionsCommand 102 subscriptionsCommand?: SubscriptionsCommand
103 liveCommand?: LiveCommand
102} 104}
103 105
104function parallelTests () { 106function parallelTests () {
@@ -324,6 +326,7 @@ async function runServer (server: ServerInfo, configOverrideArg?: any, args = []
324 server.accountsCommand = new AccountsCommand(server) 326 server.accountsCommand = new AccountsCommand(server)
325 server.blocklistCommand = new BlocklistCommand(server) 327 server.blocklistCommand = new BlocklistCommand(server)
326 server.subscriptionsCommand = new SubscriptionsCommand(server) 328 server.subscriptionsCommand = new SubscriptionsCommand(server)
329 server.liveCommand = new LiveCommand(server)
327 330
328 res(server) 331 res(server)
329 }) 332 })
diff --git a/shared/extra-utils/shared/abstract-command.ts b/shared/extra-utils/shared/abstract-command.ts
index dd4598a91..38129d559 100644
--- a/shared/extra-utils/shared/abstract-command.ts
+++ b/shared/extra-utils/shared/abstract-command.ts
@@ -1,5 +1,5 @@
1import { HttpStatusCode } from '@shared/core-utils' 1import { HttpStatusCode } from '@shared/core-utils'
2import { makeDeleteRequest, makeGetRequest, makePostBodyRequest, makePutBodyRequest, unwrapBody, unwrapText } from '../requests/requests' 2import { makeDeleteRequest, makeGetRequest, makePostBodyRequest, makePutBodyRequest, makeUploadRequest, unwrapBody, unwrapText } from '../requests/requests'
3import { ServerInfo } from '../server/servers' 3import { ServerInfo } from '../server/servers'
4 4
5export interface OverrideCommandOptions { 5export interface OverrideCommandOptions {
@@ -86,6 +86,36 @@ abstract class AbstractCommand {
86 }) 86 })
87 } 87 }
88 88
89 protected postUploadRequest (options: CommonCommandOptions & {
90 fields?: { [ fieldName: string ]: any }
91 attaches?: any
92 }) {
93 const { fields, attaches } = options
94
95 return makeUploadRequest({
96 ...this.buildCommonRequestOptions(options),
97
98 method: 'POST',
99 fields,
100 attaches
101 })
102 }
103
104 protected putUploadRequest (options: CommonCommandOptions & {
105 fields?: { [ fieldName: string ]: any }
106 attaches?: any
107 }) {
108 const { fields, attaches } = options
109
110 return makeUploadRequest({
111 ...this.buildCommonRequestOptions(options),
112
113 method: 'PUT',
114 fields,
115 attaches
116 })
117 }
118
89 private buildCommonRequestOptions (options: CommonCommandOptions) { 119 private buildCommonRequestOptions (options: CommonCommandOptions) {
90 const { token, expectedStatus, defaultExpectedStatus, path } = options 120 const { token, expectedStatus, defaultExpectedStatus, path } = options
91 121
diff --git a/shared/extra-utils/users/index.ts b/shared/extra-utils/users/index.ts
index 94ad37242..9f760d7fd 100644
--- a/shared/extra-utils/users/index.ts
+++ b/shared/extra-utils/users/index.ts
@@ -1,7 +1,6 @@
1export * from './accounts-command' 1export * from './accounts-command'
2export * from './accounts' 2export * from './accounts'
3export * from './blocklist-command' 3export * from './blocklist-command'
4
5export * from './login' 4export * from './login'
6export * from './user-notifications' 5export * from './user-notifications'
7export * from './subscriptions-command' 6export * from './subscriptions-command'
diff --git a/shared/extra-utils/videos/index.ts b/shared/extra-utils/videos/index.ts
new file mode 100644
index 000000000..c9c884285
--- /dev/null
+++ b/shared/extra-utils/videos/index.ts
@@ -0,0 +1,13 @@
1export * from './live-command'
2export * from './live'
3export * from './services'
4export * from './video-blacklist'
5export * from './video-captions'
6export * from './video-change-ownership'
7export * from './video-channels'
8export * from './video-comments'
9export * from './video-history'
10export * from './video-imports'
11export * from './video-playlists'
12export * from './video-streaming-playlists'
13export * from './videos'
diff --git a/shared/extra-utils/videos/live-command.ts b/shared/extra-utils/videos/live-command.ts
new file mode 100644
index 000000000..55811b8ba
--- /dev/null
+++ b/shared/extra-utils/videos/live-command.ts
@@ -0,0 +1,156 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { readdir } from 'fs-extra'
4import { omit } from 'lodash'
5import { join } from 'path'
6import { LiveVideo, LiveVideoCreate, LiveVideoUpdate, VideoCreateResult, VideoDetails, VideoState } from '@shared/models'
7import { HttpStatusCode } from '../../core-utils/miscs/http-error-codes'
8import { buildServerDirectory, wait } from '../miscs/miscs'
9import { unwrapBody } from '../requests'
10import { waitUntilLog } from '../server/servers'
11import { AbstractCommand, OverrideCommandOptions } from '../shared'
12import { sendRTMPStream, testFfmpegStreamError } from './live'
13import { getVideoWithToken } from './videos'
14
15export class LiveCommand extends AbstractCommand {
16
17 getLive (options: OverrideCommandOptions & {
18 videoId: number | string
19 }) {
20 const path = '/api/v1/videos/live'
21
22 return this.getRequestBody<LiveVideo>({
23 ...options,
24
25 path: path + '/' + options.videoId,
26 defaultExpectedStatus: HttpStatusCode.OK_200
27 })
28 }
29
30 updateLive (options: OverrideCommandOptions & {
31 videoId: number | string
32 fields: LiveVideoUpdate
33 }) {
34 const { videoId, fields } = options
35 const path = '/api/v1/videos/live'
36
37 return this.putBodyRequest({
38 ...options,
39
40 path: path + '/' + videoId,
41 fields,
42 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
43 })
44 }
45
46 async createLive (options: OverrideCommandOptions & {
47 fields: LiveVideoCreate
48 }) {
49 const { fields } = options
50 const path = '/api/v1/videos/live'
51
52 const attaches: any = {}
53 if (fields.thumbnailfile) attaches.thumbnailfile = fields.thumbnailfile
54 if (fields.previewfile) attaches.previewfile = fields.previewfile
55
56 const body = await unwrapBody<{ video: VideoCreateResult }>(this.postUploadRequest({
57 ...options,
58
59 path,
60 attaches,
61 fields: omit(fields, 'thumbnailfile', 'previewfile'),
62 defaultExpectedStatus: HttpStatusCode.OK_200
63 }))
64
65 return body.video
66 }
67
68 async sendRTMPStreamInVideo (options: OverrideCommandOptions & {
69 videoId: number | string
70 fixtureName?: string
71 }) {
72 const { videoId, fixtureName } = options
73 const videoLive = await this.getLive({ videoId })
74
75 return sendRTMPStream(videoLive.rtmpUrl, videoLive.streamKey, fixtureName)
76 }
77
78 async runAndTestFfmpegStreamError (options: OverrideCommandOptions & {
79 videoId: number | string
80 shouldHaveError: boolean
81 }) {
82 const command = await this.sendRTMPStreamInVideo(options)
83
84 return testFfmpegStreamError(command, options.shouldHaveError)
85 }
86
87 waitUntilLivePublished (options: OverrideCommandOptions & {
88 videoId: number | string
89 }) {
90 const { videoId } = options
91 return this.waitUntilLiveState({ videoId, state: VideoState.PUBLISHED })
92 }
93
94 waitUntilLiveWaiting (options: OverrideCommandOptions & {
95 videoId: number | string
96 }) {
97 const { videoId } = options
98 return this.waitUntilLiveState({ videoId, state: VideoState.WAITING_FOR_LIVE })
99 }
100
101 waitUntilLiveEnded (options: OverrideCommandOptions & {
102 videoId: number | string
103 }) {
104 const { videoId } = options
105 return this.waitUntilLiveState({ videoId, state: VideoState.LIVE_ENDED })
106 }
107
108 waitUntilLiveSegmentGeneration (options: OverrideCommandOptions & {
109 videoUUID: string
110 resolution: number
111 segment: number
112 }) {
113 const { resolution, segment, videoUUID } = options
114 const segmentName = `${resolution}-00000${segment}.ts`
115
116 return waitUntilLog(this.server, `${videoUUID}/${segmentName}`, 2, false)
117 }
118
119 async waitUntilLiveSaved (options: OverrideCommandOptions & {
120 videoId: number | string
121 }) {
122 let video: VideoDetails
123
124 do {
125 const res = await getVideoWithToken(this.server.url, options.token ?? this.server.accessToken, options.videoId)
126 video = res.body
127
128 await wait(500)
129 } while (video.isLive === true && video.state.id !== VideoState.PUBLISHED)
130 }
131
132 async getPlaylistsCount (options: OverrideCommandOptions & {
133 videoUUID: string
134 }) {
135 const basePath = buildServerDirectory(this.server, 'streaming-playlists')
136 const hlsPath = join(basePath, 'hls', options.videoUUID)
137
138 const files = await readdir(hlsPath)
139
140 return files.filter(f => f.endsWith('.m3u8')).length
141 }
142
143 private async waitUntilLiveState (options: OverrideCommandOptions & {
144 videoId: number | string
145 state: VideoState
146 }) {
147 let video: VideoDetails
148
149 do {
150 const res = await getVideoWithToken(this.server.url, options.token ?? this.server.accessToken, options.videoId)
151 video = res.body
152
153 await wait(500)
154 } while (video.state.id !== options.state)
155 }
156}
diff --git a/shared/extra-utils/videos/live.ts b/shared/extra-utils/videos/live.ts
index c0384769b..285a39c7e 100644
--- a/shared/extra-utils/videos/live.ts
+++ b/shared/extra-utils/videos/live.ts
@@ -3,69 +3,9 @@
3import { expect } from 'chai' 3import { expect } from 'chai'
4import * as ffmpeg from 'fluent-ffmpeg' 4import * as ffmpeg from 'fluent-ffmpeg'
5import { pathExists, readdir } from 'fs-extra' 5import { pathExists, readdir } from 'fs-extra'
6import { omit } from 'lodash'
7import { join } from 'path' 6import { join } from 'path'
8import { LiveVideo, LiveVideoCreate, LiveVideoUpdate, VideoDetails, VideoState } from '@shared/models'
9import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
10import { buildAbsoluteFixturePath, buildServerDirectory, wait } from '../miscs/miscs' 7import { buildAbsoluteFixturePath, buildServerDirectory, wait } from '../miscs/miscs'
11import { makeGetRequest, makePutBodyRequest, makeUploadRequest } from '../requests/requests' 8import { ServerInfo } from '../server/servers'
12import { ServerInfo, waitUntilLog } from '../server/servers'
13import { getVideoWithToken } from './videos'
14
15function getLive (url: string, token: string, videoId: number | string, statusCodeExpected = HttpStatusCode.OK_200) {
16 const path = '/api/v1/videos/live'
17
18 return makeGetRequest({
19 url,
20 token,
21 path: path + '/' + videoId,
22 statusCodeExpected
23 })
24}
25
26function updateLive (
27 url: string,
28 token: string,
29 videoId: number | string,
30 fields: LiveVideoUpdate,
31 statusCodeExpected = HttpStatusCode.NO_CONTENT_204
32) {
33 const path = '/api/v1/videos/live'
34
35 return makePutBodyRequest({
36 url,
37 token,
38 path: path + '/' + videoId,
39 fields,
40 statusCodeExpected
41 })
42}
43
44function createLive (url: string, token: string, fields: LiveVideoCreate, statusCodeExpected = HttpStatusCode.OK_200) {
45 const path = '/api/v1/videos/live'
46
47 const attaches: any = {}
48 if (fields.thumbnailfile) attaches.thumbnailfile = fields.thumbnailfile
49 if (fields.previewfile) attaches.previewfile = fields.previewfile
50
51 const updatedFields = omit(fields, 'thumbnailfile', 'previewfile')
52
53 return makeUploadRequest({
54 url,
55 path,
56 token,
57 attaches,
58 fields: updatedFields,
59 statusCodeExpected
60 })
61}
62
63async function sendRTMPStreamInVideo (url: string, token: string, videoId: number | string, fixtureName?: string) {
64 const res = await getLive(url, token, videoId)
65 const videoLive = res.body as LiveVideo
66
67 return sendRTMPStream(videoLive.rtmpUrl, videoLive.streamKey, fixtureName)
68}
69 9
70function sendRTMPStream (rtmpBaseUrl: string, streamKey: string, fixtureName = 'video_short.mp4') { 10function sendRTMPStream (rtmpBaseUrl: string, streamKey: string, fixtureName = 'video_short.mp4') {
71 const fixture = buildAbsoluteFixturePath(fixtureName) 11 const fixture = buildAbsoluteFixturePath(fixtureName)
@@ -109,12 +49,6 @@ function waitFfmpegUntilError (command: ffmpeg.FfmpegCommand, successAfterMS = 1
109 }) 49 })
110} 50}
111 51
112async function runAndTestFfmpegStreamError (url: string, token: string, videoId: number | string, shouldHaveError: boolean) {
113 const command = await sendRTMPStreamInVideo(url, token, videoId)
114
115 return testFfmpegStreamError(command, shouldHaveError)
116}
117
118async function testFfmpegStreamError (command: ffmpeg.FfmpegCommand, shouldHaveError: boolean) { 52async function testFfmpegStreamError (command: ffmpeg.FfmpegCommand, shouldHaveError: boolean) {
119 let error: Error 53 let error: Error
120 54
@@ -136,48 +70,9 @@ async function stopFfmpeg (command: ffmpeg.FfmpegCommand) {
136 await wait(500) 70 await wait(500)
137} 71}
138 72
139function waitUntilLivePublished (url: string, token: string, videoId: number | string) {
140 return waitUntilLiveState(url, token, videoId, VideoState.PUBLISHED)
141}
142
143function waitUntilLiveWaiting (url: string, token: string, videoId: number | string) {
144 return waitUntilLiveState(url, token, videoId, VideoState.WAITING_FOR_LIVE)
145}
146
147function waitUntilLiveEnded (url: string, token: string, videoId: number | string) {
148 return waitUntilLiveState(url, token, videoId, VideoState.LIVE_ENDED)
149}
150
151function waitUntilLiveSegmentGeneration (server: ServerInfo, videoUUID: string, resolutionNum: number, segmentNum: number) {
152 const segmentName = `${resolutionNum}-00000${segmentNum}.ts`
153 return waitUntilLog(server, `${videoUUID}/${segmentName}`, 2, false)
154}
155
156async function waitUntilLiveState (url: string, token: string, videoId: number | string, state: VideoState) {
157 let video: VideoDetails
158
159 do {
160 const res = await getVideoWithToken(url, token, videoId)
161 video = res.body
162
163 await wait(500)
164 } while (video.state.id !== state)
165}
166
167async function waitUntilLiveSaved (url: string, token: string, videoId: number | string) {
168 let video: VideoDetails
169
170 do {
171 const res = await getVideoWithToken(url, token, videoId)
172 video = res.body
173
174 await wait(500)
175 } while (video.isLive === true && video.state.id !== VideoState.PUBLISHED)
176}
177
178async function waitUntilLivePublishedOnAllServers (servers: ServerInfo[], videoId: string) { 73async function waitUntilLivePublishedOnAllServers (servers: ServerInfo[], videoId: string) {
179 for (const server of servers) { 74 for (const server of servers) {
180 await waitUntilLivePublished(server.url, server.accessToken, videoId) 75 await server.liveCommand.waitUntilLivePublished({ videoId })
181 } 76 }
182} 77}
183 78
@@ -206,33 +101,11 @@ async function checkLiveCleanup (server: ServerInfo, videoUUID: string, resoluti
206 expect(files).to.contain('segments-sha256.json') 101 expect(files).to.contain('segments-sha256.json')
207} 102}
208 103
209async function getPlaylistsCount (server: ServerInfo, videoUUID: string) {
210 const basePath = buildServerDirectory(server, 'streaming-playlists')
211 const hlsPath = join(basePath, 'hls', videoUUID)
212
213 const files = await readdir(hlsPath)
214
215 return files.filter(f => f.endsWith('.m3u8')).length
216}
217
218// ---------------------------------------------------------------------------
219
220export { 104export {
221 getLive, 105 sendRTMPStream,
222 getPlaylistsCount,
223 waitUntilLiveSaved,
224 waitUntilLivePublished,
225 updateLive,
226 createLive,
227 runAndTestFfmpegStreamError,
228 checkLiveCleanup,
229 waitUntilLiveSegmentGeneration,
230 stopFfmpeg,
231 waitUntilLiveWaiting,
232 sendRTMPStreamInVideo,
233 waitUntilLiveEnded,
234 waitFfmpegUntilError, 106 waitFfmpegUntilError,
107 testFfmpegStreamError,
108 stopFfmpeg,
235 waitUntilLivePublishedOnAllServers, 109 waitUntilLivePublishedOnAllServers,
236 sendRTMPStream, 110 checkLiveCleanup
237 testFfmpegStreamError
238} 111}