mirror of
https://github.com/owncast/owncast.git
synced 2024-11-21 20:28:15 +03:00
Stitch in an offline slate when the RTMP stream is dropped
This commit is contained in:
parent
44058c24aa
commit
5bc3555206
4 changed files with 92 additions and 14 deletions
18
config.go
18
config.go
|
@ -10,14 +10,15 @@ import (
|
|||
|
||||
// Config struct
|
||||
type Config struct {
|
||||
IPFS IPFS `yaml:"ipfs"`
|
||||
PublicHLSPath string `yaml:"publicHLSPath"`
|
||||
PrivateHLSPath string `yaml:"privateHLSPath"`
|
||||
VideoSettings VideoSettings `yaml:"videoSettings"`
|
||||
Files Files `yaml:"files"`
|
||||
FFMpegPath string `yaml:"ffmpegPath"`
|
||||
WebServerPort int `yaml:"webServerPort"`
|
||||
S3 S3 `yaml:"s3"`
|
||||
IPFS IPFS `yaml:"ipfs"`
|
||||
PublicHLSPath string `yaml:"publicHLSPath"`
|
||||
PrivateHLSPath string `yaml:"privateHLSPath"`
|
||||
VideoSettings VideoSettings `yaml:"videoSettings"`
|
||||
Files Files `yaml:"files"`
|
||||
FFMpegPath string `yaml:"ffmpegPath"`
|
||||
WebServerPort int `yaml:"webServerPort"`
|
||||
S3 S3 `yaml:"s3"`
|
||||
EnableOfflineImage bool `yaml:"enableOfflineImage"`
|
||||
}
|
||||
|
||||
type VideoSettings struct {
|
||||
|
@ -26,6 +27,7 @@ type VideoSettings struct {
|
|||
EncoderPreset string `yaml:"encoderPreset"`
|
||||
StreamQualities []StreamQuality `yaml:"streamQualities"`
|
||||
EnablePassthrough bool `yaml:"passthrough"`
|
||||
OfflineImage string `yaml:"offlineImage"`
|
||||
}
|
||||
|
||||
type StreamQuality struct {
|
||||
|
|
|
@ -2,12 +2,14 @@ publicHLSPath: webroot/hls
|
|||
privateHLSPath: hls
|
||||
ffmpegPath: /usr/local/bin/ffmpeg
|
||||
webServerPort: 8080
|
||||
enableOfflineImage: true
|
||||
|
||||
videoSettings:
|
||||
chunkLengthInSeconds: 4
|
||||
streamingKey: abc123
|
||||
encoderPreset: superfast # https://trac.ffmpeg.org/wiki/Encode/H.264
|
||||
passthrough: true # Enabling this will ignore the below stream qualities and pass through the same quality that you're sending it
|
||||
offlineImage: doc/logo.png # Is displayed when a stream ends
|
||||
|
||||
streamQualities:
|
||||
- bitrate: 1000 # in k
|
||||
|
|
83
ffmpeg.go
83
ffmpeg.go
|
@ -11,6 +11,83 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
func showStreamOfflineState(configuration Config) {
|
||||
fmt.Println("----- Stream offline! Showing offline state!")
|
||||
|
||||
var outputDir = configuration.PublicHLSPath
|
||||
var variantPlaylistPath = configuration.PublicHLSPath
|
||||
|
||||
if configuration.IPFS.Enabled || configuration.S3.Enabled {
|
||||
outputDir = configuration.PrivateHLSPath
|
||||
variantPlaylistPath = configuration.PrivateHLSPath
|
||||
}
|
||||
|
||||
outputDir = path.Join(outputDir, "%v")
|
||||
var variantPlaylistName = path.Join(variantPlaylistPath, "%v", "stream.m3u8")
|
||||
|
||||
var videoMaps = make([]string, 0)
|
||||
var streamMaps = make([]string, 0)
|
||||
var videoMapsString = ""
|
||||
var streamMappingString = ""
|
||||
if configuration.VideoSettings.EnablePassthrough || len(configuration.VideoSettings.StreamQualities) == 0 {
|
||||
fmt.Println("Enabling passthrough video")
|
||||
streamMaps = append(streamMaps, fmt.Sprintf("v:%d", 0))
|
||||
} else {
|
||||
for index, quality := range configuration.VideoSettings.StreamQualities {
|
||||
maxRate := math.Floor(float64(quality.Bitrate) * 0.8)
|
||||
videoMaps = append(videoMaps, fmt.Sprintf("-map v:0 -c:v:%d libx264 -b:v:%d %dk -maxrate %dk -bufsize %dk", index, index, int(quality.Bitrate), int(maxRate), int(maxRate)))
|
||||
streamMaps = append(streamMaps, fmt.Sprintf("v:%d", index))
|
||||
videoMapsString = strings.Join(videoMaps, " ")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
framerate := 25
|
||||
|
||||
streamMappingString = "-var_stream_map \"" + strings.Join(streamMaps, " ") + "\""
|
||||
|
||||
// ffmpeg -hide_banner -stream_loop 500 -i doc/logo.png -c:v libx264 -map v:0 -c:v:0 libx264 -b:v:0 1500k -f hls -master_pl_name stream.m3u8 -use_localtime 1 -hls_flags program_date_time+temp_file+append_list -tune zerolatency -var_stream_map "v:0" -hls_segment_filename hls/%v/stream-%Y%m%d-%s.ts hls/%v/stream.m3u8
|
||||
|
||||
ffmpegFlags := []string{
|
||||
"-hide_banner",
|
||||
"-stream_loop 5000",
|
||||
"-i", configuration.VideoSettings.OfflineImage,
|
||||
videoMapsString, // All the different video variants
|
||||
"-f hls",
|
||||
"-hls_list_size " + strconv.Itoa(configuration.Files.MaxNumberInPlaylist),
|
||||
"-hls_time " + strconv.Itoa(configuration.VideoSettings.ChunkLengthInSeconds),
|
||||
"-strftime 1",
|
||||
"-use_localtime 1",
|
||||
"-hls_playlist_type", "event",
|
||||
"-master_pl_name", "stream.m3u8",
|
||||
"-use_localtime 1",
|
||||
"-hls_flags program_date_time+temp_file",
|
||||
"-tune", "zerolatency",
|
||||
"-framerate " + strconv.Itoa(framerate),
|
||||
"-g " + strconv.Itoa(framerate*2), " -keyint_min " + strconv.Itoa(framerate*2), // multiply your output frame rate * 2. For example, if your input is -framerate 30, then use -g 60
|
||||
"-preset " + configuration.VideoSettings.EncoderPreset,
|
||||
"-sc_threshold 0", // don't create key frames on scene change - only according to -g
|
||||
"-profile:v", "high", // Main – for standard definition (SD) to 640×480, High – for high definition (HD) to 1920×1080
|
||||
"-movflags +faststart",
|
||||
"-pix_fmt yuv420p",
|
||||
|
||||
streamMappingString,
|
||||
"-hls_segment_filename " + path.Join(outputDir, "offline-%Y%m%d-%s.ts"),
|
||||
variantPlaylistName,
|
||||
}
|
||||
|
||||
ffmpegFlagsString := strings.Join(ffmpegFlags, " ")
|
||||
|
||||
ffmpegCmd := configuration.FFMpegPath + " " + ffmpegFlagsString
|
||||
|
||||
fmt.Println(ffmpegCmd)
|
||||
|
||||
_, err := exec.Command("sh", "-c", ffmpegCmd).Output()
|
||||
fmt.Println(err)
|
||||
verifyError(err)
|
||||
|
||||
}
|
||||
|
||||
func startFfmpeg(configuration Config) {
|
||||
var outputDir = configuration.PublicHLSPath
|
||||
var variantPlaylistPath = configuration.PublicHLSPath
|
||||
|
@ -21,13 +98,7 @@ func startFfmpeg(configuration Config) {
|
|||
}
|
||||
|
||||
outputDir = path.Join(outputDir, "%v")
|
||||
|
||||
// var masterPlaylistName = path.Join(configuration.PublicHLSPath, "%v", "stream.m3u8")
|
||||
var variantPlaylistName = path.Join(variantPlaylistPath, "%v", "stream.m3u8")
|
||||
// var variantRootPath = configuration.PublicHLSPath
|
||||
|
||||
// variantRootPath = path.Join(variantRootPath, "%v")
|
||||
// variantPlaylistName := path.Join("%v", "stream.m3u8")
|
||||
|
||||
log.Printf("Starting transcoder saving to /%s.", variantPlaylistName)
|
||||
pipePath := getTempPipePath()
|
||||
|
|
3
main.go
3
main.go
|
@ -95,6 +95,9 @@ func streamConnected() {
|
|||
|
||||
func streamDisconnected() {
|
||||
stats.StreamDisconnected()
|
||||
if configuration.EnableOfflineImage {
|
||||
showStreamOfflineState(configuration)
|
||||
}
|
||||
}
|
||||
|
||||
func viewerAdded(clientID string) {
|
||||
|
|
Loading…
Reference in a new issue