Support RTMPS

This commit is contained in:
Chocobozzz 2021-11-05 11:36:03 +01:00
parent 8dd754c767
commit df1db951c5
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
20 changed files with 343 additions and 51 deletions

View File

@ -214,11 +214,16 @@
<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>

View File

@ -15,11 +15,16 @@
<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>

View 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

View 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

View File

@ -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 () => {

View 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')

View 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

View 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',

View 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') },

View 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 () {

View 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.listen(CONFIG.LIVE.RTMP.PORT)
this.rtmpServer.on('error', err => {
logger.error('Cannot run RTMP server.', { err, ...lTags() })
})
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,

View 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

View 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,

View 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'

View File

@ -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 ])
})
})

21
server/tests/fixtures/rtmps.cert vendored Normal file
View File

@ -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-----

28
server/tests/fixtures/rtmps.key vendored Normal file
View File

@ -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-----

View 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}`

View File

@ -1,5 +1,7 @@
export interface LiveVideo {
rtmpUrl: string
rtmpsUrl: string
streamKey: string
saveReplay: boolean
permanentLive: boolean

View 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