live-command.ts 5.9 KB

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