]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - shared/server-commands/videos/live-command.ts
feat: show contained playlists under My videos (#5125)
[github/Chocobozzz/PeerTube.git] / shared / server-commands / videos / live-command.ts
CommitLineData
4f219914
C
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { readdir } from 'fs-extra'
4f219914 4import { join } from 'path'
bbd5aa7e 5import { omit, wait } from '@shared/core-utils'
26e3e98f
C
6import {
7 HttpStatusCode,
8 LiveVideo,
9 LiveVideoCreate,
10 LiveVideoSession,
11 LiveVideoUpdate,
12 ResultList,
13 VideoCreateResult,
14 VideoDetails,
15 VideoState
16} from '@shared/models'
4f219914 17import { unwrapBody } from '../requests'
bbae45c3 18import { ObjectStorageCommand, PeerTubeServer } from '../server'
4f219914
C
19import { AbstractCommand, OverrideCommandOptions } from '../shared'
20import { sendRTMPStream, testFfmpegStreamError } from './live'
4f219914
C
21
22export 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
cfd57d2c
C
38 // ---------------------------------------------------------------------------
39
26e3e98f
C
40 listSessions (options: OverrideCommandOptions & {
41 videoId: number | string
42 }) {
43 const path = `/api/v1/videos/live/${options.videoId}/sessions`
44
45 return this.getRequestBody<ResultList<LiveVideoSession>>({
46 ...options,
47
48 path,
49 implicitToken: true,
50 defaultExpectedStatus: HttpStatusCode.OK_200
51 })
52 }
53
54 async findLatestSession (options: OverrideCommandOptions & {
55 videoId: number | string
56 }) {
57 const { data: sessions } = await this.listSessions(options)
58
59 return sessions[sessions.length - 1]
60 }
61
62 getReplaySession (options: OverrideCommandOptions & {
63 videoId: number | string
64 }) {
65 const path = `/api/v1/videos/${options.videoId}/live-session`
66
67 return this.getRequestBody<LiveVideoSession>({
68 ...options,
69
70 path,
71 implicitToken: true,
72 defaultExpectedStatus: HttpStatusCode.OK_200
73 })
74 }
75
cfd57d2c
C
76 // ---------------------------------------------------------------------------
77
04aed767 78 update (options: OverrideCommandOptions & {
4f219914
C
79 videoId: number | string
80 fields: LiveVideoUpdate
81 }) {
82 const { videoId, fields } = options
83 const path = '/api/v1/videos/live'
84
85 return this.putBodyRequest({
86 ...options,
87
88 path: path + '/' + videoId,
89 fields,
a1637fa1 90 implicitToken: true,
4f219914
C
91 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
92 })
93 }
94
04aed767 95 async create (options: OverrideCommandOptions & {
4f219914
C
96 fields: LiveVideoCreate
97 }) {
98 const { fields } = options
99 const path = '/api/v1/videos/live'
100
101 const attaches: any = {}
102 if (fields.thumbnailfile) attaches.thumbnailfile = fields.thumbnailfile
103 if (fields.previewfile) attaches.previewfile = fields.previewfile
104
105 const body = await unwrapBody<{ video: VideoCreateResult }>(this.postUploadRequest({
106 ...options,
107
108 path,
109 attaches,
bbd5aa7e 110 fields: omit(fields, [ 'thumbnailfile', 'previewfile' ]),
a1637fa1 111 implicitToken: true,
4f219914
C
112 defaultExpectedStatus: HttpStatusCode.OK_200
113 }))
114
115 return body.video
116 }
117
cfd57d2c
C
118 // ---------------------------------------------------------------------------
119
4f219914
C
120 async sendRTMPStreamInVideo (options: OverrideCommandOptions & {
121 videoId: number | string
122 fixtureName?: string
c826f34a 123 copyCodecs?: boolean
4f219914 124 }) {
c826f34a 125 const { videoId, fixtureName, copyCodecs } = options
04aed767 126 const videoLive = await this.get({ videoId })
4f219914 127
c826f34a 128 return sendRTMPStream({ rtmpBaseUrl: videoLive.rtmpUrl, streamKey: videoLive.streamKey, fixtureName, copyCodecs })
4f219914
C
129 }
130
04aed767 131 async runAndTestStreamError (options: OverrideCommandOptions & {
4f219914
C
132 videoId: number | string
133 shouldHaveError: boolean
134 }) {
135 const command = await this.sendRTMPStreamInVideo(options)
136
137 return testFfmpegStreamError(command, options.shouldHaveError)
138 }
139
cfd57d2c
C
140 // ---------------------------------------------------------------------------
141
04aed767 142 waitUntilPublished (options: OverrideCommandOptions & {
4f219914
C
143 videoId: number | string
144 }) {
145 const { videoId } = options
04aed767 146 return this.waitUntilState({ videoId, state: VideoState.PUBLISHED })
4f219914
C
147 }
148
04aed767 149 waitUntilWaiting (options: OverrideCommandOptions & {
4f219914
C
150 videoId: number | string
151 }) {
152 const { videoId } = options
04aed767 153 return this.waitUntilState({ videoId, state: VideoState.WAITING_FOR_LIVE })
4f219914
C
154 }
155
04aed767 156 waitUntilEnded (options: OverrideCommandOptions & {
4f219914
C
157 videoId: number | string
158 }) {
159 const { videoId } = options
04aed767 160 return this.waitUntilState({ videoId, state: VideoState.LIVE_ENDED })
4f219914
C
161 }
162
bbae45c3
C
163 async waitUntilSegmentGeneration (options: OverrideCommandOptions & {
164 server: PeerTubeServer
4f219914 165 videoUUID: string
53023be3 166 playlistNumber: number
4f219914 167 segment: number
bbae45c3 168 objectStorage: boolean
4f219914 169 }) {
bbae45c3 170 const { server, objectStorage, playlistNumber, segment, videoUUID } = options
53023be3 171
34aa316f 172 const segmentName = `${playlistNumber}-00000${segment}.ts`
bbae45c3
C
173 const baseUrl = objectStorage
174 ? ObjectStorageCommand.getPlaylistBaseUrl() + 'hls'
175 : server.url + '/static/streaming-playlists/hls'
176
177 let error = true
178
179 while (error) {
180 try {
181 await this.getRawRequest({
182 ...options,
183
184 url: `${baseUrl}/${videoUUID}/${segmentName}`,
185 implicitToken: false,
186 defaultExpectedStatus: HttpStatusCode.OK_200
187 })
188
dd84f4f2
C
189 const video = await server.videos.get({ id: videoUUID })
190 const hlsPlaylist = video.streamingPlaylists[0]
191
192 const shaBody = await server.streamingPlaylists.getSegmentSha256({ url: hlsPlaylist.segmentsSha256Url })
193
194 if (!shaBody[segmentName]) {
195 throw new Error('Segment SHA does not exist')
196 }
197
bbae45c3
C
198 error = false
199 } catch {
200 error = true
201 await wait(100)
202 }
203 }
34aa316f
C
204 }
205
cfd57d2c
C
206 async waitUntilReplacedByReplay (options: OverrideCommandOptions & {
207 videoId: number | string
208 }) {
209 let video: VideoDetails
210
211 do {
212 video = await this.server.videos.getWithToken({ token: options.token, id: options.videoId })
213
214 await wait(500)
215 } while (video.isLive === true || video.state.id !== VideoState.PUBLISHED)
216 }
217
218 // ---------------------------------------------------------------------------
219
220 getSegmentFile (options: OverrideCommandOptions & {
53023be3
C
221 videoUUID: string
222 playlistNumber: number
223 segment: number
cfd57d2c 224 objectStorage?: boolean // default false
53023be3 225 }) {
cfd57d2c 226 const { playlistNumber, segment, videoUUID, objectStorage = false } = options
53023be3
C
227
228 const segmentName = `${playlistNumber}-00000${segment}.ts`
cfd57d2c
C
229 const baseUrl = objectStorage
230 ? ObjectStorageCommand.getPlaylistBaseUrl()
231 : `${this.server.url}/static/streaming-playlists/hls`
232
233 const url = `${baseUrl}/${videoUUID}/${segmentName}`
53023be3
C
234
235 return this.getRawRequest({
236 ...options,
237
238 url,
239 implicitToken: false,
240 defaultExpectedStatus: HttpStatusCode.OK_200
241 })
4f219914
C
242 }
243
cfd57d2c
C
244 getPlaylistFile (options: OverrideCommandOptions & {
245 videoUUID: string
246 playlistName: string
247 objectStorage?: boolean // default false
4f219914 248 }) {
cfd57d2c 249 const { playlistName, videoUUID, objectStorage = false } = options
4f219914 250
cfd57d2c
C
251 const baseUrl = objectStorage
252 ? ObjectStorageCommand.getPlaylistBaseUrl()
253 : `${this.server.url}/static/streaming-playlists/hls`
4f219914 254
cfd57d2c
C
255 const url = `${baseUrl}/${videoUUID}/${playlistName}`
256
257 return this.getRawRequest({
258 ...options,
259
260 url,
261 implicitToken: false,
262 defaultExpectedStatus: HttpStatusCode.OK_200
263 })
4f219914
C
264 }
265
cfd57d2c
C
266 // ---------------------------------------------------------------------------
267
04aed767 268 async countPlaylists (options: OverrideCommandOptions & {
4f219914
C
269 videoUUID: string
270 }) {
89d241a7 271 const basePath = this.server.servers.buildDirectory('streaming-playlists')
4f219914
C
272 const hlsPath = join(basePath, 'hls', options.videoUUID)
273
274 const files = await readdir(hlsPath)
275
276 return files.filter(f => f.endsWith('.m3u8')).length
277 }
278
04aed767 279 private async waitUntilState (options: OverrideCommandOptions & {
4f219914
C
280 videoId: number | string
281 state: VideoState
282 }) {
283 let video: VideoDetails
284
285 do {
89d241a7 286 video = await this.server.videos.getWithToken({ token: options.token, id: options.videoId })
4f219914
C
287
288 await wait(500)
289 } while (video.state.id !== options.state)
290 }
291}