aboutsummaryrefslogtreecommitdiffhomepage
path: root/shared
diff options
context:
space:
mode:
Diffstat (limited to 'shared')
-rw-r--r--shared/models/plugins/client/client-hook.model.ts5
-rw-r--r--shared/models/plugins/server/server-hook.model.ts4
-rw-r--r--shared/models/users/index.ts1
-rw-r--r--shared/models/users/two-factor-enable-result.model.ts7
-rw-r--r--shared/models/users/user.model.ts2
-rw-r--r--shared/server-commands/requests/requests.ts7
-rw-r--r--shared/server-commands/server/server.ts45
-rw-r--r--shared/server-commands/users/index.ts1
-rw-r--r--shared/server-commands/users/login-command.ts73
-rw-r--r--shared/server-commands/users/two-factor-command.ts92
-rw-r--r--shared/server-commands/users/users-command.ts8
-rw-r--r--shared/server-commands/videos/live-command.ts99
-rw-r--r--shared/server-commands/videos/live.ts2
-rw-r--r--shared/server-commands/videos/streaming-playlists-command.ts38
14 files changed, 317 insertions, 67 deletions
diff --git a/shared/models/plugins/client/client-hook.model.ts b/shared/models/plugins/client/client-hook.model.ts
index e6313b60e..20e019304 100644
--- a/shared/models/plugins/client/client-hook.model.ts
+++ b/shared/models/plugins/client/client-hook.model.ts
@@ -88,7 +88,10 @@ export const clientFilterHookObject = {
88 'filter:share.video-playlist-url.build.result': true, 88 'filter:share.video-playlist-url.build.result': true,
89 89
90 // Filter videojs options built for PeerTube player 90 // Filter videojs options built for PeerTube player
91 'filter:internal.player.videojs.options.result': true 91 'filter:internal.player.videojs.options.result': true,
92
93 // Filter p2p media loader options built for PeerTube player
94 'filter:internal.player.p2p-media-loader.options.result': true
92} 95}
93 96
94export type ClientFilterHookName = keyof typeof clientFilterHookObject 97export type ClientFilterHookName = keyof typeof clientFilterHookObject
diff --git a/shared/models/plugins/server/server-hook.model.ts b/shared/models/plugins/server/server-hook.model.ts
index 5bf01c4b4..f11d2050b 100644
--- a/shared/models/plugins/server/server-hook.model.ts
+++ b/shared/models/plugins/server/server-hook.model.ts
@@ -103,7 +103,9 @@ export const serverFilterHookObject = {
103 'filter:job-queue.process.result': true, 103 'filter:job-queue.process.result': true,
104 104
105 'filter:transcoding.manual.resolutions-to-transcode.result': true, 105 'filter:transcoding.manual.resolutions-to-transcode.result': true,
106 'filter:transcoding.auto.resolutions-to-transcode.result': true 106 'filter:transcoding.auto.resolutions-to-transcode.result': true,
107
108 'filter:activity-pub.remote-video-comment.create.accept.result': true
107} 109}
108 110
109export type ServerFilterHookName = keyof typeof serverFilterHookObject 111export type ServerFilterHookName = keyof typeof serverFilterHookObject
diff --git a/shared/models/users/index.ts b/shared/models/users/index.ts
index b25978587..32f7a441c 100644
--- a/shared/models/users/index.ts
+++ b/shared/models/users/index.ts
@@ -1,3 +1,4 @@
1export * from './two-factor-enable-result.model'
1export * from './user-create-result.model' 2export * from './user-create-result.model'
2export * from './user-create.model' 3export * from './user-create.model'
3export * from './user-flag.model' 4export * from './user-flag.model'
diff --git a/shared/models/users/two-factor-enable-result.model.ts b/shared/models/users/two-factor-enable-result.model.ts
new file mode 100644
index 000000000..1fc801f0a
--- /dev/null
+++ b/shared/models/users/two-factor-enable-result.model.ts
@@ -0,0 +1,7 @@
1export interface TwoFactorEnableResult {
2 otpRequest: {
3 requestToken: string
4 secret: string
5 uri: string
6 }
7}
diff --git a/shared/models/users/user.model.ts b/shared/models/users/user.model.ts
index 63c5c8a92..7b6494ff8 100644
--- a/shared/models/users/user.model.ts
+++ b/shared/models/users/user.model.ts
@@ -62,6 +62,8 @@ export interface User {
62 pluginAuth: string | null 62 pluginAuth: string | null
63 63
64 lastLoginDate: Date | null 64 lastLoginDate: Date | null
65
66 twoFactorEnabled: boolean
65} 67}
66 68
67export interface MyUserSpecialPlaylist { 69export interface MyUserSpecialPlaylist {
diff --git a/shared/server-commands/requests/requests.ts b/shared/server-commands/requests/requests.ts
index 85cbc9be9..8cc1245e0 100644
--- a/shared/server-commands/requests/requests.ts
+++ b/shared/server-commands/requests/requests.ts
@@ -134,7 +134,12 @@ function unwrapText (test: request.Test): Promise<string> {
134function unwrapBodyOrDecodeToJSON <T> (test: request.Test): Promise<T> { 134function unwrapBodyOrDecodeToJSON <T> (test: request.Test): Promise<T> {
135 return test.then(res => { 135 return test.then(res => {
136 if (res.body instanceof Buffer) { 136 if (res.body instanceof Buffer) {
137 return JSON.parse(new TextDecoder().decode(res.body)) 137 try {
138 return JSON.parse(new TextDecoder().decode(res.body))
139 } catch (err) {
140 console.error('Cannot decode JSON.', res.body)
141 throw err
142 }
138 } 143 }
139 144
140 return res.body 145 return res.body
diff --git a/shared/server-commands/server/server.ts b/shared/server-commands/server/server.ts
index 2b4c9c9f8..7096faf21 100644
--- a/shared/server-commands/server/server.ts
+++ b/shared/server-commands/server/server.ts
@@ -13,7 +13,15 @@ import { AbusesCommand } from '../moderation'
13import { OverviewsCommand } from '../overviews' 13import { OverviewsCommand } from '../overviews'
14import { SearchCommand } from '../search' 14import { SearchCommand } from '../search'
15import { SocketIOCommand } from '../socket' 15import { SocketIOCommand } from '../socket'
16import { AccountsCommand, BlocklistCommand, LoginCommand, NotificationsCommand, SubscriptionsCommand, UsersCommand } from '../users' 16import {
17 AccountsCommand,
18 BlocklistCommand,
19 LoginCommand,
20 NotificationsCommand,
21 SubscriptionsCommand,
22 TwoFactorCommand,
23 UsersCommand
24} from '../users'
17import { 25import {
18 BlacklistCommand, 26 BlacklistCommand,
19 CaptionsCommand, 27 CaptionsCommand,
@@ -136,6 +144,7 @@ export class PeerTubeServer {
136 videos?: VideosCommand 144 videos?: VideosCommand
137 videoStats?: VideoStatsCommand 145 videoStats?: VideoStatsCommand
138 views?: ViewsCommand 146 views?: ViewsCommand
147 twoFactor?: TwoFactorCommand
139 148
140 constructor (options: { serverNumber: number } | { url: string }) { 149 constructor (options: { serverNumber: number } | { url: string }) {
141 if ((options as any).url) { 150 if ((options as any).url) {
@@ -182,6 +191,12 @@ export class PeerTubeServer {
182 this.port = parseInt(parsed.port) 191 this.port = parseInt(parsed.port)
183 } 192 }
184 193
194 getDirectoryPath (directoryName: string) {
195 const testDirectory = 'test' + this.internalServerNumber
196
197 return join(root(), testDirectory, directoryName)
198 }
199
185 async flushAndRun (configOverride?: Object, options: RunServerOptions = {}) { 200 async flushAndRun (configOverride?: Object, options: RunServerOptions = {}) {
186 await ServersCommand.flushTests(this.internalServerNumber) 201 await ServersCommand.flushTests(this.internalServerNumber)
187 202
@@ -341,19 +356,20 @@ export class PeerTubeServer {
341 suffix: '_test' + this.internalServerNumber 356 suffix: '_test' + this.internalServerNumber
342 }, 357 },
343 storage: { 358 storage: {
344 tmp: `test${this.internalServerNumber}/tmp/`, 359 tmp: this.getDirectoryPath('tmp') + '/',
345 bin: `test${this.internalServerNumber}/bin/`, 360 bin: this.getDirectoryPath('bin') + '/',
346 avatars: `test${this.internalServerNumber}/avatars/`, 361 avatars: this.getDirectoryPath('avatars') + '/',
347 videos: `test${this.internalServerNumber}/videos/`, 362 videos: this.getDirectoryPath('videos') + '/',
348 streaming_playlists: `test${this.internalServerNumber}/streaming-playlists/`, 363 streaming_playlists: this.getDirectoryPath('streaming-playlists') + '/',
349 redundancy: `test${this.internalServerNumber}/redundancy/`, 364 redundancy: this.getDirectoryPath('redundancy') + '/',
350 logs: `test${this.internalServerNumber}/logs/`, 365 logs: this.getDirectoryPath('logs') + '/',
351 previews: `test${this.internalServerNumber}/previews/`, 366 previews: this.getDirectoryPath('previews') + '/',
352 thumbnails: `test${this.internalServerNumber}/thumbnails/`, 367 thumbnails: this.getDirectoryPath('thumbnails') + '/',
353 torrents: `test${this.internalServerNumber}/torrents/`, 368 torrents: this.getDirectoryPath('torrents') + '/',
354 captions: `test${this.internalServerNumber}/captions/`, 369 captions: this.getDirectoryPath('captions') + '/',
355 cache: `test${this.internalServerNumber}/cache/`, 370 cache: this.getDirectoryPath('cache') + '/',
356 plugins: `test${this.internalServerNumber}/plugins/` 371 plugins: this.getDirectoryPath('plugins') + '/',
372 well_known: this.getDirectoryPath('well-known') + '/'
357 }, 373 },
358 admin: { 374 admin: {
359 email: `admin${this.internalServerNumber}@example.com` 375 email: `admin${this.internalServerNumber}@example.com`
@@ -410,5 +426,6 @@ export class PeerTubeServer {
410 this.videoStudio = new VideoStudioCommand(this) 426 this.videoStudio = new VideoStudioCommand(this)
411 this.videoStats = new VideoStatsCommand(this) 427 this.videoStats = new VideoStatsCommand(this)
412 this.views = new ViewsCommand(this) 428 this.views = new ViewsCommand(this)
429 this.twoFactor = new TwoFactorCommand(this)
413 } 430 }
414} 431}
diff --git a/shared/server-commands/users/index.ts b/shared/server-commands/users/index.ts
index f6f93b4d2..1afc02dc1 100644
--- a/shared/server-commands/users/index.ts
+++ b/shared/server-commands/users/index.ts
@@ -5,4 +5,5 @@ export * from './login'
5export * from './login-command' 5export * from './login-command'
6export * from './notifications-command' 6export * from './notifications-command'
7export * from './subscriptions-command' 7export * from './subscriptions-command'
8export * from './two-factor-command'
8export * from './users-command' 9export * from './users-command'
diff --git a/shared/server-commands/users/login-command.ts b/shared/server-commands/users/login-command.ts
index 54070e426..f2fc6d1c5 100644
--- a/shared/server-commands/users/login-command.ts
+++ b/shared/server-commands/users/login-command.ts
@@ -2,34 +2,27 @@ import { HttpStatusCode, PeerTubeProblemDocument } from '@shared/models'
2import { unwrapBody } from '../requests' 2import { unwrapBody } from '../requests'
3import { AbstractCommand, OverrideCommandOptions } from '../shared' 3import { AbstractCommand, OverrideCommandOptions } from '../shared'
4 4
5type LoginOptions = OverrideCommandOptions & {
6 client?: { id?: string, secret?: string }
7 user?: { username: string, password?: string }
8 otpToken?: string
9}
10
5export class LoginCommand extends AbstractCommand { 11export class LoginCommand extends AbstractCommand {
6 12
7 login (options: OverrideCommandOptions & { 13 async login (options: LoginOptions = {}) {
8 client?: { id?: string, secret?: string } 14 const res = await this._login(options)
9 user?: { username: string, password?: string }
10 } = {}) {
11 const { client = this.server.store.client, user = this.server.store.user } = options
12 const path = '/api/v1/users/token'
13 15
14 const body = { 16 return this.unwrapLoginBody(res.body)
15 client_id: client.id, 17 }
16 client_secret: client.secret,
17 username: user.username,
18 password: user.password ?? 'password',
19 response_type: 'code',
20 grant_type: 'password',
21 scope: 'upload'
22 }
23 18
24 return unwrapBody<{ access_token: string, refresh_token: string } & PeerTubeProblemDocument>(this.postBodyRequest({ 19 async loginAndGetResponse (options: LoginOptions = {}) {
25 ...options, 20 const res = await this._login(options)
26 21
27 path, 22 return {
28 requestType: 'form', 23 res,
29 fields: body, 24 body: this.unwrapLoginBody(res.body)
30 implicitToken: false, 25 }
31 defaultExpectedStatus: HttpStatusCode.OK_200
32 }))
33 } 26 }
34 27
35 getAccessToken (arg1?: { username: string, password?: string }): Promise<string> 28 getAccessToken (arg1?: { username: string, password?: string }): Promise<string>
@@ -129,4 +122,38 @@ export class LoginCommand extends AbstractCommand {
129 defaultExpectedStatus: HttpStatusCode.OK_200 122 defaultExpectedStatus: HttpStatusCode.OK_200
130 }) 123 })
131 } 124 }
125
126 private _login (options: LoginOptions) {
127 const { client = this.server.store.client, user = this.server.store.user, otpToken } = options
128 const path = '/api/v1/users/token'
129
130 const body = {
131 client_id: client.id,
132 client_secret: client.secret,
133 username: user.username,
134 password: user.password ?? 'password',
135 response_type: 'code',
136 grant_type: 'password',
137 scope: 'upload'
138 }
139
140 const headers = otpToken
141 ? { 'x-peertube-otp': otpToken }
142 : {}
143
144 return this.postBodyRequest({
145 ...options,
146
147 path,
148 headers,
149 requestType: 'form',
150 fields: body,
151 implicitToken: false,
152 defaultExpectedStatus: HttpStatusCode.OK_200
153 })
154 }
155
156 private unwrapLoginBody (body: any) {
157 return body as { access_token: string, refresh_token: string } & PeerTubeProblemDocument
158 }
132} 159}
diff --git a/shared/server-commands/users/two-factor-command.ts b/shared/server-commands/users/two-factor-command.ts
new file mode 100644
index 000000000..5542acfda
--- /dev/null
+++ b/shared/server-commands/users/two-factor-command.ts
@@ -0,0 +1,92 @@
1import { TOTP } from 'otpauth'
2import { HttpStatusCode, TwoFactorEnableResult } from '@shared/models'
3import { unwrapBody } from '../requests'
4import { AbstractCommand, OverrideCommandOptions } from '../shared'
5
6export class TwoFactorCommand extends AbstractCommand {
7
8 static buildOTP (options: {
9 secret: string
10 }) {
11 const { secret } = options
12
13 return new TOTP({
14 issuer: 'PeerTube',
15 algorithm: 'SHA1',
16 digits: 6,
17 period: 30,
18 secret
19 })
20 }
21
22 request (options: OverrideCommandOptions & {
23 userId: number
24 currentPassword?: string
25 }) {
26 const { currentPassword, userId } = options
27
28 const path = '/api/v1/users/' + userId + '/two-factor/request'
29
30 return unwrapBody<TwoFactorEnableResult>(this.postBodyRequest({
31 ...options,
32
33 path,
34 fields: { currentPassword },
35 implicitToken: true,
36 defaultExpectedStatus: HttpStatusCode.OK_200
37 }))
38 }
39
40 confirmRequest (options: OverrideCommandOptions & {
41 userId: number
42 requestToken: string
43 otpToken: string
44 }) {
45 const { userId, requestToken, otpToken } = options
46
47 const path = '/api/v1/users/' + userId + '/two-factor/confirm-request'
48
49 return this.postBodyRequest({
50 ...options,
51
52 path,
53 fields: { requestToken, otpToken },
54 implicitToken: true,
55 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
56 })
57 }
58
59 disable (options: OverrideCommandOptions & {
60 userId: number
61 currentPassword?: string
62 }) {
63 const { userId, currentPassword } = options
64 const path = '/api/v1/users/' + userId + '/two-factor/disable'
65
66 return this.postBodyRequest({
67 ...options,
68
69 path,
70 fields: { currentPassword },
71 implicitToken: true,
72 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
73 })
74 }
75
76 async requestAndConfirm (options: OverrideCommandOptions & {
77 userId: number
78 currentPassword?: string
79 }) {
80 const { userId, currentPassword } = options
81
82 const { otpRequest } = await this.request({ userId, currentPassword })
83
84 await this.confirmRequest({
85 userId,
86 requestToken: otpRequest.requestToken,
87 otpToken: TwoFactorCommand.buildOTP({ secret: otpRequest.secret }).generate()
88 })
89
90 return otpRequest
91 }
92}
diff --git a/shared/server-commands/users/users-command.ts b/shared/server-commands/users/users-command.ts
index d8303848d..811b9685b 100644
--- a/shared/server-commands/users/users-command.ts
+++ b/shared/server-commands/users/users-command.ts
@@ -202,7 +202,8 @@ export class UsersCommand extends AbstractCommand {
202 token, 202 token,
203 userId: user.id, 203 userId: user.id,
204 userChannelId: me.videoChannels[0].id, 204 userChannelId: me.videoChannels[0].id,
205 userChannelName: me.videoChannels[0].name 205 userChannelName: me.videoChannels[0].name,
206 password
206 } 207 }
207 } 208 }
208 209
@@ -217,12 +218,13 @@ export class UsersCommand extends AbstractCommand {
217 username: string 218 username: string
218 password?: string 219 password?: string
219 displayName?: string 220 displayName?: string
221 email?: string
220 channel?: { 222 channel?: {
221 name: string 223 name: string
222 displayName: string 224 displayName: string
223 } 225 }
224 }) { 226 }) {
225 const { username, password = 'password', displayName, channel } = options 227 const { username, password = 'password', displayName, channel, email = username + '@example.com' } = options
226 const path = '/api/v1/users/register' 228 const path = '/api/v1/users/register'
227 229
228 return this.postBodyRequest({ 230 return this.postBodyRequest({
@@ -232,7 +234,7 @@ export class UsersCommand extends AbstractCommand {
232 fields: { 234 fields: {
233 username, 235 username,
234 password, 236 password,
235 email: username + '@example.com', 237 email,
236 displayName, 238 displayName,
237 channel 239 channel
238 }, 240 },
diff --git a/shared/server-commands/videos/live-command.ts b/shared/server-commands/videos/live-command.ts
index d804fd883..b163f7189 100644
--- a/shared/server-commands/videos/live-command.ts
+++ b/shared/server-commands/videos/live-command.ts
@@ -15,6 +15,7 @@ import {
15 VideoState 15 VideoState
16} from '@shared/models' 16} from '@shared/models'
17import { unwrapBody } from '../requests' 17import { unwrapBody } from '../requests'
18import { ObjectStorageCommand, PeerTubeServer } from '../server'
18import { AbstractCommand, OverrideCommandOptions } from '../shared' 19import { AbstractCommand, OverrideCommandOptions } from '../shared'
19import { sendRTMPStream, testFfmpegStreamError } from './live' 20import { sendRTMPStream, testFfmpegStreamError } from './live'
20 21
@@ -34,6 +35,8 @@ export class LiveCommand extends AbstractCommand {
34 }) 35 })
35 } 36 }
36 37
38 // ---------------------------------------------------------------------------
39
37 listSessions (options: OverrideCommandOptions & { 40 listSessions (options: OverrideCommandOptions & {
38 videoId: number | string 41 videoId: number | string
39 }) { 42 }) {
@@ -70,6 +73,8 @@ export class LiveCommand extends AbstractCommand {
70 }) 73 })
71 } 74 }
72 75
76 // ---------------------------------------------------------------------------
77
73 update (options: OverrideCommandOptions & { 78 update (options: OverrideCommandOptions & {
74 videoId: number | string 79 videoId: number | string
75 fields: LiveVideoUpdate 80 fields: LiveVideoUpdate
@@ -110,6 +115,8 @@ export class LiveCommand extends AbstractCommand {
110 return body.video 115 return body.video
111 } 116 }
112 117
118 // ---------------------------------------------------------------------------
119
113 async sendRTMPStreamInVideo (options: OverrideCommandOptions & { 120 async sendRTMPStreamInVideo (options: OverrideCommandOptions & {
114 videoId: number | string 121 videoId: number | string
115 fixtureName?: string 122 fixtureName?: string
@@ -130,6 +137,8 @@ export class LiveCommand extends AbstractCommand {
130 return testFfmpegStreamError(command, options.shouldHaveError) 137 return testFfmpegStreamError(command, options.shouldHaveError)
131 } 138 }
132 139
140 // ---------------------------------------------------------------------------
141
133 waitUntilPublished (options: OverrideCommandOptions & { 142 waitUntilPublished (options: OverrideCommandOptions & {
134 videoId: number | string 143 videoId: number | string
135 }) { 144 }) {
@@ -151,27 +160,77 @@ export class LiveCommand extends AbstractCommand {
151 return this.waitUntilState({ videoId, state: VideoState.LIVE_ENDED }) 160 return this.waitUntilState({ videoId, state: VideoState.LIVE_ENDED })
152 } 161 }
153 162
154 waitUntilSegmentGeneration (options: OverrideCommandOptions & { 163 async waitUntilSegmentGeneration (options: OverrideCommandOptions & {
164 server: PeerTubeServer
155 videoUUID: string 165 videoUUID: string
156 playlistNumber: number 166 playlistNumber: number
157 segment: number 167 segment: number
158 totalSessions?: number 168 objectStorage: boolean
159 }) { 169 }) {
160 const { playlistNumber, segment, videoUUID, totalSessions = 1 } = options 170 const { server, objectStorage, playlistNumber, segment, videoUUID } = options
171
161 const segmentName = `${playlistNumber}-00000${segment}.ts` 172 const segmentName = `${playlistNumber}-00000${segment}.ts`
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
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
198 error = false
199 } catch {
200 error = true
201 await wait(100)
202 }
203 }
204 }
205
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 })
162 213
163 return this.server.servers.waitUntilLog(`${videoUUID}/${segmentName}`, totalSessions * 2, false) 214 await wait(500)
215 } while (video.isLive === true || video.state.id !== VideoState.PUBLISHED)
164 } 216 }
165 217
166 getSegment (options: OverrideCommandOptions & { 218 // ---------------------------------------------------------------------------
219
220 getSegmentFile (options: OverrideCommandOptions & {
167 videoUUID: string 221 videoUUID: string
168 playlistNumber: number 222 playlistNumber: number
169 segment: number 223 segment: number
224 objectStorage?: boolean // default false
170 }) { 225 }) {
171 const { playlistNumber, segment, videoUUID } = options 226 const { playlistNumber, segment, videoUUID, objectStorage = false } = options
172 227
173 const segmentName = `${playlistNumber}-00000${segment}.ts` 228 const segmentName = `${playlistNumber}-00000${segment}.ts`
174 const url = `${this.server.url}/static/streaming-playlists/hls/${videoUUID}/${segmentName}` 229 const baseUrl = objectStorage
230 ? ObjectStorageCommand.getPlaylistBaseUrl()
231 : `${this.server.url}/static/streaming-playlists/hls`
232
233 const url = `${baseUrl}/${videoUUID}/${segmentName}`
175 234
176 return this.getRawRequest({ 235 return this.getRawRequest({
177 ...options, 236 ...options,
@@ -182,18 +241,30 @@ export class LiveCommand extends AbstractCommand {
182 }) 241 })
183 } 242 }
184 243
185 async waitUntilReplacedByReplay (options: OverrideCommandOptions & { 244 getPlaylistFile (options: OverrideCommandOptions & {
186 videoId: number | string 245 videoUUID: string
246 playlistName: string
247 objectStorage?: boolean // default false
187 }) { 248 }) {
188 let video: VideoDetails 249 const { playlistName, videoUUID, objectStorage = false } = options
189 250
190 do { 251 const baseUrl = objectStorage
191 video = await this.server.videos.getWithToken({ token: options.token, id: options.videoId }) 252 ? ObjectStorageCommand.getPlaylistBaseUrl()
253 : `${this.server.url}/static/streaming-playlists/hls`
192 254
193 await wait(500) 255 const url = `${baseUrl}/${videoUUID}/${playlistName}`
194 } while (video.isLive === true || video.state.id !== VideoState.PUBLISHED) 256
257 return this.getRawRequest({
258 ...options,
259
260 url,
261 implicitToken: false,
262 defaultExpectedStatus: HttpStatusCode.OK_200
263 })
195 } 264 }
196 265
266 // ---------------------------------------------------------------------------
267
197 async countPlaylists (options: OverrideCommandOptions & { 268 async countPlaylists (options: OverrideCommandOptions & {
198 videoUUID: string 269 videoUUID: string
199 }) { 270 }) {
diff --git a/shared/server-commands/videos/live.ts b/shared/server-commands/videos/live.ts
index 6f180b05f..0d9c32aab 100644
--- a/shared/server-commands/videos/live.ts
+++ b/shared/server-commands/videos/live.ts
@@ -1,7 +1,7 @@
1import ffmpeg, { FfmpegCommand } from 'fluent-ffmpeg' 1import ffmpeg, { FfmpegCommand } from 'fluent-ffmpeg'
2import { buildAbsoluteFixturePath, wait } from '@shared/core-utils' 2import { buildAbsoluteFixturePath, wait } from '@shared/core-utils'
3import { PeerTubeServer } from '../server/server'
4import { VideoDetails, VideoInclude } from '@shared/models' 3import { VideoDetails, VideoInclude } from '@shared/models'
4import { PeerTubeServer } from '../server/server'
5 5
6function sendRTMPStream (options: { 6function sendRTMPStream (options: {
7 rtmpBaseUrl: string 7 rtmpBaseUrl: string
diff --git a/shared/server-commands/videos/streaming-playlists-command.ts b/shared/server-commands/videos/streaming-playlists-command.ts
index 5d40d35cb..25e446e72 100644
--- a/shared/server-commands/videos/streaming-playlists-command.ts
+++ b/shared/server-commands/videos/streaming-playlists-command.ts
@@ -1,22 +1,42 @@
1import { wait } from '@shared/core-utils'
1import { HttpStatusCode } from '@shared/models' 2import { HttpStatusCode } from '@shared/models'
2import { unwrapBody, unwrapTextOrDecode, unwrapBodyOrDecodeToJSON } from '../requests' 3import { unwrapBody, unwrapBodyOrDecodeToJSON, unwrapTextOrDecode } from '../requests'
3import { AbstractCommand, OverrideCommandOptions } from '../shared' 4import { AbstractCommand, OverrideCommandOptions } from '../shared'
4 5
5export class StreamingPlaylistsCommand extends AbstractCommand { 6export class StreamingPlaylistsCommand extends AbstractCommand {
6 7
7 get (options: OverrideCommandOptions & { 8 async get (options: OverrideCommandOptions & {
8 url: string 9 url: string
10 withRetry?: boolean // default false
11 currentRetry?: number
9 }) { 12 }) {
10 return unwrapTextOrDecode(this.getRawRequest({ 13 const { withRetry, currentRetry = 1 } = options
11 ...options,
12 14
13 url: options.url, 15 try {
14 implicitToken: false, 16 const result = await unwrapTextOrDecode(this.getRawRequest({
15 defaultExpectedStatus: HttpStatusCode.OK_200 17 ...options,
16 })) 18
19 url: options.url,
20 implicitToken: false,
21 defaultExpectedStatus: HttpStatusCode.OK_200
22 }))
23
24 return result
25 } catch (err) {
26 if (!withRetry || currentRetry > 5) throw err
27
28 await wait(100)
29
30 return this.get({
31 ...options,
32
33 withRetry,
34 currentRetry: currentRetry + 1
35 })
36 }
17 } 37 }
18 38
19 getSegment (options: OverrideCommandOptions & { 39 getFragmentedSegment (options: OverrideCommandOptions & {
20 url: string 40 url: string
21 range?: string 41 range?: string
22 }) { 42 }) {