diff --git a/core/ffmpeg/hlsFilesystemCleanup.go b/core/ffmpeg/hlsFilesystemCleanup.go index a8701a02e..6487a40cf 100644 --- a/core/ffmpeg/hlsFilesystemCleanup.go +++ b/core/ffmpeg/hlsFilesystemCleanup.go @@ -10,54 +10,65 @@ import ( "github.com/owncast/owncast/config" ) -// Cleanup will delete old files off disk that are no longer being referenced +// CleanupOldContent will delete old files from the private dir that are no longer being referenced // in the stream. -func Cleanup(directoryPath string) { +func CleanupOldContent(baseDirectory string) { // Determine how many files we should keep on disk maxNumber := config.Config.GetMaxNumberOfReferencedSegmentsInPlaylist() buffer := 10 - files, err := getSegmentFiles(directoryPath) + files, err := getAllFilesRecursive(baseDirectory) if err != nil { log.Fatal(err) } - if len(files) < maxNumber+buffer { - return - } - - // Delete old files on disk - filesToDelete := files[maxNumber+buffer:] - for _, file := range filesToDelete { - os.Remove(filepath.Join(directoryPath, file.Name())) - } -} - -func getSegmentFiles(dirname string) ([]os.FileInfo, error) { - f, err := os.Open(dirname) - if err != nil { - return nil, err - } - list, err := f.Readdir(-1) // -1 says to get a list of all files - f.Close() - if err != nil { - return nil, err - } - - filteredList := make([]os.FileInfo, 0) - - // Filter out playlists because we don't want to clean them up - for _, file := range list { - if filepath.Ext(file.Name()) == ".m3u8" { + // Delete old private HLS files on disk + for directory := range files { + files := files[directory] + if len(files) < maxNumber+buffer { continue } - filteredList = append(filteredList, file) - } - // Sort by date so we can delete old files - sort.Slice(filteredList, func(i, j int) bool { - return filteredList[i].ModTime().UnixNano() > filteredList[j].ModTime().UnixNano() + filesToDelete := files[maxNumber+buffer:] + log.Traceln("Deleting", len(filesToDelete), "old files from", baseDirectory, "for video variant", directory) + + for _, file := range filesToDelete { + fileToDelete := filepath.Join(baseDirectory, directory, file.Name()) + err := os.Remove(fileToDelete) + if err != nil { + log.Errorln(err) + } + } + } +} + +func getAllFilesRecursive(baseDirectory string) (map[string][]os.FileInfo, error) { + var files = make(map[string][]os.FileInfo) + + var directory string + filepath.Walk(baseDirectory, func(path string, info os.FileInfo, err error) error { + if err != nil { + log.Fatalf(err.Error()) + return err + } + + if info.IsDir() { + directory = info.Name() + } + + if filepath.Ext(info.Name()) == ".ts" { + files[directory] = append(files[directory], info) + } + + return nil }) - return filteredList, nil + // Sort by date so we can delete old files + for directory := range files { + sort.Slice(files[directory], func(i, j int) bool { + return files[directory][i].ModTime().UnixNano() > files[directory][j].ModTime().UnixNano() + }) + } + + return files, nil } diff --git a/core/storageproviders/local.go b/core/storageproviders/local.go index 842ef95eb..21d2715d4 100644 --- a/core/storageproviders/local.go +++ b/core/storageproviders/local.go @@ -2,6 +2,7 @@ package storageproviders import ( "path/filepath" + "time" log "github.com/sirupsen/logrus" @@ -13,9 +14,22 @@ import ( type LocalStorage struct { } +// Cleanup old public HLS content every N min from the webroot. +var _onlineCleanupTicker *time.Ticker + // Setup configures this storage provider func (s *LocalStorage) Setup() error { - // no-op + // NOTE: This cleanup timer will have to be disabled to support recordings in the future + // as all HLS segments have to be publicly available on disk to keep a recording of them. + _onlineCleanupTicker = time.NewTicker(1 * time.Minute) + go func() { + for { + select { + case <-_onlineCleanupTicker.C: + ffmpeg.CleanupOldContent(config.PublicHLSStoragePath) + } + } + }() return nil } @@ -49,15 +63,7 @@ func (s *LocalStorage) Save(filePath string, retryCount int) (string, error) { newPath = filepath.Join(config.WebRoot, filePath) } - // Move video segments to the destination directory. - // Copy playlists to the destination directory so they can still be referenced in - // the private hls working directory. - if filepath.Ext(filePath) == ".m3u8" { - utils.Copy(filePath, newPath) - } else { - utils.Move(filePath, newPath) - ffmpeg.Cleanup(filepath.Dir(newPath)) - } + utils.Copy(filePath, newPath) return newPath, nil } diff --git a/core/storageproviders/s3Storage.go b/core/storageproviders/s3Storage.go index 9ea8bc0c1..b5d0c87f4 100644 --- a/core/storageproviders/s3Storage.go +++ b/core/storageproviders/s3Storage.go @@ -6,7 +6,6 @@ import ( "os" "path/filepath" - "github.com/owncast/owncast/core/ffmpeg" "github.com/owncast/owncast/core/playlist" "github.com/owncast/owncast/utils" log "github.com/sirupsen/logrus" @@ -160,8 +159,6 @@ func (s *S3Storage) Save(filePath string, retryCount int) (string, error) { } } - ffmpeg.Cleanup(filepath.Dir(filePath)) - return response.Location, nil } diff --git a/core/streamState.go b/core/streamState.go index 2f0254529..4f06e8291 100644 --- a/core/streamState.go +++ b/core/streamState.go @@ -16,7 +16,11 @@ import ( "github.com/grafov/m3u8" ) -var _cleanupTimer *time.Timer +// After the stream goes offline this timer fires a full cleanup after N min. +var _offlineCleanupTimer *time.Timer + +// While a stream takes place cleanup old HLS content every N min. +var _onlineCleanupTicker *time.Ticker //SetStreamAsConnected sets the stream as connected func SetStreamAsConnected() { @@ -25,6 +29,8 @@ func SetStreamAsConnected() { _stats.LastDisconnectTime = utils.NullTime{time.Now(), false} StopOfflineCleanupTimer() + startOnlineCleanupTimer() + if _yp != nil { _yp.Start() } @@ -112,15 +118,16 @@ func SetStreamAsDisconnected() { } StartOfflineCleanupTimer() + stopOnlineCleanupTimer() } // StartOfflineCleanupTimer will fire a cleanup after n minutes being disconnected func StartOfflineCleanupTimer() { - _cleanupTimer = time.NewTimer(5 * time.Minute) + _offlineCleanupTimer = time.NewTimer(5 * time.Minute) go func() { for { select { - case <-_cleanupTimer.C: + case <-_offlineCleanupTimer.C: // Reset the session count since the session is over _stats.SessionMaxViewerCount = 0 resetDirectories() @@ -132,7 +139,25 @@ func StartOfflineCleanupTimer() { // StopOfflineCleanupTimer will stop the previous cleanup timer func StopOfflineCleanupTimer() { - if _cleanupTimer != nil { - _cleanupTimer.Stop() + if _offlineCleanupTimer != nil { + _offlineCleanupTimer.Stop() + } +} + +func startOnlineCleanupTimer() { + _onlineCleanupTicker = time.NewTicker(1 * time.Minute) + go func() { + for { + select { + case <-_onlineCleanupTicker.C: + ffmpeg.CleanupOldContent(config.PrivateHLSStoragePath) + } + } + }() +} + +func stopOnlineCleanupTimer() { + if _onlineCleanupTicker != nil { + _onlineCleanupTicker.Stop() } }