From df1db951c512a58110171d046ef367789df02733 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 5 Nov 2021 11:36:03 +0100 Subject: [PATCH] Support RTMPS --- .../shared/video-edit.component.html | 7 +- .../live-stream-information.component.html | 7 +- config/default.yaml | 9 ++ config/production.yaml.example | 7 + server.ts | 2 +- server/helpers/ffmpeg-utils.ts | 13 +- server/initializers/checker-after-init.ts | 14 ++ server/initializers/checker-before-init.ts | 1 + server/initializers/config.ts | 8 + server/initializers/constants.ts | 4 +- server/lib/live/live-manager.ts | 92 +++++++---- server/lib/live/shared/muxing-session.ts | 12 +- server/models/video/video-live.ts | 16 +- server/tests/api/live/index.ts | 1 + server/tests/api/live/live-rtmps.ts | 146 ++++++++++++++++++ server/tests/fixtures/rtmps.cert | 21 +++ server/tests/fixtures/rtmps.key | 28 ++++ shared/extra-utils/server/server.ts | 2 + shared/models/videos/live/live-video.model.ts | 2 + support/doc/api/openapi.yaml | 2 + 20 files changed, 343 insertions(+), 51 deletions(-) create mode 100644 server/tests/api/live/live-rtmps.ts create mode 100644 server/tests/fixtures/rtmps.cert create mode 100644 server/tests/fixtures/rtmps.key diff --git a/client/src/app/+videos/+video-edit/shared/video-edit.component.html b/client/src/app/+videos/+video-edit/shared/video-edit.component.html index 1c0ec72d1..aa88d6c4c 100644 --- a/client/src/app/+videos/+video-edit/shared/video-edit.component.html +++ b/client/src/app/+videos/+video-edit/shared/video-edit.component.html @@ -214,11 +214,16 @@ -
+
+
+ + +
+
diff --git a/client/src/app/shared/shared-video-live/live-stream-information.component.html b/client/src/app/shared/shared-video-live/live-stream-information.component.html index 7df1e5bc2..35e894a55 100644 --- a/client/src/app/shared/shared-video-live/live-stream-information.component.html +++ b/client/src/app/shared/shared-video-live/live-stream-information.component.html @@ -15,11 +15,16 @@
-
+
+
+ + +
+
diff --git a/config/default.yaml b/config/default.yaml index c70e6a205..c30c29a6b 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -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 diff --git a/config/production.yaml.example b/config/production.yaml.example index 0da96f612..0993eb9f2 100644 --- a/config/production.yaml.example +++ b/config/production.yaml.example @@ -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 diff --git a/server.ts b/server.ts index 9af83cde9..b8c1d1251 100644 --- 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 () => { diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index 54fd031b7..ec24f357b 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts @@ -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') diff --git a/server/initializers/checker-after-init.ts b/server/initializers/checker-after-init.ts index 7a9e07482..57ef0d218 100644 --- a/server/initializers/checker-after-init.ts +++ b/server/initializers/checker-after-init.ts @@ -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 diff --git a/server/initializers/checker-before-init.ts b/server/initializers/checker-before-init.ts index 39f0cebf6..1015c5e45 100644 --- a/server/initializers/checker-before-init.ts +++ b/server/initializers/checker-before-init.ts @@ -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', diff --git a/server/initializers/config.ts b/server/initializers/config.ts index 0d5e29499..1288768d8 100644 --- a/server/initializers/config.ts +++ b/server/initializers/config.ts @@ -271,9 +271,17 @@ const CONFIG = { get ALLOW_REPLAY () { return config.get('live.allow_replay') }, RTMP: { + get ENABLED () { return config.get('live.rtmp.enabled') }, get PORT () { return config.get('live.rtmp.port') } }, + RTMPS: { + get ENABLED () { return config.get('live.rtmps.enabled') }, + get PORT () { return config.get('live.rtmps.port') }, + get KEY_FILE () { return config.get('live.rtmps.key_file') }, + get CERT_FILE () { return config.get('live.rtmps.cert_file') } + }, + TRANSCODING: { get ENABLED () { return config.get('live.transcoding.enabled') }, get THREADS () { return config.get('live.transcoding.threads') }, diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 3781f9508..fb6bc9a66 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts @@ -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 () { diff --git a/server/lib/live/live-manager.ts b/server/lib/live/live-manager.ts index d7dc841d9..c75cc3bda 100644 --- a/server/lib/live/live-manager.ts +++ b/server/lib/live/live-manager.ts @@ -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() 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, diff --git a/server/lib/live/shared/muxing-session.ts b/server/lib/live/shared/muxing-session.ts index b52363af7..c71f4e25f 100644 --- a/server/lib/live/shared/muxing-session.ts +++ b/server/lib/live/shared/muxing-session.ts @@ -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 diff --git a/server/models/video/video-live.ts b/server/models/video/video-live.ts index 014491d50..0bc8da022 100644 --- a/server/models/video/video-live.ts +++ b/server/models/video/video-live.ts @@ -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 } 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, diff --git a/server/tests/api/live/index.ts b/server/tests/api/live/index.ts index e6bcef49f..105416b8d 100644 --- a/server/tests/api/live/index.ts +++ b/server/tests/api/live/index.ts @@ -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 index 000000000..378e6df3c --- /dev/null +++ b/server/tests/api/live/live-rtmps.ts @@ -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 index 000000000..3ef606c52 --- /dev/null +++ b/server/tests/fixtures/rtmps.cert @@ -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 index 000000000..14a85e70a --- /dev/null +++ b/server/tests/fixtures/rtmps.key @@ -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----- diff --git a/shared/extra-utils/server/server.ts b/shared/extra-utils/server/server.ts index f7048ba43..1ed00d724 100644 --- a/shared/extra-utils/server/server.ts +++ b/shared/extra-utils/server/server.ts @@ -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}` diff --git a/shared/models/videos/live/live-video.model.ts b/shared/models/videos/live/live-video.model.ts index d6e9a50d1..815a93804 100644 --- a/shared/models/videos/live/live-video.model.ts +++ b/shared/models/videos/live/live-video.model.ts @@ -1,5 +1,7 @@ export interface LiveVideo { rtmpUrl: string + rtmpsUrl: string + streamKey: string saveReplay: boolean permanentLive: boolean diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml index 48109664a..0f72b08d2 100644 --- a/support/doc/api/openapi.yaml +++ b/support/doc/api/openapi.yaml @@ -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 -- 2.41.0