diff --git a/config/constants.go b/config/constants.go index 9d782241e..08c8a4ebf 100644 --- a/config/constants.go +++ b/config/constants.go @@ -7,8 +7,6 @@ const ( StaticVersionNumber = "0.0.9" // Shown when you build from develop // WebRoot is the web server root directory. WebRoot = "webroot" - // PrivateHLSStoragePath is the HLS write directory. - PrivateHLSStoragePath = "hls" // FfmpegSuggestedVersion is the version of ffmpeg we suggest. FfmpegSuggestedVersion = "v4.1.5" // Requires the v // DataDirectory is the directory we save data to. @@ -18,8 +16,9 @@ const ( ) var ( - // PublicHLSStoragePath is the directory we write public HLS files to for distribution. - PublicHLSStoragePath = filepath.Join(WebRoot, "hls") // BackupDirectory is the directory we write backup files to. BackupDirectory = filepath.Join(DataDirectory, "backup") + + // HLSStoragePath is the directory HLS video is written to. + HLSStoragePath = filepath.Join(DataDirectory, "hls") ) diff --git a/controllers/hls.go b/controllers/hls.go new file mode 100644 index 000000000..137d4c02a --- /dev/null +++ b/controllers/hls.go @@ -0,0 +1,50 @@ +package controllers + +import ( + "net/http" + "path" + "path/filepath" + "strconv" + "strings" + + "github.com/owncast/owncast/config" + "github.com/owncast/owncast/core" + "github.com/owncast/owncast/core/data" + "github.com/owncast/owncast/router/middleware" + "github.com/owncast/owncast/utils" +) + +// HandleHLSRequest will manage all requests to HLS content. +func HandleHLSRequest(w http.ResponseWriter, r *http.Request) { + // Sanity check to limit requests to HLS file types. + if filepath.Ext(r.URL.Path) != ".m3u8" && filepath.Ext(r.URL.Path) != ".ts" { + w.WriteHeader(http.StatusNotFound) + return + } + + requestedPath := r.URL.Path + relativePath := strings.Replace(requestedPath, "/hls/", "", 1) + fullPath := filepath.Join(config.HLSStoragePath, relativePath) + + // If using external storage then only allow requests for the + // master playlist at stream.m3u8, no variants or segments. + if data.GetS3Config().Enabled && relativePath != "stream.m3u8" { + w.WriteHeader(http.StatusNotFound) + return + } + + // Handle playlists + if path.Ext(r.URL.Path) == ".m3u8" { + // Playlists should never be cached. + middleware.DisableCache(w) + + // Use this as an opportunity to mark this viewer as active. + id := utils.GenerateClientIDFromRequest(r) + core.SetViewerIDActive(id) + } else { + cacheTime := utils.GetCacheDurationSecondsForPath(relativePath) + w.Header().Set("Cache-Control", "public, max-age="+strconv.Itoa(cacheTime)) + } + + http.ServeFile(w, r, fullPath) +} diff --git a/controllers/index.go b/controllers/index.go index 28a0c7d6e..59eee5fed 100644 --- a/controllers/index.go +++ b/controllers/index.go @@ -62,14 +62,6 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) { return } - if path.Ext(r.URL.Path) == ".m3u8" { - middleware.DisableCache(w) - - // Use this as an opportunity to mark this viewer as active. - id := utils.GenerateClientIDFromRequest(r) - core.SetViewerIDActive(id) - } - // Set a cache control max-age header middleware.SetCachingHeaders(w, r) diff --git a/core/core.go b/core/core.go index 6a1fb1884..763da6367 100644 --- a/core/core.go +++ b/core/core.go @@ -118,9 +118,8 @@ func transitionToOfflineVideoStreamContent() { func resetDirectories() { log.Trace("Resetting file directories to a clean slate.") - // Wipe the public, web-accessible hls data directory - utils.CleanupDirectory(config.PublicHLSStoragePath) - utils.CleanupDirectory(config.PrivateHLSStoragePath) + // Wipe hls data directory + utils.CleanupDirectory(config.HLSStoragePath) // Remove the previous thumbnail logo := data.GetLogoPath() diff --git a/core/storageproviders/local.go b/core/storageproviders/local.go index ee75b178b..b45e90ca4 100644 --- a/core/storageproviders/local.go +++ b/core/storageproviders/local.go @@ -1,14 +1,12 @@ package storageproviders import ( - "path/filepath" "time" log "github.com/sirupsen/logrus" "github.com/owncast/owncast/config" "github.com/owncast/owncast/core/transcoder" - "github.com/owncast/owncast/utils" ) // LocalStorage represents an instance of the local storage provider for HLS video. @@ -25,7 +23,7 @@ func (s *LocalStorage) Setup() error { _onlineCleanupTicker = time.NewTicker(1 * time.Minute) go func() { for range _onlineCleanupTicker.C { - transcoder.CleanupOldContent(config.PublicHLSStoragePath) + transcoder.CleanupOldContent(config.HLSStoragePath) } }() return nil @@ -55,15 +53,5 @@ func (s *LocalStorage) MasterPlaylistWritten(localFilePath string) { // Save will save a local filepath using the storage provider. func (s *LocalStorage) Save(filePath string, retryCount int) (string, error) { - newPath := "" - - // This is a hack - if filePath == "hls/stream.m3u8" { - newPath = filepath.Join(config.PublicHLSStoragePath, filepath.Base(filePath)) - } else { - newPath = filepath.Join(config.WebRoot, filePath) - } - - err := utils.Copy(filePath, newPath) - return newPath, err + return filePath, nil } diff --git a/core/storageproviders/s3Storage.go b/core/storageproviders/s3Storage.go index bcee60a28..7bb014a08 100644 --- a/core/storageproviders/s3Storage.go +++ b/core/storageproviders/s3Storage.go @@ -199,7 +199,7 @@ func (s *S3Storage) rewriteRemotePlaylist(filePath string) error { item.URI = s.host + filepath.Join("/hls", item.URI) } - publicPath := filepath.Join(config.PublicHLSStoragePath, filepath.Base(filePath)) + publicPath := filepath.Join(config.HLSStoragePath, filepath.Base(filePath)) newPlaylist := p.String() diff --git a/core/streamState.go b/core/streamState.go index 0c54a948c..9e48f3f4c 100644 --- a/core/streamState.go +++ b/core/streamState.go @@ -50,17 +50,12 @@ func setStreamAsConnected(rtmpOut *io.PipeReader) { go _yp.Start() } - segmentPath := config.PublicHLSStoragePath - s3Config := data.GetS3Config() + segmentPath := config.HLSStoragePath if err := setupStorage(); err != nil { log.Fatalln("failed to setup the storage", err) } - if s3Config.Enabled { - segmentPath = config.PrivateHLSStoragePath - } - go func() { _transcoder = transcoder.NewTranscoder() _transcoder.TranscoderCompleted = func(error) { @@ -100,8 +95,8 @@ func SetStreamAsDisconnected() { } for index := range _currentBroadcast.OutputSettings { - playlistFilePath := fmt.Sprintf(filepath.Join(config.PrivateHLSStoragePath, "%d/stream.m3u8"), index) - segmentFilePath := fmt.Sprintf(filepath.Join(config.PrivateHLSStoragePath, "%d/%s"), index, offlineFilename) + playlistFilePath := fmt.Sprintf(filepath.Join(config.HLSStoragePath, "%d/stream.m3u8"), index) + segmentFilePath := fmt.Sprintf(filepath.Join(config.HLSStoragePath, "%d/%s"), index, offlineFilename) if err := utils.Copy(offlineFilePath, segmentFilePath); err != nil { log.Warnln(err) @@ -191,7 +186,7 @@ func startOnlineCleanupTimer() { _onlineCleanupTicker = time.NewTicker(1 * time.Minute) go func() { for range _onlineCleanupTicker.C { - transcoder.CleanupOldContent(config.PrivateHLSStoragePath) + transcoder.CleanupOldContent(config.HLSStoragePath) } }() } diff --git a/core/transcoder/fileWriterReceiverService.go b/core/transcoder/fileWriterReceiverService.go index 02e983d4f..e974cc08d 100644 --- a/core/transcoder/fileWriterReceiverService.go +++ b/core/transcoder/fileWriterReceiverService.go @@ -59,7 +59,7 @@ func (s *FileWriterReceiverService) uploadHandler(w http.ResponseWriter, r *http } path := r.URL.Path - writePath := filepath.Join(config.PrivateHLSStoragePath, path) + writePath := filepath.Join(config.HLSStoragePath, path) var buf bytes.Buffer defer r.Body.Close() diff --git a/core/transcoder/transcoder.go b/core/transcoder/transcoder.go index 697ebc927..a47bd217d 100644 --- a/core/transcoder/transcoder.go +++ b/core/transcoder/transcoder.go @@ -233,19 +233,8 @@ func NewTranscoder() *Transcoder { transcoder.currentStreamOutputSettings = data.GetStreamOutputVariants() transcoder.currentLatencyLevel = data.GetStreamLatencyLevel() transcoder.codec = getCodec(data.GetVideoCodec()) - - var outputPath string - if data.GetS3Config().Enabled { - // Segments are not available via the local HTTP server - outputPath = config.PrivateHLSStoragePath - } else { - // Segments are available via the local HTTP server - outputPath = config.PublicHLSStoragePath - } - - transcoder.segmentOutputPath = outputPath - // Playlists are available via the local HTTP server - transcoder.playlistOutputPath = config.PublicHLSStoragePath + transcoder.segmentOutputPath = config.HLSStoragePath + transcoder.playlistOutputPath = config.HLSStoragePath transcoder.input = "pipe:0" // stdin diff --git a/core/transcoder/utils.go b/core/transcoder/utils.go index a124002d9..83d6676df 100644 --- a/core/transcoder/utils.go +++ b/core/transcoder/utils.go @@ -94,27 +94,16 @@ func handleTranscoderMessage(message string) { func createVariantDirectories() { // Create private hls data dirs - utils.CleanupDirectory(config.PublicHLSStoragePath) - utils.CleanupDirectory(config.PrivateHLSStoragePath) + utils.CleanupDirectory(config.HLSStoragePath) if len(data.GetStreamOutputVariants()) != 0 { for index := range data.GetStreamOutputVariants() { - if err := os.MkdirAll(path.Join(config.PrivateHLSStoragePath, strconv.Itoa(index)), 0750); err != nil { - log.Fatalln(err) - } - dir := path.Join(config.PublicHLSStoragePath, strconv.Itoa(index)) - log.Traceln("Creating", dir) - if err := os.MkdirAll(dir, 0750); err != nil { + if err := os.MkdirAll(path.Join(config.HLSStoragePath, strconv.Itoa(index)), 0750); err != nil { log.Fatalln(err) } } } else { - dir := path.Join(config.PrivateHLSStoragePath, strconv.Itoa(0)) - log.Traceln("Creating", dir) - if err := os.MkdirAll(dir, 0750); err != nil { - log.Fatalln(err) - } - dir = path.Join(config.PublicHLSStoragePath, strconv.Itoa(0)) + dir := path.Join(config.HLSStoragePath, strconv.Itoa(0)) log.Traceln("Creating", dir) if err := os.MkdirAll(dir, 0750); err != nil { log.Fatalln(err) diff --git a/router/router.go b/router/router.go index 5b5c5c82b..3f663ec2b 100644 --- a/router/router.go +++ b/router/router.go @@ -74,6 +74,9 @@ func Start() error { // Current inbound broadcaster http.HandleFunc("/api/admin/status", middleware.RequireAdminAuth(admin.Status)) + // Return HLS video + http.HandleFunc("/hls/", controllers.HandleHLSRequest) + // Disconnect inbound stream http.HandleFunc("/api/admin/disconnect", middleware.RequireAdminAuth(admin.DisconnectInboundConnection))