Support RTMPS
This commit is contained in:
parent
8dd754c767
commit
df1db951c5
|
@ -214,11 +214,16 @@
|
||||||
<my-live-documentation-link></my-live-documentation-link>
|
<my-live-documentation-link></my-live-documentation-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div *ngIf="liveVideo.rtmpUrl" class="form-group">
|
||||||
<label for="liveVideoRTMPUrl" i18n>Live RTMP Url</label>
|
<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>
|
<my-input-toggle-hidden inputId="liveVideoRTMPUrl" [value]="liveVideo.rtmpUrl" [withToggle]="false" [withCopy]="true" [show]="true" [readonly]="true"></my-input-toggle-hidden>
|
||||||
</div>
|
</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">
|
<div class="form-group">
|
||||||
<label for="liveVideoStreamKey" i18n>Live stream key</label>
|
<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>
|
<my-input-toggle-hidden inputId="liveVideoStreamKey" [value]="liveVideo.streamKey" [withCopy]="true" [readonly]="true"></my-input-toggle-hidden>
|
||||||
|
|
|
@ -15,11 +15,16 @@
|
||||||
<my-live-documentation-link></my-live-documentation-link>
|
<my-live-documentation-link></my-live-documentation-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div *ngIf="live.rtmpUrl" class="form-group">
|
||||||
<label for="liveVideoRTMPUrl" i18n>Live RTMP Url</label>
|
<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>
|
<my-input-toggle-hidden inputId="liveVideoRTMPUrl" [value]="live.rtmpUrl" [withToggle]="false" [withCopy]="true" [show]="true" [readonly]="true"></my-input-toggle-hidden>
|
||||||
</div>
|
</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">
|
<div class="form-group">
|
||||||
<label for="liveVideoStreamKey" i18n>Live stream key</label>
|
<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>
|
<my-input-toggle-hidden inputId="liveVideoStreamKey" [value]="live.streamKey" [withCopy]="true" [readonly]="true"></my-input-toggle-hidden>
|
||||||
|
|
|
@ -370,8 +370,17 @@ live:
|
||||||
|
|
||||||
# Your firewall should accept traffic from this port in TCP if you enable live
|
# Your firewall should accept traffic from this port in TCP if you enable live
|
||||||
rtmp:
|
rtmp:
|
||||||
|
enabled: true
|
||||||
port: 1935
|
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
|
# Allow to transcode the live streaming in multiple live resolutions
|
||||||
transcoding:
|
transcoding:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
|
@ -380,8 +380,15 @@ live:
|
||||||
|
|
||||||
# Your firewall should accept traffic from this port in TCP if you enable live
|
# Your firewall should accept traffic from this port in TCP if you enable live
|
||||||
rtmp:
|
rtmp:
|
||||||
|
enabled: true
|
||||||
port: 1935
|
port: 1935
|
||||||
|
|
||||||
|
rtmps:
|
||||||
|
enabled: false
|
||||||
|
port: 1936
|
||||||
|
key_file: ''
|
||||||
|
cert_file: ''
|
||||||
|
|
||||||
# Allow to transcode the live streaming in multiple live resolutions
|
# Allow to transcode the live streaming in multiple live resolutions
|
||||||
transcoding:
|
transcoding:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
|
@ -306,7 +306,7 @@ async function startApplication () {
|
||||||
.catch(err => logger.error('Cannot update streaming playlist infohashes.', { err }))
|
.catch(err => logger.error('Cannot update streaming playlist infohashes.', { err }))
|
||||||
|
|
||||||
LiveManager.Instance.init()
|
LiveManager.Instance.init()
|
||||||
if (CONFIG.LIVE.ENABLED) LiveManager.Instance.run()
|
if (CONFIG.LIVE.ENABLED) await LiveManager.Instance.run()
|
||||||
|
|
||||||
// Make server listening
|
// Make server listening
|
||||||
server.listen(port, hostname, async () => {
|
server.listen(port, hostname, async () => {
|
||||||
|
|
|
@ -219,7 +219,7 @@ async function transcode (options: TranscodeOptions) {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async function getLiveTranscodingCommand (options: {
|
async function getLiveTranscodingCommand (options: {
|
||||||
rtmpUrl: string
|
inputUrl: string
|
||||||
|
|
||||||
outPath: string
|
outPath: string
|
||||||
masterPlaylistName: string
|
masterPlaylistName: string
|
||||||
|
@ -234,10 +234,9 @@ async function getLiveTranscodingCommand (options: {
|
||||||
availableEncoders: AvailableEncoders
|
availableEncoders: AvailableEncoders
|
||||||
profile: string
|
profile: string
|
||||||
}) {
|
}) {
|
||||||
const { rtmpUrl, outPath, resolutions, fps, bitrate, availableEncoders, profile, masterPlaylistName, ratio } = options
|
const { inputUrl, outPath, resolutions, fps, bitrate, availableEncoders, profile, masterPlaylistName, ratio } = options
|
||||||
const input = rtmpUrl
|
|
||||||
|
|
||||||
const command = getFFmpeg(input, 'live')
|
const command = getFFmpeg(inputUrl, 'live')
|
||||||
|
|
||||||
const varStreamMap: string[] = []
|
const varStreamMap: string[] = []
|
||||||
|
|
||||||
|
@ -259,7 +258,7 @@ async function getLiveTranscodingCommand (options: {
|
||||||
const resolutionFPS = computeFPS(fps, resolution)
|
const resolutionFPS = computeFPS(fps, resolution)
|
||||||
|
|
||||||
const baseEncoderBuilderParams = {
|
const baseEncoderBuilderParams = {
|
||||||
input,
|
input: inputUrl,
|
||||||
|
|
||||||
availableEncoders,
|
availableEncoders,
|
||||||
profile,
|
profile,
|
||||||
|
@ -327,8 +326,8 @@ async function getLiveTranscodingCommand (options: {
|
||||||
return command
|
return command
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLiveMuxingCommand (rtmpUrl: string, outPath: string, masterPlaylistName: string) {
|
function getLiveMuxingCommand (inputUrl: string, outPath: string, masterPlaylistName: string) {
|
||||||
const command = getFFmpeg(rtmpUrl, 'live')
|
const command = getFFmpeg(inputUrl, 'live')
|
||||||
|
|
||||||
command.outputOption('-c:v copy')
|
command.outputOption('-c:v copy')
|
||||||
command.outputOption('-c:a copy')
|
command.outputOption('-c:a copy')
|
||||||
|
|
|
@ -151,6 +151,20 @@ function checkConfig () {
|
||||||
if (CONFIG.LIVE.ALLOW_REPLAY === true && CONFIG.TRANSCODING.ENABLED === false) {
|
if (CONFIG.LIVE.ALLOW_REPLAY === true && CONFIG.TRANSCODING.ENABLED === false) {
|
||||||
return 'Live allow replay cannot be enabled if transcoding is not enabled.'
|
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
|
// Object storage
|
||||||
|
|
|
@ -47,6 +47,7 @@ function checkMissedConfig () {
|
||||||
'search.remote_uri.users', 'search.remote_uri.anonymous', 'search.search_index.enabled', 'search.search_index.url',
|
'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',
|
'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.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.enabled', 'live.transcoding.threads', 'live.transcoding.profile',
|
||||||
'live.transcoding.resolutions.144p', 'live.transcoding.resolutions.240p', 'live.transcoding.resolutions.360p',
|
'live.transcoding.resolutions.144p', 'live.transcoding.resolutions.240p', 'live.transcoding.resolutions.360p',
|
||||||
'live.transcoding.resolutions.480p', 'live.transcoding.resolutions.720p', 'live.transcoding.resolutions.1080p',
|
'live.transcoding.resolutions.480p', 'live.transcoding.resolutions.720p', 'live.transcoding.resolutions.1080p',
|
||||||
|
|
|
@ -271,9 +271,17 @@ const CONFIG = {
|
||||||
get ALLOW_REPLAY () { return config.get<boolean>('live.allow_replay') },
|
get ALLOW_REPLAY () { return config.get<boolean>('live.allow_replay') },
|
||||||
|
|
||||||
RTMP: {
|
RTMP: {
|
||||||
|
get ENABLED () { return config.get<boolean>('live.rtmp.enabled') },
|
||||||
get PORT () { return config.get<number>('live.rtmp.port') }
|
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: {
|
TRANSCODING: {
|
||||||
get ENABLED () { return config.get<boolean>('live.transcoding.enabled') },
|
get ENABLED () { return config.get<boolean>('live.transcoding.enabled') },
|
||||||
get THREADS () { return config.get<number>('live.transcoding.threads') },
|
get THREADS () { return config.get<number>('live.transcoding.threads') },
|
||||||
|
|
|
@ -52,7 +52,8 @@ const WEBSERVER = {
|
||||||
WS: '',
|
WS: '',
|
||||||
HOSTNAME: '',
|
HOSTNAME: '',
|
||||||
PORT: 0,
|
PORT: 0,
|
||||||
RTMP_URL: ''
|
RTMP_URL: '',
|
||||||
|
RTMPS_URL: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sortable columns per schema
|
// Sortable columns per schema
|
||||||
|
@ -998,6 +999,7 @@ function updateWebserverUrls () {
|
||||||
WEBSERVER.PORT = CONFIG.WEBSERVER.PORT
|
WEBSERVER.PORT = CONFIG.WEBSERVER.PORT
|
||||||
|
|
||||||
WEBSERVER.RTMP_URL = 'rtmp://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.LIVE.RTMP.PORT + '/' + VIDEO_LIVE.RTMP.BASE_PATH
|
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 () {
|
function updateWebserverConfig () {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
|
||||||
|
import { readFile } from 'fs-extra'
|
||||||
import { createServer, Server } from 'net'
|
import { createServer, Server } from 'net'
|
||||||
|
import { createServer as createServerTLS, Server as ServerTLS } from 'tls'
|
||||||
import { isTestInstance } from '@server/helpers/core-utils'
|
import { isTestInstance } from '@server/helpers/core-utils'
|
||||||
import {
|
import {
|
||||||
computeResolutionsToTranscode,
|
computeResolutionsToTranscode,
|
||||||
|
@ -19,8 +21,8 @@ import { MStreamingPlaylistVideo, MVideo, MVideoLiveVideo } from '@server/types/
|
||||||
import { VideoState, VideoStreamingPlaylistType } from '@shared/models'
|
import { VideoState, VideoStreamingPlaylistType } from '@shared/models'
|
||||||
import { federateVideoIfNeeded } from '../activitypub/videos'
|
import { federateVideoIfNeeded } from '../activitypub/videos'
|
||||||
import { JobQueue } from '../job-queue'
|
import { JobQueue } from '../job-queue'
|
||||||
import { PeerTubeSocket } from '../peertube-socket'
|
|
||||||
import { generateHLSMasterPlaylistFilename, generateHlsSha256SegmentsFilename } from '../paths'
|
import { generateHLSMasterPlaylistFilename, generateHlsSha256SegmentsFilename } from '../paths'
|
||||||
|
import { PeerTubeSocket } from '../peertube-socket'
|
||||||
import { LiveQuotaStore } from './live-quota-store'
|
import { LiveQuotaStore } from './live-quota-store'
|
||||||
import { LiveSegmentShaStore } from './live-segment-sha-store'
|
import { LiveSegmentShaStore } from './live-segment-sha-store'
|
||||||
import { cleanupLive } from './live-utils'
|
import { cleanupLive } from './live-utils'
|
||||||
|
@ -40,9 +42,6 @@ const config = {
|
||||||
gop_cache: VIDEO_LIVE.RTMP.GOP_CACHE,
|
gop_cache: VIDEO_LIVE.RTMP.GOP_CACHE,
|
||||||
ping: VIDEO_LIVE.RTMP.PING,
|
ping: VIDEO_LIVE.RTMP.PING,
|
||||||
ping_timeout: VIDEO_LIVE.RTMP.PING_TIMEOUT
|
ping_timeout: VIDEO_LIVE.RTMP.PING_TIMEOUT
|
||||||
},
|
|
||||||
transcoding: {
|
|
||||||
ffmpeg: 'ffmpeg'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,6 +57,9 @@ class LiveManager {
|
||||||
private readonly watchersPerVideo = new Map<number, number[]>()
|
private readonly watchersPerVideo = new Map<number, number[]>()
|
||||||
|
|
||||||
private rtmpServer: Server
|
private rtmpServer: Server
|
||||||
|
private rtmpsServer: ServerTLS
|
||||||
|
|
||||||
|
private running = false
|
||||||
|
|
||||||
private constructor () {
|
private constructor () {
|
||||||
}
|
}
|
||||||
|
@ -73,7 +75,9 @@ class LiveManager {
|
||||||
return this.abortSession(sessionId)
|
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) }))
|
.catch(err => logger.error('Cannot handle sessions.', { err, ...lTags(sessionId) }))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -82,12 +86,12 @@ class LiveManager {
|
||||||
})
|
})
|
||||||
|
|
||||||
registerConfigChangedHandler(() => {
|
registerConfigChangedHandler(() => {
|
||||||
if (!this.rtmpServer && CONFIG.LIVE.ENABLED === true) {
|
if (!this.running && CONFIG.LIVE.ENABLED === true) {
|
||||||
this.run()
|
this.run().catch(err => logger.error('Cannot run live server.', { err }))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.rtmpServer && CONFIG.LIVE.ENABLED === false) {
|
if (this.running && CONFIG.LIVE.ENABLED === false) {
|
||||||
this.stop()
|
this.stop()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -99,12 +103,16 @@ class LiveManager {
|
||||||
setInterval(() => this.updateLiveViews(), VIEW_LIFETIME.LIVE)
|
setInterval(() => this.updateLiveViews(), VIEW_LIFETIME.LIVE)
|
||||||
}
|
}
|
||||||
|
|
||||||
run () {
|
async run () {
|
||||||
logger.info('Running RTMP server on port %d', config.rtmp.port, lTags())
|
this.running = true
|
||||||
|
|
||||||
|
if (CONFIG.LIVE.RTMP.ENABLED) {
|
||||||
|
logger.info('Running RTMP server on port %d', CONFIG.LIVE.RTMP.PORT, lTags())
|
||||||
|
|
||||||
this.rtmpServer = createServer(socket => {
|
this.rtmpServer = createServer(socket => {
|
||||||
const session = new NodeRtmpSession(config, socket)
|
const session = new NodeRtmpSession(config, socket)
|
||||||
|
|
||||||
|
session.inputOriginUrl = 'rtmp://127.0.0.1:' + CONFIG.LIVE.RTMP.PORT
|
||||||
session.run()
|
session.run()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -115,7 +123,33 @@ class LiveManager {
|
||||||
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 () {
|
stop () {
|
||||||
|
this.running = false
|
||||||
|
|
||||||
logger.info('Stopping RTMP server.', lTags())
|
logger.info('Stopping RTMP server.', lTags())
|
||||||
|
|
||||||
this.rtmpServer.close()
|
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)
|
const videoLive = await VideoLiveModel.loadByStreamKey(streamKey)
|
||||||
if (!videoLive) {
|
if (!videoLive) {
|
||||||
logger.warn('Unknown live video with stream key %s.', streamKey, lTags(sessionId))
|
logger.warn('Unknown live video with stream key %s.', streamKey, lTags(sessionId))
|
||||||
|
@ -197,20 +231,18 @@ class LiveManager {
|
||||||
|
|
||||||
this.videoSessions.set(video.id, sessionId)
|
this.videoSessions.set(video.id, sessionId)
|
||||||
|
|
||||||
const rtmpUrl = 'rtmp://127.0.0.1:' + config.rtmp.port + streamPath
|
|
||||||
|
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const probe = await ffprobePromise(rtmpUrl)
|
const probe = await ffprobePromise(inputUrl)
|
||||||
|
|
||||||
const [ { resolution, ratio }, fps, bitrate ] = await Promise.all([
|
const [ { resolution, ratio }, fps, bitrate ] = await Promise.all([
|
||||||
getVideoFileResolution(rtmpUrl, probe),
|
getVideoFileResolution(inputUrl, probe),
|
||||||
getVideoFileFPS(rtmpUrl, probe),
|
getVideoFileFPS(inputUrl, probe),
|
||||||
getVideoFileBitrate(rtmpUrl, probe)
|
getVideoFileBitrate(inputUrl, probe)
|
||||||
])
|
])
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
'%s probing took %d ms (bitrate: %d, fps: %d, resolution: %d)',
|
'%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)
|
const allResolutions = this.buildAllResolutionsToTranscode(resolution)
|
||||||
|
@ -226,7 +258,7 @@ class LiveManager {
|
||||||
sessionId,
|
sessionId,
|
||||||
videoLive,
|
videoLive,
|
||||||
streamingPlaylist,
|
streamingPlaylist,
|
||||||
rtmpUrl,
|
inputUrl,
|
||||||
fps,
|
fps,
|
||||||
bitrate,
|
bitrate,
|
||||||
ratio,
|
ratio,
|
||||||
|
@ -238,13 +270,13 @@ class LiveManager {
|
||||||
sessionId: string
|
sessionId: string
|
||||||
videoLive: MVideoLiveVideo
|
videoLive: MVideoLiveVideo
|
||||||
streamingPlaylist: MStreamingPlaylistVideo
|
streamingPlaylist: MStreamingPlaylistVideo
|
||||||
rtmpUrl: string
|
inputUrl: string
|
||||||
fps: number
|
fps: number
|
||||||
bitrate: number
|
bitrate: number
|
||||||
ratio: number
|
ratio: number
|
||||||
allResolutions: 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 videoUUID = videoLive.Video.uuid
|
||||||
const localLTags = lTags(sessionId, videoUUID)
|
const localLTags = lTags(sessionId, videoUUID)
|
||||||
|
|
||||||
|
@ -257,7 +289,7 @@ class LiveManager {
|
||||||
sessionId,
|
sessionId,
|
||||||
videoLive,
|
videoLive,
|
||||||
streamingPlaylist,
|
streamingPlaylist,
|
||||||
rtmpUrl,
|
inputUrl,
|
||||||
bitrate,
|
bitrate,
|
||||||
ratio,
|
ratio,
|
||||||
fps,
|
fps,
|
||||||
|
|
|
@ -52,7 +52,7 @@ class MuxingSession extends EventEmitter {
|
||||||
private readonly sessionId: string
|
private readonly sessionId: string
|
||||||
private readonly videoLive: MVideoLiveVideo
|
private readonly videoLive: MVideoLiveVideo
|
||||||
private readonly streamingPlaylist: MStreamingPlaylistVideo
|
private readonly streamingPlaylist: MStreamingPlaylistVideo
|
||||||
private readonly rtmpUrl: string
|
private readonly inputUrl: string
|
||||||
private readonly fps: number
|
private readonly fps: number
|
||||||
private readonly allResolutions: number[]
|
private readonly allResolutions: number[]
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ class MuxingSession extends EventEmitter {
|
||||||
sessionId: string
|
sessionId: string
|
||||||
videoLive: MVideoLiveVideo
|
videoLive: MVideoLiveVideo
|
||||||
streamingPlaylist: MStreamingPlaylistVideo
|
streamingPlaylist: MStreamingPlaylistVideo
|
||||||
rtmpUrl: string
|
inputUrl: string
|
||||||
fps: number
|
fps: number
|
||||||
bitrate: number
|
bitrate: number
|
||||||
ratio: number
|
ratio: number
|
||||||
|
@ -97,7 +97,7 @@ class MuxingSession extends EventEmitter {
|
||||||
this.sessionId = options.sessionId
|
this.sessionId = options.sessionId
|
||||||
this.videoLive = options.videoLive
|
this.videoLive = options.videoLive
|
||||||
this.streamingPlaylist = options.streamingPlaylist
|
this.streamingPlaylist = options.streamingPlaylist
|
||||||
this.rtmpUrl = options.rtmpUrl
|
this.inputUrl = options.inputUrl
|
||||||
this.fps = options.fps
|
this.fps = options.fps
|
||||||
|
|
||||||
this.bitrate = options.bitrate
|
this.bitrate = options.bitrate
|
||||||
|
@ -120,7 +120,7 @@ class MuxingSession extends EventEmitter {
|
||||||
|
|
||||||
this.ffmpegCommand = CONFIG.LIVE.TRANSCODING.ENABLED
|
this.ffmpegCommand = CONFIG.LIVE.TRANSCODING.ENABLED
|
||||||
? await getLiveTranscodingCommand({
|
? await getLiveTranscodingCommand({
|
||||||
rtmpUrl: this.rtmpUrl,
|
inputUrl: this.inputUrl,
|
||||||
|
|
||||||
outPath,
|
outPath,
|
||||||
masterPlaylistName: this.streamingPlaylist.playlistFilename,
|
masterPlaylistName: this.streamingPlaylist.playlistFilename,
|
||||||
|
@ -133,7 +133,7 @@ class MuxingSession extends EventEmitter {
|
||||||
availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(),
|
availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(),
|
||||||
profile: CONFIG.LIVE.TRANSCODING.PROFILE
|
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)
|
logger.info('Running live muxing/transcoding for %s.', this.videoUUID, this.lTags)
|
||||||
|
|
||||||
|
@ -173,7 +173,7 @@ class MuxingSession extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private onFFmpegEnded (outPath: string) {
|
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(() => {
|
setTimeout(() => {
|
||||||
// Wait latest segments generation, and close watchers
|
// Wait latest segments generation, and close watchers
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { AttributesOnly } from '@shared/core-utils'
|
||||||
import { LiveVideo, VideoState } from '@shared/models'
|
import { LiveVideo, VideoState } from '@shared/models'
|
||||||
import { VideoModel } from './video'
|
import { VideoModel } from './video'
|
||||||
import { VideoBlacklistModel } from './video-blacklist'
|
import { VideoBlacklistModel } from './video-blacklist'
|
||||||
|
import { CONFIG } from '@server/initializers/config'
|
||||||
|
|
||||||
@DefaultScope(() => ({
|
@DefaultScope(() => ({
|
||||||
include: [
|
include: [
|
||||||
|
@ -97,11 +98,18 @@ export class VideoLiveModel extends Model<Partial<AttributesOnly<VideoLiveModel>
|
||||||
}
|
}
|
||||||
|
|
||||||
toFormattedJSON (): LiveVideo {
|
toFormattedJSON (): LiveVideo {
|
||||||
return {
|
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 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
|
if (this.streamKey) {
|
||||||
? WEBSERVER.RTMP_URL
|
if (CONFIG.LIVE.RTMP.ENABLED) rtmpUrl = WEBSERVER.RTMP_URL
|
||||||
: null,
|
if (CONFIG.LIVE.RTMPS.ENABLED) rtmpsUrl = WEBSERVER.RTMPS_URL
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
rtmpUrl,
|
||||||
|
rtmpsUrl,
|
||||||
|
|
||||||
streamKey: this.streamKey,
|
streamKey: this.streamKey,
|
||||||
permanentLive: this.permanentLive,
|
permanentLive: this.permanentLive,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import './live-constraints'
|
import './live-constraints'
|
||||||
import './live-socket-messages'
|
import './live-socket-messages'
|
||||||
import './live-permanent'
|
import './live-permanent'
|
||||||
|
import './live-rtmps'
|
||||||
import './live-save-replay'
|
import './live-save-replay'
|
||||||
import './live-views'
|
import './live-views'
|
||||||
import './live'
|
import './live'
|
||||||
|
|
146
server/tests/api/live/live-rtmps.ts
Normal file
146
server/tests/api/live/live-rtmps.ts
Normal 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
21
server/tests/fixtures/rtmps.cert
vendored
Normal 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
28
server/tests/fixtures/rtmps.key
vendored
Normal 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-----
|
|
@ -56,6 +56,7 @@ export class PeerTubeServer {
|
||||||
port?: number
|
port?: number
|
||||||
|
|
||||||
rtmpPort?: number
|
rtmpPort?: number
|
||||||
|
rtmpsPort?: number
|
||||||
|
|
||||||
parallel?: boolean
|
parallel?: boolean
|
||||||
internalServerNumber: number
|
internalServerNumber: number
|
||||||
|
@ -154,6 +155,7 @@ export class PeerTubeServer {
|
||||||
|
|
||||||
this.internalServerNumber = this.parallel ? this.randomServer() : this.serverNumber
|
this.internalServerNumber = this.parallel ? this.randomServer() : this.serverNumber
|
||||||
this.rtmpPort = this.parallel ? this.randomRTMP() : 1936
|
this.rtmpPort = this.parallel ? this.randomRTMP() : 1936
|
||||||
|
this.rtmpsPort = this.parallel ? this.randomRTMP() : 1937
|
||||||
this.port = 9000 + this.internalServerNumber
|
this.port = 9000 + this.internalServerNumber
|
||||||
|
|
||||||
this.url = `http://localhost:${this.port}`
|
this.url = `http://localhost:${this.port}`
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
export interface LiveVideo {
|
export interface LiveVideo {
|
||||||
rtmpUrl: string
|
rtmpUrl: string
|
||||||
|
rtmpsUrl: string
|
||||||
|
|
||||||
streamKey: string
|
streamKey: string
|
||||||
saveReplay: boolean
|
saveReplay: boolean
|
||||||
permanentLive: boolean
|
permanentLive: boolean
|
||||||
|
|
|
@ -7258,6 +7258,8 @@ components:
|
||||||
properties:
|
properties:
|
||||||
rtmpUrl:
|
rtmpUrl:
|
||||||
type: string
|
type: string
|
||||||
|
rtmpsUrl:
|
||||||
|
type: string
|
||||||
streamKey:
|
streamKey:
|
||||||
type: string
|
type: string
|
||||||
description: RTMP stream key to use to stream into this live video
|
description: RTMP stream key to use to stream into this live video
|
||||||
|
|
Loading…
Reference in New Issue
Block a user