]>
Commit | Line | Data |
---|---|---|
4f219914 C |
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | ||
3 | import { readdir } from 'fs-extra' | |
4 | import { omit } from 'lodash' | |
5 | import { join } from 'path' | |
c55e3d72 | 6 | import { wait } from '@shared/core-utils' |
26e3e98f C |
7 | import { |
8 | HttpStatusCode, | |
9 | LiveVideo, | |
10 | LiveVideoCreate, | |
11 | LiveVideoSession, | |
12 | LiveVideoUpdate, | |
13 | ResultList, | |
14 | VideoCreateResult, | |
15 | VideoDetails, | |
16 | VideoState | |
17 | } from '@shared/models' | |
4f219914 | 18 | import { unwrapBody } from '../requests' |
4f219914 C |
19 | import { AbstractCommand, OverrideCommandOptions } from '../shared' |
20 | import { sendRTMPStream, testFfmpegStreamError } from './live' | |
4f219914 C |
21 | |
22 | export class LiveCommand extends AbstractCommand { | |
23 | ||
04aed767 | 24 | get (options: OverrideCommandOptions & { |
4f219914 C |
25 | videoId: number | string |
26 | }) { | |
27 | const path = '/api/v1/videos/live' | |
28 | ||
29 | return this.getRequestBody<LiveVideo>({ | |
30 | ...options, | |
31 | ||
32 | path: path + '/' + options.videoId, | |
a1637fa1 | 33 | implicitToken: true, |
4f219914 C |
34 | defaultExpectedStatus: HttpStatusCode.OK_200 |
35 | }) | |
36 | } | |
37 | ||
26e3e98f C |
38 | listSessions (options: OverrideCommandOptions & { |
39 | videoId: number | string | |
40 | }) { | |
41 | const path = `/api/v1/videos/live/${options.videoId}/sessions` | |
42 | ||
43 | return this.getRequestBody<ResultList<LiveVideoSession>>({ | |
44 | ...options, | |
45 | ||
46 | path, | |
47 | implicitToken: true, | |
48 | defaultExpectedStatus: HttpStatusCode.OK_200 | |
49 | }) | |
50 | } | |
51 | ||
52 | async findLatestSession (options: OverrideCommandOptions & { | |
53 | videoId: number | string | |
54 | }) { | |
55 | const { data: sessions } = await this.listSessions(options) | |
56 | ||
57 | return sessions[sessions.length - 1] | |
58 | } | |
59 | ||
60 | getReplaySession (options: OverrideCommandOptions & { | |
61 | videoId: number | string | |
62 | }) { | |
63 | const path = `/api/v1/videos/${options.videoId}/live-session` | |
64 | ||
65 | return this.getRequestBody<LiveVideoSession>({ | |
66 | ...options, | |
67 | ||
68 | path, | |
69 | implicitToken: true, | |
70 | defaultExpectedStatus: HttpStatusCode.OK_200 | |
71 | }) | |
72 | } | |
73 | ||
04aed767 | 74 | update (options: OverrideCommandOptions & { |
4f219914 C |
75 | videoId: number | string |
76 | fields: LiveVideoUpdate | |
77 | }) { | |
78 | const { videoId, fields } = options | |
79 | const path = '/api/v1/videos/live' | |
80 | ||
81 | return this.putBodyRequest({ | |
82 | ...options, | |
83 | ||
84 | path: path + '/' + videoId, | |
85 | fields, | |
a1637fa1 | 86 | implicitToken: true, |
4f219914 C |
87 | defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 |
88 | }) | |
89 | } | |
90 | ||
04aed767 | 91 | async create (options: OverrideCommandOptions & { |
4f219914 C |
92 | fields: LiveVideoCreate |
93 | }) { | |
94 | const { fields } = options | |
95 | const path = '/api/v1/videos/live' | |
96 | ||
97 | const attaches: any = {} | |
98 | if (fields.thumbnailfile) attaches.thumbnailfile = fields.thumbnailfile | |
99 | if (fields.previewfile) attaches.previewfile = fields.previewfile | |
100 | ||
101 | const body = await unwrapBody<{ video: VideoCreateResult }>(this.postUploadRequest({ | |
102 | ...options, | |
103 | ||
104 | path, | |
105 | attaches, | |
106 | fields: omit(fields, 'thumbnailfile', 'previewfile'), | |
a1637fa1 | 107 | implicitToken: true, |
4f219914 C |
108 | defaultExpectedStatus: HttpStatusCode.OK_200 |
109 | })) | |
110 | ||
111 | return body.video | |
112 | } | |
113 | ||
114 | async sendRTMPStreamInVideo (options: OverrideCommandOptions & { | |
115 | videoId: number | string | |
116 | fixtureName?: string | |
c826f34a | 117 | copyCodecs?: boolean |
4f219914 | 118 | }) { |
c826f34a | 119 | const { videoId, fixtureName, copyCodecs } = options |
04aed767 | 120 | const videoLive = await this.get({ videoId }) |
4f219914 | 121 | |
c826f34a | 122 | return sendRTMPStream({ rtmpBaseUrl: videoLive.rtmpUrl, streamKey: videoLive.streamKey, fixtureName, copyCodecs }) |
4f219914 C |
123 | } |
124 | ||
04aed767 | 125 | async runAndTestStreamError (options: OverrideCommandOptions & { |
4f219914 C |
126 | videoId: number | string |
127 | shouldHaveError: boolean | |
128 | }) { | |
129 | const command = await this.sendRTMPStreamInVideo(options) | |
130 | ||
131 | return testFfmpegStreamError(command, options.shouldHaveError) | |
132 | } | |
133 | ||
04aed767 | 134 | waitUntilPublished (options: OverrideCommandOptions & { |
4f219914 C |
135 | videoId: number | string |
136 | }) { | |
137 | const { videoId } = options | |
04aed767 | 138 | return this.waitUntilState({ videoId, state: VideoState.PUBLISHED }) |
4f219914 C |
139 | } |
140 | ||
04aed767 | 141 | waitUntilWaiting (options: OverrideCommandOptions & { |
4f219914 C |
142 | videoId: number | string |
143 | }) { | |
144 | const { videoId } = options | |
04aed767 | 145 | return this.waitUntilState({ videoId, state: VideoState.WAITING_FOR_LIVE }) |
4f219914 C |
146 | } |
147 | ||
04aed767 | 148 | waitUntilEnded (options: OverrideCommandOptions & { |
4f219914 C |
149 | videoId: number | string |
150 | }) { | |
151 | const { videoId } = options | |
04aed767 | 152 | return this.waitUntilState({ videoId, state: VideoState.LIVE_ENDED }) |
4f219914 C |
153 | } |
154 | ||
04aed767 | 155 | waitUntilSegmentGeneration (options: OverrideCommandOptions & { |
4f219914 | 156 | videoUUID: string |
53023be3 | 157 | playlistNumber: number |
4f219914 | 158 | segment: number |
53023be3 | 159 | totalSessions?: number |
4f219914 | 160 | }) { |
53023be3 C |
161 | const { playlistNumber, segment, videoUUID, totalSessions = 1 } = options |
162 | const segmentName = `${playlistNumber}-00000${segment}.ts` | |
4f219914 | 163 | |
53023be3 C |
164 | return this.server.servers.waitUntilLog(`${videoUUID}/${segmentName}`, totalSessions * 2, false) |
165 | } | |
166 | ||
167 | getSegment (options: OverrideCommandOptions & { | |
168 | videoUUID: string | |
169 | playlistNumber: number | |
170 | segment: number | |
171 | }) { | |
172 | const { playlistNumber, segment, videoUUID } = options | |
173 | ||
174 | const segmentName = `${playlistNumber}-00000${segment}.ts` | |
175 | const url = `${this.server.url}/static/streaming-playlists/hls/${videoUUID}/${segmentName}` | |
176 | ||
177 | return this.getRawRequest({ | |
178 | ...options, | |
179 | ||
180 | url, | |
181 | implicitToken: false, | |
182 | defaultExpectedStatus: HttpStatusCode.OK_200 | |
183 | }) | |
4f219914 C |
184 | } |
185 | ||
4ec52d04 | 186 | async waitUntilReplacedByReplay (options: OverrideCommandOptions & { |
4f219914 C |
187 | videoId: number | string |
188 | }) { | |
189 | let video: VideoDetails | |
190 | ||
191 | do { | |
89d241a7 | 192 | video = await this.server.videos.getWithToken({ token: options.token, id: options.videoId }) |
4f219914 C |
193 | |
194 | await wait(500) | |
0305db28 | 195 | } while (video.isLive === true || video.state.id !== VideoState.PUBLISHED) |
4f219914 C |
196 | } |
197 | ||
04aed767 | 198 | async countPlaylists (options: OverrideCommandOptions & { |
4f219914 C |
199 | videoUUID: string |
200 | }) { | |
89d241a7 | 201 | const basePath = this.server.servers.buildDirectory('streaming-playlists') |
4f219914 C |
202 | const hlsPath = join(basePath, 'hls', options.videoUUID) |
203 | ||
204 | const files = await readdir(hlsPath) | |
205 | ||
206 | return files.filter(f => f.endsWith('.m3u8')).length | |
207 | } | |
208 | ||
04aed767 | 209 | private async waitUntilState (options: OverrideCommandOptions & { |
4f219914 C |
210 | videoId: number | string |
211 | state: VideoState | |
212 | }) { | |
213 | let video: VideoDetails | |
214 | ||
215 | do { | |
89d241a7 | 216 | video = await this.server.videos.getWithToken({ token: options.token, id: options.videoId }) |
4f219914 C |
217 | |
218 | await wait(500) | |
219 | } while (video.state.id !== options.state) | |
220 | } | |
221 | } |