Fixed offline video state to use fMP4 segments instead of launching ffmpeg with the old TS file.
This commit is contained in:
+35
-10
@@ -1,6 +1,7 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@@ -98,23 +99,47 @@ func createInitialOfflineState() error {
|
||||
// offline video stream state only. No live stream HLS segments will continue to be
|
||||
// referenced.
|
||||
func transitionToOfflineVideoStreamContent() {
|
||||
log.Traceln("Firing transcoder with offline stream state")
|
||||
log.Traceln("Placing offline fMP4 content into HLS directories")
|
||||
|
||||
_transcoder := transcoder.NewTranscoder()
|
||||
_transcoder.SetIdentifier("offline")
|
||||
_transcoder.SetLatencyLevel(models.GetLatencyLevel(4))
|
||||
_transcoder.SetIsEvent(true)
|
||||
configRepository := configrepository.Get()
|
||||
|
||||
offlineFilePath, err := saveOfflineClipToDisk("offline-v2.ts")
|
||||
offlineInitPath, offlineSegmentPath, err := saveOfflineFMP4ToDisk()
|
||||
if err != nil {
|
||||
log.Fatalln("unable to save offline clip:", err)
|
||||
log.Fatalln("unable to save offline fMP4 files:", err)
|
||||
}
|
||||
|
||||
_transcoder.SetInput(offlineFilePath)
|
||||
go _transcoder.Start(false)
|
||||
variants := configRepository.GetStreamOutputVariants()
|
||||
if len(variants) == 0 {
|
||||
// Ensure at least one variant directory exists.
|
||||
variants = make([]models.StreamOutputVariant, 1)
|
||||
}
|
||||
|
||||
for index := range variants {
|
||||
variantDir := filepath.Join(config.HLSStoragePath, fmt.Sprintf("%d", index))
|
||||
if err := os.MkdirAll(variantDir, 0o750); err != nil {
|
||||
log.Errorln("unable to create variant directory:", err)
|
||||
continue
|
||||
}
|
||||
makeVariantIndexOffline(index, offlineInitPath, offlineSegmentPath)
|
||||
}
|
||||
|
||||
// Write the master playlist that points to each variant's playlist.
|
||||
masterPlaylistPath := filepath.Join(config.HLSStoragePath, "stream.m3u8")
|
||||
masterFile, err := os.Create(masterPlaylistPath) //nolint:gosec
|
||||
if err != nil {
|
||||
log.Errorln("unable to create master playlist:", err)
|
||||
} else {
|
||||
defer masterFile.Close()
|
||||
_, _ = masterFile.WriteString("#EXTM3U\n")
|
||||
_, _ = masterFile.WriteString("#EXT-X-VERSION:7\n")
|
||||
_, _ = masterFile.WriteString("#EXT-X-INDEPENDENT-SEGMENTS\n")
|
||||
for index := range variants {
|
||||
_, _ = fmt.Fprintf(masterFile, "#EXT-X-STREAM-INF:BANDWIDTH=0\n")
|
||||
_, _ = fmt.Fprintf(masterFile, "%d/stream.m3u8\n", index)
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the logo to be the thumbnail
|
||||
configRepository := configrepository.Get()
|
||||
logo := configRepository.GetLogoPath()
|
||||
dst := filepath.Join(config.TempDir, "thumbnail.jpg")
|
||||
if err = utils.Copy(filepath.Join("data", logo), dst); err != nil {
|
||||
|
||||
+34
-40
@@ -5,14 +5,13 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/grafov/m3u8"
|
||||
"github.com/owncast/owncast/config"
|
||||
"github.com/owncast/owncast/static"
|
||||
"github.com/owncast/owncast/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func appendOfflineToVariantPlaylist(index int, playlistFilePath string) {
|
||||
func appendOfflineToVariantPlaylist(index int, playlistFilePath string, initFilename string, segmentFilename string) {
|
||||
existingPlaylistContents, err := os.ReadFile(playlistFilePath) // nolint: gosec
|
||||
if err != nil {
|
||||
log.Debugln("unable to read existing playlist file", err)
|
||||
@@ -34,9 +33,10 @@ func appendOfflineToVariantPlaylist(index int, playlistFilePath string) {
|
||||
|
||||
// Manually append the offline clip to the end of the media playlist.
|
||||
_, _ = atomicWriteTmpPlaylistFile.WriteString("#EXT-X-DISCONTINUITY\n")
|
||||
_, _ = fmt.Fprintf(atomicWriteTmpPlaylistFile, "#EXT-X-MAP:URI=\"%s\"\n", initFilename)
|
||||
// If "offline" content gets changed then change the duration below
|
||||
_, _ = atomicWriteTmpPlaylistFile.WriteString("#EXTINF:8.000000,\n")
|
||||
_, _ = atomicWriteTmpPlaylistFile.WriteString("offline-v2.ts\n")
|
||||
_, _ = fmt.Fprintf(atomicWriteTmpPlaylistFile, "%s\n", segmentFilename)
|
||||
_, _ = atomicWriteTmpPlaylistFile.WriteString("#EXT-X-ENDLIST\n")
|
||||
|
||||
if err := atomicWriteTmpPlaylistFile.Close(); err != nil {
|
||||
@@ -48,48 +48,57 @@ func appendOfflineToVariantPlaylist(index int, playlistFilePath string) {
|
||||
}
|
||||
}
|
||||
|
||||
func makeVariantIndexOffline(index int, offlineFilePath string, offlineFilename string) {
|
||||
playlistFilePath := fmt.Sprintf(filepath.Join(config.HLSStoragePath, "%d/stream.m3u8"), index)
|
||||
segmentFilePath := fmt.Sprintf(filepath.Join(config.HLSStoragePath, "%d/%s"), index, offlineFilename)
|
||||
func makeVariantIndexOffline(index int, offlineInitPath string, offlineSegmentPath string) {
|
||||
variantDir := filepath.Join(config.HLSStoragePath, fmt.Sprintf("%d", index))
|
||||
playlistFilePath := filepath.Join(variantDir, "stream.m3u8")
|
||||
|
||||
if err := utils.Copy(offlineFilePath, segmentFilePath); err != nil {
|
||||
initDest := filepath.Join(variantDir, "offline-init.mp4")
|
||||
segmentDest := filepath.Join(variantDir, "offline-v2.m4s")
|
||||
|
||||
if err := utils.Copy(offlineInitPath, initDest); err != nil {
|
||||
log.Warnln(err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := _storage.Save(initDest, 0); err != nil {
|
||||
log.Warnln(err)
|
||||
}
|
||||
|
||||
if _, err := _storage.Save(segmentFilePath, 0); err != nil {
|
||||
if err := utils.Copy(offlineSegmentPath, segmentDest); err != nil {
|
||||
log.Warnln(err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := _storage.Save(segmentDest, 0); err != nil {
|
||||
log.Warnln(err)
|
||||
}
|
||||
|
||||
if utils.DoesFileExists(playlistFilePath) {
|
||||
appendOfflineToVariantPlaylist(index, playlistFilePath)
|
||||
appendOfflineToVariantPlaylist(index, playlistFilePath, "offline-init.mp4", "offline-v2.m4s")
|
||||
} else {
|
||||
createEmptyOfflinePlaylist(playlistFilePath, offlineFilename)
|
||||
createEmptyOfflinePlaylist(playlistFilePath, "offline-init.mp4", "offline-v2.m4s")
|
||||
}
|
||||
if _, err := _storage.Save(playlistFilePath, 0); err != nil {
|
||||
log.Warnln(err)
|
||||
}
|
||||
}
|
||||
|
||||
func createEmptyOfflinePlaylist(playlistFilePath string, offlineFilename string) {
|
||||
p, err := m3u8.NewMediaPlaylist(1, 1)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
}
|
||||
|
||||
// If "offline" content gets changed then change the duration below
|
||||
if err := p.Append(offlineFilename, 8.0, ""); err != nil {
|
||||
log.Errorln(err)
|
||||
}
|
||||
|
||||
p.Close()
|
||||
func createEmptyOfflinePlaylist(playlistFilePath string, initFilename string, segmentFilename string) {
|
||||
f, err := os.Create(playlistFilePath) //nolint:gosec
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err := f.Write(p.Encode().Bytes()); err != nil {
|
||||
log.Errorln(err)
|
||||
}
|
||||
|
||||
_, _ = f.WriteString("#EXTM3U\n")
|
||||
_, _ = f.WriteString("#EXT-X-VERSION:7\n")
|
||||
_, _ = f.WriteString("#EXT-X-TARGETDURATION:8\n")
|
||||
_, _ = fmt.Fprintf(f, "#EXT-X-MAP:URI=\"%s\"\n", initFilename)
|
||||
// If "offline" content gets changed then change the duration below
|
||||
_, _ = f.WriteString("#EXTINF:8.000000,\n")
|
||||
_, _ = fmt.Fprintf(f, "%s\n", segmentFilename)
|
||||
_, _ = f.WriteString("#EXT-X-ENDLIST\n")
|
||||
}
|
||||
|
||||
func saveOfflineFMP4ToDisk() (initPath string, segmentPath string, err error) {
|
||||
@@ -121,18 +130,3 @@ func saveOfflineFMP4ToDisk() (initPath string, segmentPath string, err error) {
|
||||
|
||||
return initPath, segmentPath, nil
|
||||
}
|
||||
|
||||
func saveOfflineClipToDisk(offlineFilename string) (string, error) {
|
||||
offlineFileData := static.GetOfflineSegment()
|
||||
offlineTmpFile, err := os.CreateTemp(config.TempDir, offlineFilename)
|
||||
if err != nil {
|
||||
log.Errorln("unable to create temp file for offline video segment", err)
|
||||
}
|
||||
|
||||
if _, err = offlineTmpFile.Write(offlineFileData); err != nil {
|
||||
return "", fmt.Errorf("unable to write offline segment to disk: %s", err)
|
||||
}
|
||||
|
||||
offlineFilePath := offlineTmpFile.Name()
|
||||
return filepath.Abs(offlineFilePath)
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ func getAllFilesRecursive(baseDirectory string) (map[string][]os.FileInfo, error
|
||||
directory = info.Name()
|
||||
}
|
||||
|
||||
if filepath.Ext(info.Name()) == ".m4s" {
|
||||
if filepath.Ext(info.Name()) == ".m4s" || filepath.Ext(info.Name()) == ".mp4" {
|
||||
files[directory] = append(files[directory], info)
|
||||
}
|
||||
|
||||
|
||||
@@ -317,7 +317,7 @@ func (s *S3Storage) retrieveAllVideoSegments() ([]s3object, error) {
|
||||
err := s.s3Client.ListObjectsPages(allObjectsListRequest,
|
||||
func(page *s3.ListObjectsOutput, lastPage bool) bool {
|
||||
for _, item := range page.Contents {
|
||||
if strings.HasSuffix(*item.Key, ".m4s") {
|
||||
if strings.HasSuffix(*item.Key, ".m4s") || strings.HasSuffix(*item.Key, ".mp4") {
|
||||
allObjects = append(allObjects, s3object{
|
||||
key: *item.Key,
|
||||
lastModified: *item.LastModified,
|
||||
|
||||
Vendored
BIN
Binary file not shown.
Vendored
-8
@@ -51,14 +51,6 @@ func GetWebIndexTemplate() (*template.Template, error) {
|
||||
return tmpl, err
|
||||
}
|
||||
|
||||
//go:embed offline-v2.ts
|
||||
var offlineVideoSegment []byte
|
||||
|
||||
// GetOfflineSegment will return the offline video segment data.
|
||||
func GetOfflineSegment() []byte {
|
||||
return getFileSystemStaticFileOrDefault("offline-v2.ts", offlineVideoSegment)
|
||||
}
|
||||
|
||||
//go:embed offline-init.mp4
|
||||
var offlineInitSegment []byte
|
||||
|
||||
|
||||
+1
-1
@@ -221,7 +221,7 @@ func GetCacheDurationSecondsForPath(filePath string) int {
|
||||
} else if fileExtension == ".js" || fileExtension == ".css" {
|
||||
// Cache javascript & CSS
|
||||
return 60 * 60 * 24 * defaultDaysCached
|
||||
} else if fileExtension == ".ts" || fileExtension == ".woff2" {
|
||||
} else if fileExtension == ".m4s" || fileExtension == ".mp4" || fileExtension == ".woff2" {
|
||||
// Cache video segments as long as you want. They can't change.
|
||||
// This matters most for local hosting of segments for recordings
|
||||
// and not for live or 3rd party storage.
|
||||
|
||||
Reference in New Issue
Block a user