]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Support RTMPS
authorChocobozzz <me@florianbigard.com>
Fri, 5 Nov 2021 10:36:03 +0000 (11:36 +0100)
committerChocobozzz <me@florianbigard.com>
Fri, 5 Nov 2021 10:38:17 +0000 (11:38 +0100)
20 files changed:
client/src/app/+videos/+video-edit/shared/video-edit.component.html
client/src/app/shared/shared-video-live/live-stream-information.component.html
config/default.yaml
config/production.yaml.example
server.ts
server/helpers/ffmpeg-utils.ts
server/initializers/checker-after-init.ts
server/initializers/checker-before-init.ts
server/initializers/config.ts
server/initializers/constants.ts
server/lib/live/live-manager.ts
server/lib/live/shared/muxing-session.ts
server/models/video/video-live.ts
server/tests/api/live/index.ts
server/tests/api/live/live-rtmps.ts [new file with mode: 0644]
server/tests/fixtures/rtmps.cert [new file with mode: 0644]
server/tests/fixtures/rtmps.key [new file with mode: 0644]
shared/extra-utils/server/server.ts
shared/models/videos/live/live-video.model.ts
support/doc/api/openapi.yaml

index 1c0ec72d1d6d5688af671c82d4ff582d553bd09e..aa88d6c4c7deb16f4a66c01c1dd85d419c167039 100644 (file)
               <my-live-documentation-link></my-live-documentation-link>
             </div>
 
-            <div class="form-group">
+            <div *ngIf="liveVideo.rtmpUrl" class="form-group">
               <label for="liveVideoRTMPUrl" i18n>Live RTMP Url</label>
               <my-input-toggle-hidden inputId="liveVideoRTMPUrl" [value]="liveVideo.rtmpUrl" [withToggle]="false" [withCopy]="true" [show]="true" [readonly]="true"></my-input-toggle-hidden>
             </div>
 
+            <div *ngIf="liveVideo.rtmpsUrl" class="form-group">
+              <label for="liveVideoRTMPSUrl" i18n>Live RTMPS Url</label>
+              <my-input-toggle-hidden inputId="liveVideoRTMPSUrl" [value]="liveVideo.rtmpsUrl" [withToggle]="false" [withCopy]="true" [show]="true" [readonly]="true"></my-input-toggle-hidden>
+            </div>
+
             <div class="form-group">
               <label for="liveVideoStreamKey" i18n>Live stream key</label>
               <my-input-toggle-hidden inputId="liveVideoStreamKey" [value]="liveVideo.streamKey" [withCopy]="true" [readonly]="true"></my-input-toggle-hidden>
index 7df1e5bc2d9c43b6c81ff899d2851e26ccd5cd50..35e894a55c51476eed0981acd51fce2b99db40be 100644 (file)
       <my-live-documentation-link></my-live-documentation-link>
     </div>
 
-    <div class="form-group">
+    <div *ngIf="live.rtmpUrl" class="form-group">
       <label for="liveVideoRTMPUrl" i18n>Live RTMP Url</label>
       <my-input-toggle-hidden inputId="liveVideoRTMPUrl" [value]="live.rtmpUrl" [withToggle]="false" [withCopy]="true" [show]="true" [readonly]="true"></my-input-toggle-hidden>
     </div>
 
+    <div *ngIf="live.rtmpsUrl" class="form-group">
+      <label for="liveVideoRTMPSUrl" i18n>Live RTMPS Url</label>
+      <my-input-toggle-hidden inputId="liveVideoRTMPSUrl" [value]="live.rtmpsUrl" [withToggle]="false" [withCopy]="true" [show]="true" [readonly]="true"></my-input-toggle-hidden>
+    </div>
+
     <div class="form-group">
       <label for="liveVideoStreamKey" i18n>Live stream key</label>
       <my-input-toggle-hidden inputId="liveVideoStreamKey" [value]="live.streamKey" [withCopy]="true" [readonly]="true"></my-input-toggle-hidden>
index c70e6a205886a05dc87fb320da50ed31eec38035..c30c29a6b8d2278a8ae179d3eef5bc25a67f5da6 100644 (file)
@@ -370,8 +370,17 @@ live:
 
   # Your firewall should accept traffic from this port in TCP if you enable live
   rtmp:
+    enabled: true
     port: 1935
 
+  rtmps:
+    enabled: false
+    port: 1936
+    # Absolute path
+    key_file: ''
+    # Absolute path
+    cert_file: ''
+
   # Allow to transcode the live streaming in multiple live resolutions
   transcoding:
     enabled: true
index 0da96f612c9f9685683d6a20980590d7ea77e8c2..0993eb9f2a35e4c2a72d89197eeadc90b98acbef 100644 (file)
@@ -380,8 +380,15 @@ live:
 
   # Your firewall should accept traffic from this port in TCP if you enable live
   rtmp:
+    enabled: true
     port: 1935
 
+  rtmps:
+    enabled: false
+    port: 1936
+    key_file: ''
+    cert_file: ''
+
   # Allow to transcode the live streaming in multiple live resolutions
   transcoding:
     enabled: true
index 9af83cde9ffa9973a447efcd5885d48c3fbbd009..b8c1d12517e21666a1b00d314d6e20dbf34cade2 100644 (file)
--- a/server.ts
+++ b/server.ts
@@ -306,7 +306,7 @@ async function startApplication () {
     .catch(err => logger.error('Cannot update streaming playlist infohashes.', { err }))
 
   LiveManager.Instance.init()
-  if (CONFIG.LIVE.ENABLED) LiveManager.Instance.run()
+  if (CONFIG.LIVE.ENABLED) await LiveManager.Instance.run()
 
   // Make server listening
   server.listen(port, hostname, async () => {
index 54fd031b75ae48bdb71038c869f66509738ed5d6..ec24f357be15e0c0dcd61e27699d15224bcc5f18 100644 (file)
@@ -219,7 +219,7 @@ async function transcode (options: TranscodeOptions) {
 // ---------------------------------------------------------------------------
 
 async function getLiveTranscodingCommand (options: {
-  rtmpUrl: string
+  inputUrl: string
 
   outPath: string
   masterPlaylistName: string
@@ -234,10 +234,9 @@ async function getLiveTranscodingCommand (options: {
   availableEncoders: AvailableEncoders
   profile: string
 }) {
-  const { rtmpUrl, outPath, resolutions, fps, bitrate, availableEncoders, profile, masterPlaylistName, ratio } = options
-  const input = rtmpUrl
+  const { inputUrl, outPath, resolutions, fps, bitrate, availableEncoders, profile, masterPlaylistName, ratio } = options
 
-  const command = getFFmpeg(input, 'live')
+  const command = getFFmpeg(inputUrl, 'live')
 
   const varStreamMap: string[] = []
 
@@ -259,7 +258,7 @@ async function getLiveTranscodingCommand (options: {
     const resolutionFPS = computeFPS(fps, resolution)
 
     const baseEncoderBuilderParams = {
-      input,
+      input: inputUrl,
 
       availableEncoders,
       profile,
@@ -327,8 +326,8 @@ async function getLiveTranscodingCommand (options: {
   return command
 }
 
-function getLiveMuxingCommand (rtmpUrl: string, outPath: string, masterPlaylistName: string) {
-  const command = getFFmpeg(rtmpUrl, 'live')
+function getLiveMuxingCommand (inputUrl: string, outPath: string, masterPlaylistName: string) {
+  const command = getFFmpeg(inputUrl, 'live')
 
   command.outputOption('-c:v copy')
   command.outputOption('-c:a copy')
index 7a9e0748294912e7e16130a52124fd1a67d3919d..57ef0d218afbaacbb934d2fdc971aa14707f0db4 100644 (file)
@@ -151,6 +151,20 @@ function checkConfig () {
     if (CONFIG.LIVE.ALLOW_REPLAY === true && CONFIG.TRANSCODING.ENABLED === false) {
       return 'Live allow replay cannot be enabled if transcoding is not enabled.'
     }
+
+    if (CONFIG.LIVE.RTMP.ENABLED === false && CONFIG.LIVE.RTMPS.ENABLED === false) {
+      return 'You must enable at least RTMP or RTMPS'
+    }
+
+    if (CONFIG.LIVE.RTMPS.ENABLED) {
+      if (!CONFIG.LIVE.RTMPS.KEY_FILE) {
+        return 'You must specify a key file to enabled RTMPS'
+      }
+
+      if (!CONFIG.LIVE.RTMPS.CERT_FILE) {
+        return 'You must specify a cert file to enable RTMPS'
+      }
+    }
   }
 
   // Object storage
index 39f0cebf652c3e3b3dc638d0ba34f6f6db226e2a..1015c5e451a8502bcfbf1fab7d6d63f7ffeacd72 100644 (file)
@@ -47,6 +47,7 @@ function checkMissedConfig () {
     'search.remote_uri.users', 'search.remote_uri.anonymous', 'search.search_index.enabled', 'search.search_index.url',
     'search.search_index.disable_local_search', 'search.search_index.is_default_search',
     'live.enabled', 'live.allow_replay', 'live.max_duration', 'live.max_user_lives', 'live.max_instance_lives',
+    'live.rtmp.enabled', 'live.rtmp.port', 'live.rtmps.enabled', 'live.rtmps.port', 'live.rtmps.key_file', 'live.rtmps.cert_file',
     'live.transcoding.enabled', 'live.transcoding.threads', 'live.transcoding.profile',
     'live.transcoding.resolutions.144p', 'live.transcoding.resolutions.240p', 'live.transcoding.resolutions.360p',
     'live.transcoding.resolutions.480p', 'live.transcoding.resolutions.720p', 'live.transcoding.resolutions.1080p',
index 0d5e2949950704cf305dbdc797fdb8763f1b185c..1288768d82b08768739bb9f3b5790cf5654e043c 100644 (file)
@@ -271,9 +271,17 @@ const CONFIG = {
     get ALLOW_REPLAY () { return config.get<boolean>('live.allow_replay') },
 
     RTMP: {
+      get ENABLED () { return config.get<boolean>('live.rtmp.enabled') },
       get PORT () { return config.get<number>('live.rtmp.port') }
     },
 
+    RTMPS: {
+      get ENABLED () { return config.get<boolean>('live.rtmps.enabled') },
+      get PORT () { return config.get<number>('live.rtmps.port') },
+      get KEY_FILE () { return config.get<string>('live.rtmps.key_file') },
+      get CERT_FILE () { return config.get<string>('live.rtmps.cert_file') }
+    },
+
     TRANSCODING: {
       get ENABLED () { return config.get<boolean>('live.transcoding.enabled') },
       get THREADS () { return config.get<number>('live.transcoding.threads') },
index 3781f9508b58222c9e4b8d8367a81c524df28d1b..fb6bc9a6685c0688263d813985114e5062050235 100644 (file)
@@ -52,7 +52,8 @@ const WEBSERVER = {
   WS: '',
   HOSTNAME: '',
   PORT: 0,
-  RTMP_URL: ''
+  RTMP_URL: '',
+  RTMPS_URL: ''
 }
 
 // Sortable columns per schema
@@ -998,6 +999,7 @@ function updateWebserverUrls () {
   WEBSERVER.PORT = CONFIG.WEBSERVER.PORT
 
   WEBSERVER.RTMP_URL = 'rtmp://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.LIVE.RTMP.PORT + '/' + VIDEO_LIVE.RTMP.BASE_PATH
+  WEBSERVER.RTMPS_URL = 'rtmps://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.LIVE.RTMPS.PORT + '/' + VIDEO_LIVE.RTMP.BASE_PATH
 }
 
 function updateWebserverConfig () {
index d7dc841d929d07dd612922b2749a3cd42aed53d0..c75cc3bda4bf986de39b0bd5aca23f2ca29d6b1c 100644 (file)
@@ -1,5 +1,7 @@
 
+import { readFile } from 'fs-extra'
 import { createServer, Server } from 'net'
+import { createServer as createServerTLS, Server as ServerTLS } from 'tls'
 import { isTestInstance } from '@server/helpers/core-utils'
 import {
   computeResolutionsToTranscode,
@@ -19,8 +21,8 @@ import { MStreamingPlaylistVideo, MVideo, MVideoLiveVideo } from '@server/types/
 import { VideoState, VideoStreamingPlaylistType } from '@shared/models'
 import { federateVideoIfNeeded } from '../activitypub/videos'
 import { JobQueue } from '../job-queue'
-import { PeerTubeSocket } from '../peertube-socket'
 import { generateHLSMasterPlaylistFilename, generateHlsSha256SegmentsFilename } from '../paths'
+import { PeerTubeSocket } from '../peertube-socket'
 import { LiveQuotaStore } from './live-quota-store'
 import { LiveSegmentShaStore } from './live-segment-sha-store'
 import { cleanupLive } from './live-utils'
@@ -40,9 +42,6 @@ const config = {
     gop_cache: VIDEO_LIVE.RTMP.GOP_CACHE,
     ping: VIDEO_LIVE.RTMP.PING,
     ping_timeout: VIDEO_LIVE.RTMP.PING_TIMEOUT
-  },
-  transcoding: {
-    ffmpeg: 'ffmpeg'
   }
 }
 
@@ -58,6 +57,9 @@ class LiveManager {
   private readonly watchersPerVideo = new Map<number, number[]>()
 
   private rtmpServer: Server
+  private rtmpsServer: ServerTLS
+
+  private running = false
 
   private constructor () {
   }
@@ -73,7 +75,9 @@ class LiveManager {
         return this.abortSession(sessionId)
       }
 
-      this.handleSession(sessionId, streamPath, splittedPath[2])
+      const session = this.getContext().sessions.get(sessionId)
+
+      this.handleSession(sessionId, session.inputOriginUrl + streamPath, splittedPath[2])
         .catch(err => logger.error('Cannot handle sessions.', { err, ...lTags(sessionId) }))
     })
 
@@ -82,12 +86,12 @@ class LiveManager {
     })
 
     registerConfigChangedHandler(() => {
-      if (!this.rtmpServer && CONFIG.LIVE.ENABLED === true) {
-        this.run()
+      if (!this.running && CONFIG.LIVE.ENABLED === true) {
+        this.run().catch(err => logger.error('Cannot run live server.', { err }))
         return
       }
 
-      if (this.rtmpServer && CONFIG.LIVE.ENABLED === false) {
+      if (this.running && CONFIG.LIVE.ENABLED === false) {
         this.stop()
       }
     })
@@ -99,23 +103,53 @@ class LiveManager {
     setInterval(() => this.updateLiveViews(), VIEW_LIFETIME.LIVE)
   }
 
-  run () {
-    logger.info('Running RTMP server on port %d', config.rtmp.port, lTags())
+  async run () {
+    this.running = true
 
-    this.rtmpServer = createServer(socket => {
-      const session = new NodeRtmpSession(config, socket)
+    if (CONFIG.LIVE.RTMP.ENABLED) {
+      logger.info('Running RTMP server on port %d', CONFIG.LIVE.RTMP.PORT, lTags())
 
-      session.run()
-    })
+      this.rtmpServer = createServer(socket => {
+        const session = new NodeRtmpSession(config, socket)
 
-    this.rtmpServer.on('error', err => {
-      logger.error('Cannot run RTMP server.', { err, ...lTags() })
-    })
+        session.inputOriginUrl = 'rtmp://127.0.0.1:' + CONFIG.LIVE.RTMP.PORT
+        session.run()
+      })
+
+      this.rtmpServer.on('error', err => {
+        logger.error('Cannot run RTMP server.', { err, ...lTags() })
+      })
+
+      this.rtmpServer.listen(CONFIG.LIVE.RTMP.PORT)
+    }
 
-    this.rtmpServer.listen(CONFIG.LIVE.RTMP.PORT)
+    if (CONFIG.LIVE.RTMPS.ENABLED) {
+      logger.info('Running RTMPS server on port %d', CONFIG.LIVE.RTMPS.PORT, lTags())
+
+      const [ key, cert ] = await Promise.all([
+        readFile(CONFIG.LIVE.RTMPS.KEY_FILE),
+        readFile(CONFIG.LIVE.RTMPS.CERT_FILE)
+      ])
+      const serverOptions = { key, cert }
+
+      this.rtmpsServer = createServerTLS(serverOptions, socket => {
+        const session = new NodeRtmpSession(config, socket)
+
+        session.inputOriginUrl = 'rtmps://127.0.0.1:' + CONFIG.LIVE.RTMPS.PORT
+        session.run()
+      })
+
+      this.rtmpsServer.on('error', err => {
+        logger.error('Cannot run RTMPS server.', { err, ...lTags() })
+      })
+
+      this.rtmpsServer.listen(CONFIG.LIVE.RTMPS.PORT)
+    }
   }
 
   stop () {
+    this.running = false
+
     logger.info('Stopping RTMP server.', lTags())
 
     this.rtmpServer.close()
@@ -174,7 +208,7 @@ class LiveManager {
     }
   }
 
-  private async handleSession (sessionId: string, streamPath: string, streamKey: string) {
+  private async handleSession (sessionId: string, inputUrl: string, streamKey: string) {
     const videoLive = await VideoLiveModel.loadByStreamKey(streamKey)
     if (!videoLive) {
       logger.warn('Unknown live video with stream key %s.', streamKey, lTags(sessionId))
@@ -197,20 +231,18 @@ class LiveManager {
 
     this.videoSessions.set(video.id, sessionId)
 
-    const rtmpUrl = 'rtmp://127.0.0.1:' + config.rtmp.port + streamPath
-
     const now = Date.now()
-    const probe = await ffprobePromise(rtmpUrl)
+    const probe = await ffprobePromise(inputUrl)
 
     const [ { resolution, ratio }, fps, bitrate ] = await Promise.all([
-      getVideoFileResolution(rtmpUrl, probe),
-      getVideoFileFPS(rtmpUrl, probe),
-      getVideoFileBitrate(rtmpUrl, probe)
+      getVideoFileResolution(inputUrl, probe),
+      getVideoFileFPS(inputUrl, probe),
+      getVideoFileBitrate(inputUrl, probe)
     ])
 
     logger.info(
       '%s probing took %d ms (bitrate: %d, fps: %d, resolution: %d)',
-      rtmpUrl, Date.now() - now, bitrate, fps, resolution, lTags(sessionId, video.uuid)
+      inputUrl, Date.now() - now, bitrate, fps, resolution, lTags(sessionId, video.uuid)
     )
 
     const allResolutions = this.buildAllResolutionsToTranscode(resolution)
@@ -226,7 +258,7 @@ class LiveManager {
       sessionId,
       videoLive,
       streamingPlaylist,
-      rtmpUrl,
+      inputUrl,
       fps,
       bitrate,
       ratio,
@@ -238,13 +270,13 @@ class LiveManager {
     sessionId: string
     videoLive: MVideoLiveVideo
     streamingPlaylist: MStreamingPlaylistVideo
-    rtmpUrl: string
+    inputUrl: string
     fps: number
     bitrate: number
     ratio: number
     allResolutions: number[]
   }) {
-    const { sessionId, videoLive, streamingPlaylist, allResolutions, fps, bitrate, ratio, rtmpUrl } = options
+    const { sessionId, videoLive, streamingPlaylist, allResolutions, fps, bitrate, ratio, inputUrl } = options
     const videoUUID = videoLive.Video.uuid
     const localLTags = lTags(sessionId, videoUUID)
 
@@ -257,7 +289,7 @@ class LiveManager {
       sessionId,
       videoLive,
       streamingPlaylist,
-      rtmpUrl,
+      inputUrl,
       bitrate,
       ratio,
       fps,
index b52363af7941c0f27f661f434db79ff74da5cae0..c71f4e25f365df98c53fc99f17a7b91b783049cc 100644 (file)
@@ -52,7 +52,7 @@ class MuxingSession extends EventEmitter {
   private readonly sessionId: string
   private readonly videoLive: MVideoLiveVideo
   private readonly streamingPlaylist: MStreamingPlaylistVideo
-  private readonly rtmpUrl: string
+  private readonly inputUrl: string
   private readonly fps: number
   private readonly allResolutions: number[]
 
@@ -84,7 +84,7 @@ class MuxingSession extends EventEmitter {
     sessionId: string
     videoLive: MVideoLiveVideo
     streamingPlaylist: MStreamingPlaylistVideo
-    rtmpUrl: string
+    inputUrl: string
     fps: number
     bitrate: number
     ratio: number
@@ -97,7 +97,7 @@ class MuxingSession extends EventEmitter {
     this.sessionId = options.sessionId
     this.videoLive = options.videoLive
     this.streamingPlaylist = options.streamingPlaylist
-    this.rtmpUrl = options.rtmpUrl
+    this.inputUrl = options.inputUrl
     this.fps = options.fps
 
     this.bitrate = options.bitrate
@@ -120,7 +120,7 @@ class MuxingSession extends EventEmitter {
 
     this.ffmpegCommand = CONFIG.LIVE.TRANSCODING.ENABLED
       ? await getLiveTranscodingCommand({
-        rtmpUrl: this.rtmpUrl,
+        inputUrl: this.inputUrl,
 
         outPath,
         masterPlaylistName: this.streamingPlaylist.playlistFilename,
@@ -133,7 +133,7 @@ class MuxingSession extends EventEmitter {
         availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(),
         profile: CONFIG.LIVE.TRANSCODING.PROFILE
       })
-      : getLiveMuxingCommand(this.rtmpUrl, outPath, this.streamingPlaylist.playlistFilename)
+      : getLiveMuxingCommand(this.inputUrl, outPath, this.streamingPlaylist.playlistFilename)
 
     logger.info('Running live muxing/transcoding for %s.', this.videoUUID, this.lTags)
 
@@ -173,7 +173,7 @@ class MuxingSession extends EventEmitter {
   }
 
   private onFFmpegEnded (outPath: string) {
-    logger.info('RTMP transmuxing for video %s ended. Scheduling cleanup', this.rtmpUrl, this.lTags)
+    logger.info('RTMP transmuxing for video %s ended. Scheduling cleanup', this.inputUrl, this.lTags)
 
     setTimeout(() => {
       // Wait latest segments generation, and close watchers
index 014491d50011f532de056c2e79e022d1276ce33c..0bc8da02231f10daa64e7b92249cb30e9b821a2a 100644 (file)
@@ -5,6 +5,7 @@ import { AttributesOnly } from '@shared/core-utils'
 import { LiveVideo, VideoState } from '@shared/models'
 import { VideoModel } from './video'
 import { VideoBlacklistModel } from './video-blacklist'
+import { CONFIG } from '@server/initializers/config'
 
 @DefaultScope(() => ({
   include: [
@@ -97,11 +98,18 @@ export class VideoLiveModel extends Model<Partial<AttributesOnly<VideoLiveModel>
   }
 
   toFormattedJSON (): LiveVideo {
+    let rtmpUrl: string = null
+    let rtmpsUrl: string = null
+
+    // If we don't have a stream key, it means this is a remote live so we don't specify the rtmp URL
+    if (this.streamKey) {
+      if (CONFIG.LIVE.RTMP.ENABLED) rtmpUrl = WEBSERVER.RTMP_URL
+      if (CONFIG.LIVE.RTMPS.ENABLED) rtmpsUrl = WEBSERVER.RTMPS_URL
+    }
+
     return {
-      // If we don't have a stream key, it means this is a remote live so we don't specify the rtmp URL
-      rtmpUrl: this.streamKey
-        ? WEBSERVER.RTMP_URL
-        : null,
+      rtmpUrl,
+      rtmpsUrl,
 
       streamKey: this.streamKey,
       permanentLive: this.permanentLive,
index e6bcef49f77fca0a2c7d54fb39da645c142ad54a..105416b8da2e8196295e3303d341a817b9fc1d5b 100644 (file)
@@ -1,6 +1,7 @@
 import './live-constraints'
 import './live-socket-messages'
 import './live-permanent'
+import './live-rtmps'
 import './live-save-replay'
 import './live-views'
 import './live'
diff --git a/server/tests/api/live/live-rtmps.ts b/server/tests/api/live/live-rtmps.ts
new file mode 100644 (file)
index 0000000..378e6df
--- /dev/null
@@ -0,0 +1,146 @@
+/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
+
+import 'mocha'
+import * as chai from 'chai'
+import { VideoPrivacy } from '@shared/models'
+import {
+  buildAbsoluteFixturePath,
+  cleanupTests,
+  createSingleServer,
+  PeerTubeServer,
+  sendRTMPStream,
+  setAccessTokensToServers,
+  setDefaultVideoChannel,
+  stopFfmpeg,
+  testFfmpegStreamError,
+  waitUntilLivePublishedOnAllServers
+} from '../../../../shared/extra-utils'
+
+const expect = chai.expect
+
+describe('Test live RTMPS', function () {
+  let server: PeerTubeServer
+  let rtmpUrl: string
+  let rtmpsUrl: string
+
+  async function createLiveWrapper () {
+    const liveAttributes = {
+      name: 'live',
+      channelId: server.store.channel.id,
+      privacy: VideoPrivacy.PUBLIC,
+      saveReplay: false
+    }
+
+    const { uuid } = await server.live.create({ fields: liveAttributes })
+
+    const live = await server.live.get({ videoId: uuid })
+    const video = await server.videos.get({ id: uuid })
+
+    return Object.assign(video, live)
+  }
+
+  before(async function () {
+    this.timeout(120000)
+
+    server = await createSingleServer(1)
+
+    // Get the access tokens
+    await setAccessTokensToServers([ server ])
+    await setDefaultVideoChannel([ server ])
+
+    await server.config.updateCustomSubConfig({
+      newConfig: {
+        live: {
+          enabled: true,
+          allowReplay: true,
+          transcoding: {
+            enabled: false
+          }
+        }
+      }
+    })
+
+    rtmpUrl = 'rtmp://' + server.hostname + ':' + server.rtmpPort + '/live'
+    rtmpsUrl = 'rtmps://' + server.hostname + ':' + server.rtmpsPort + '/live'
+  })
+
+  it('Should enable RTMPS endpoint only', async function () {
+    this.timeout(240000)
+
+    await server.kill()
+    await server.run({
+      live: {
+        rtmp: {
+          enabled: false
+        },
+        rtmps: {
+          enabled: true,
+          port: server.rtmpsPort,
+          key_file: buildAbsoluteFixturePath('rtmps.key'),
+          cert_file: buildAbsoluteFixturePath('rtmps.cert')
+        }
+      }
+    })
+
+    {
+      const liveVideo = await createLiveWrapper()
+
+      expect(liveVideo.rtmpUrl).to.not.exist
+      expect(liveVideo.rtmpsUrl).to.equal(rtmpsUrl)
+
+      const command = sendRTMPStream({ rtmpBaseUrl: rtmpUrl, streamKey: liveVideo.streamKey })
+      await testFfmpegStreamError(command, true)
+    }
+
+    {
+      const liveVideo = await createLiveWrapper()
+
+      const command = sendRTMPStream({ rtmpBaseUrl: rtmpsUrl, streamKey: liveVideo.streamKey })
+      await waitUntilLivePublishedOnAllServers([ server ], liveVideo.uuid)
+      await stopFfmpeg(command)
+    }
+  })
+
+  it('Should enable both RTMP and RTMPS', async function () {
+    this.timeout(240000)
+
+    await server.kill()
+    await server.run({
+      live: {
+        rtmp: {
+          enabled: true,
+          port: server.rtmpPort
+        },
+        rtmps: {
+          enabled: true,
+          port: server.rtmpsPort,
+          key_file: buildAbsoluteFixturePath('rtmps.key'),
+          cert_file: buildAbsoluteFixturePath('rtmps.cert')
+        }
+      }
+    })
+
+    {
+      const liveVideo = await createLiveWrapper()
+
+      expect(liveVideo.rtmpUrl).to.equal(rtmpUrl)
+      expect(liveVideo.rtmpsUrl).to.equal(rtmpsUrl)
+
+      const command = sendRTMPStream({ rtmpBaseUrl: rtmpUrl, streamKey: liveVideo.streamKey })
+      await waitUntilLivePublishedOnAllServers([ server ], liveVideo.uuid)
+      await stopFfmpeg(command)
+    }
+
+    {
+      const liveVideo = await createLiveWrapper()
+
+      const command = sendRTMPStream({ rtmpBaseUrl: rtmpsUrl, streamKey: liveVideo.streamKey })
+      await waitUntilLivePublishedOnAllServers([ server ], liveVideo.uuid)
+      await stopFfmpeg(command)
+    }
+  })
+
+  after(async function () {
+    await cleanupTests([ server ])
+  })
+})
diff --git a/server/tests/fixtures/rtmps.cert b/server/tests/fixtures/rtmps.cert
new file mode 100644 (file)
index 0000000..3ef606c
--- /dev/null
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDazCCAlOgAwIBAgIUKNycLAZUs2jFsWUW+zZhBkpLB2wwDQYJKoZIhvcNAQEL
+BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMTExMDUxMDA4MzhaFw0yMTEy
+MDUxMDA4MzhaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
+HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDak20d81KG/9mVLU6Qw/uRniC935yf9Rlp8FVCDxUd
+zLbfHjrnIOv8kqinUI0nuEQC4DnF7Rbafe88WDU33Q8ixU/R0czUGq1AEwIjyN30
+5NjokCb26xWIly7RCfc/Ot6tjguHwKvcxqJMNC0Lit9Go9MDVnGFLkgHia68P72T
+ZDVV44YpzwYDicwQs5C4nZ4yzAeclia07qfUY0VAEZlxJ/9zjwYHCT0AKaEPH35E
+dUvjuvJ1OSHSN1S4acR+TPR3FwKQh3H/M/GWIqoiIOpdjFUBLs80QOM2aNrLmlyP
+JtyFJLxCP7Ery9fGY/yzHeSxpgOKwZopD6uHZKi5yazNAgMBAAGjUzBRMB0GA1Ud
+DgQWBBSSjhRQdWsybNQMLMhkwV+xiP2uoDAfBgNVHSMEGDAWgBSSjhRQdWsybNQM
+LMhkwV+xiP2uoDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQC8
+rJu3J5sqVKNQaXOmLPd49RM7KG3Y1KPqbQi1lh+sW6aefZ9daeh3JDYGBZGPG/Fi
+IMMP+LhGG0WqDm4ClK00wyNhBuNPEyzvuN/WMRX5djPxO1IZi+KogFwXsn853Ov9
+oV3nxArNNjDu2n92FiB7RTlXRXPIoRo2zEBcLvveGySn9XUazRzlqx6FAxYe2xsw
+U3cZ6/wwU1YsEZa5bwIQk+gkFj3zDsTyEkn2ntcE2NlR+AhCHKa/yAxgPFycAVPX
+2o+wNnc6H4syP98mMGj9hEE3RSJyCPgGBlgi7Swl64G3YygFPJzfLX9YTuxwr/eI
+oitEjF9ljtmdEnf0RdOj
+-----END CERTIFICATE-----
diff --git a/server/tests/fixtures/rtmps.key b/server/tests/fixtures/rtmps.key
new file mode 100644 (file)
index 0000000..14a85e7
--- /dev/null
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDak20d81KG/9mV
+LU6Qw/uRniC935yf9Rlp8FVCDxUdzLbfHjrnIOv8kqinUI0nuEQC4DnF7Rbafe88
+WDU33Q8ixU/R0czUGq1AEwIjyN305NjokCb26xWIly7RCfc/Ot6tjguHwKvcxqJM
+NC0Lit9Go9MDVnGFLkgHia68P72TZDVV44YpzwYDicwQs5C4nZ4yzAeclia07qfU
+Y0VAEZlxJ/9zjwYHCT0AKaEPH35EdUvjuvJ1OSHSN1S4acR+TPR3FwKQh3H/M/GW
+IqoiIOpdjFUBLs80QOM2aNrLmlyPJtyFJLxCP7Ery9fGY/yzHeSxpgOKwZopD6uH
+ZKi5yazNAgMBAAECggEAND7C+UK8+jnTl13CBsZhrnfemaQGexGJ5pGkv2p9gKb7
+Gy/Nooty/OdNWtjdNJ5N22YfSRkXulgZxBHNfrHfOU9yedOtIxHRUZx5iXYs36mH
+02cJeUHN3t1MOnkoWTvIGDH4vZUnP1lXV+Gs1rJ2Fht4h7a04cGjQ/H8C1EtDjqX
+kzH2T/gwo5hdGrxifRTs5wCVoP/iUwNtBI4WrY2rfC6sV+NOICgp0xX0NvGWZ8UT
+K1Ntpl8IxnxmeBd26d+Gbjc9d9fIRDtyXby4YOIlDZxnIiZEI0I452JqGl/jrXaP
+F3Troet4OBj5uH5s374d6ubKq66XogiLMIjEj2tYfQKBgQDtuaOu+y549bFJKVc9
+TCiWSOl/0j2kKKG8UG23zMC//AT13WqZDT5ObfOAuMhy70au/PD84D9RU/+gRVWb
+ptfybD9ugRNC8PkmdT82uYtZpS4+Xw4qyWVRgqQFmjSYz63cLcULVi8kiG8XmG5u
+QGgT/tNv5mxhOMUGSxhClOpLBwKBgQDrYO9UrLs+gDVKbHF4Dh+YJpaLnwwF+TFA
+j3ZbkE0XEeeXp/YDgyClmWwEkteJeNljtreCZ9gMkx3JdR9i8uecUQ2tFDBg3cN0
+BZAex2jFwSb0QbfzHNnE07I+aEIfHHjYXjzABl+1Yt95giKjce0Ke+8Zzahue0+9
+lYcAHemQiwKBgQCs9JAbIdJo3NBUW0iGZ19sH7YKciq4wXsSaC27OLPPugrd2m7Q
+1arMIwCzWT01KdLyQ0MNqBVJFWT49RjYuuWIEauAuVYLMQkEKu+H4Cx7V0syw7Op
++4bEa9jr3op/1zE17PLcUaLQ4JZ6w0Ms4Z0XVyH72thlT4lBD+ehoXhohwKBgEtJ
+LAPnY9Sv6Vuup/SAf/aIkSqDarMWa3x85pyO4Tl5zpuha3zgGjcdhYFI/ovIDbBp
+JvUdBeuvup1PSwS5MP+8pSUxCfBRvkyD4v8VRRvLlgwWYSHvnm/oTmDLtCqDTtvV
++JRq9X3s7BHPYAjrTahGz8lvEGqWIoE/LHkLGEPVAoGAaF3VHuqDfmD9PJUAlsU1
+qxN7yfOd2ve0+66Ghus24DVqUFqwp5f2AxZXYUtSaNUp8fVbqIi+Yq3YDTU2KfId
+5QNA/AiKi4VUNLElsG5DZlbszsE5KNp9fWQoggdQ5LND7AGEKeFERHOVQ7C5sc/C
+omIqK5/PsZmaf4OZLyecxJY=
+-----END PRIVATE KEY-----
index f7048ba437aa81af4e6c5af4c6ab48b25b3cd576..1ed00d724ae2b6ad4ecefc82e0af183301d69bbb 100644 (file)
@@ -56,6 +56,7 @@ export class PeerTubeServer {
   port?: number
 
   rtmpPort?: number
+  rtmpsPort?: number
 
   parallel?: boolean
   internalServerNumber: number
@@ -154,6 +155,7 @@ export class PeerTubeServer {
 
     this.internalServerNumber = this.parallel ? this.randomServer() : this.serverNumber
     this.rtmpPort = this.parallel ? this.randomRTMP() : 1936
+    this.rtmpsPort = this.parallel ? this.randomRTMP() : 1937
     this.port = 9000 + this.internalServerNumber
 
     this.url = `http://localhost:${this.port}`
index d6e9a50d154c212ee5c43aa1528694b1f4e55d73..815a93804953fbdfe71c6f65287188e6f2d74de2 100644 (file)
@@ -1,5 +1,7 @@
 export interface LiveVideo {
   rtmpUrl: string
+  rtmpsUrl: string
+
   streamKey: string
   saveReplay: boolean
   permanentLive: boolean
index 48109664a6108857bbcda3e2cc4792d692b27d27..0f72b08d2e48b22f7aadd226211d51b1663c9588 100644 (file)
@@ -7258,6 +7258,8 @@ components:
       properties:
         rtmpUrl:
           type: string
+        rtmpsUrl:
+          type: string
         streamKey:
           type: string
           description: RTMP stream key to use to stream into this live video