160 lines
4.2 KiB
Go
160 lines
4.2 KiB
Go
package playlist
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"path"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/radovskyb/watcher"
|
|
|
|
"github.com/gabek/owncast/config"
|
|
"github.com/gabek/owncast/models"
|
|
"github.com/gabek/owncast/utils"
|
|
)
|
|
|
|
var (
|
|
_storage models.ChunkStorageProvider
|
|
variants []models.Variant
|
|
)
|
|
|
|
//StartVideoContentMonitor starts the video content monitor
|
|
func StartVideoContentMonitor(storage models.ChunkStorageProvider) error {
|
|
_storage = storage
|
|
|
|
pathToMonitor := config.Config.GetPrivateHLSSavePath()
|
|
|
|
// Create at least one structure to store the segments for the different stream variants
|
|
variants = make([]models.Variant, len(config.Config.VideoSettings.StreamQualities))
|
|
if len(config.Config.VideoSettings.StreamQualities) > 0 {
|
|
for index := range variants {
|
|
variants[index] = models.Variant{
|
|
VariantIndex: index,
|
|
Segments: make(map[string]*models.Segment),
|
|
}
|
|
}
|
|
} else {
|
|
variants[0] = models.Variant{
|
|
VariantIndex: 0,
|
|
Segments: make(map[string]*models.Segment),
|
|
}
|
|
}
|
|
|
|
// log.Printf("Using directory %s for storing files with %d variants...\n", pathToMonitor, len(variants))
|
|
|
|
w := watcher.New()
|
|
|
|
go func() {
|
|
for {
|
|
select {
|
|
case event := <-w.Event:
|
|
|
|
relativePath := utils.GetRelativePathFromAbsolutePath(event.Path)
|
|
if path.Ext(relativePath) == ".tmp" {
|
|
continue
|
|
}
|
|
|
|
// Ignore removals
|
|
if event.Op == watcher.Remove {
|
|
continue
|
|
}
|
|
|
|
// fmt.Println(event.Op, relativePath)
|
|
|
|
// Handle updates to the master playlist by copying it to webroot
|
|
if relativePath == path.Join(config.Config.GetPrivateHLSSavePath(), "stream.m3u8") {
|
|
utils.Copy(event.Path, path.Join(config.Config.GetPublicHLSSavePath(), "stream.m3u8"))
|
|
|
|
} else if filepath.Ext(event.Path) == ".m3u8" {
|
|
// Handle updates to playlists, but not the master playlist
|
|
updateVariantPlaylist(event.Path)
|
|
|
|
} else if filepath.Ext(event.Path) == ".ts" {
|
|
segment, err := getSegmentFromPath(event.Path)
|
|
if err != nil {
|
|
log.Error("failed to get the segment from path")
|
|
panic(err)
|
|
}
|
|
|
|
newObjectPathChannel := make(chan string, 1)
|
|
go func() {
|
|
newObjectPath, err := storage.Save(path.Join(config.Config.GetPrivateHLSSavePath(), segment.RelativeUploadPath), 0)
|
|
if err != nil {
|
|
log.Errorln("failed to save the file to the chunk storage.", err)
|
|
}
|
|
|
|
newObjectPathChannel <- newObjectPath
|
|
}()
|
|
|
|
newObjectPath := <-newObjectPathChannel
|
|
segment.RemoteID = newObjectPath
|
|
// fmt.Println("Uploaded", segment.RelativeUploadPath, "as", newObjectPath)
|
|
|
|
variants[segment.VariantIndex].Segments[filepath.Base(segment.RelativeUploadPath)] = &segment
|
|
|
|
// Force a variant's playlist to be updated after a file is uploaded.
|
|
associatedVariantPlaylist := strings.ReplaceAll(event.Path, path.Base(event.Path), "stream.m3u8")
|
|
updateVariantPlaylist(associatedVariantPlaylist)
|
|
}
|
|
case err := <-w.Error:
|
|
panic(err)
|
|
case <-w.Closed:
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Watch the hls segment storage folder recursively for changes.
|
|
w.FilterOps(watcher.Write, watcher.Rename, watcher.Create)
|
|
|
|
if err := w.AddRecursive(pathToMonitor); err != nil {
|
|
return err
|
|
}
|
|
|
|
return w.Start(time.Millisecond * 200)
|
|
}
|
|
|
|
func getSegmentFromPath(fullDiskPath string) (models.Segment, error) {
|
|
segment := models.Segment{
|
|
FullDiskPath: fullDiskPath,
|
|
RelativeUploadPath: utils.GetRelativePathFromAbsolutePath(fullDiskPath),
|
|
}
|
|
|
|
index, err := strconv.Atoi(segment.RelativeUploadPath[0:1])
|
|
if err != nil {
|
|
return segment, err
|
|
}
|
|
|
|
segment.VariantIndex = index
|
|
|
|
return segment, nil
|
|
}
|
|
|
|
func getVariantIndexFromPath(fullDiskPath string) (int, error) {
|
|
return strconv.Atoi(fullDiskPath[0:1])
|
|
}
|
|
|
|
func updateVariantPlaylist(fullPath string) error {
|
|
relativePath := utils.GetRelativePathFromAbsolutePath(fullPath)
|
|
variantIndex, err := getVariantIndexFromPath(relativePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
variant := variants[variantIndex]
|
|
|
|
playlistBytes, err := ioutil.ReadFile(fullPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
playlistString := string(playlistBytes)
|
|
playlistString = _storage.GenerateRemotePlaylist(playlistString, variant)
|
|
|
|
return WritePlaylist(playlistString, path.Join(config.Config.GetPublicHLSSavePath(), relativePath))
|
|
}
|