Support using the custom video serving endpoint even if you don't use object storage (#2924)

* feat(video): refactor video serving endpoint

It can now be used without an object storage provider. Closes #2785

* fix: remove debug log
This commit is contained in:
Gabe Kangas
2023-05-30 14:05:24 -07:00
committed by GitHub
parent 31f2db06f7
commit cd458630ec
14 changed files with 156 additions and 79 deletions

View File

@@ -70,6 +70,7 @@ const (
customColorVariableValuesKey = "custom_color_variable_values"
streamKeysKey = "stream_keys"
disableSearchIndexingKey = "disable_search_indexing"
videoServingEndpointKey = "video_serving_endpoint"
)
// GetExtraPageBodyContent will return the user-supplied body content.
@@ -974,3 +975,14 @@ func GetDisableSearchIndexing() bool {
}
return disableSearchIndexing
}
// GetVideoServingEndpoint returns the custom video endpont.
func GetVideoServingEndpoint() string {
message, _ := _datastore.GetString(videoServingEndpointKey)
return message
}
// SetVideoServingEndpoint sets the custom video endpoint.
func SetVideoServingEndpoint(message string) error {
return _datastore.SetString(videoServingEndpointKey, message)
}

View File

@@ -8,7 +8,7 @@ import (
)
const (
datastoreValuesVersion = 2
datastoreValuesVersion = 3
datastoreValueVersionKey = "DATA_STORE_VERSION"
)
@@ -25,6 +25,8 @@ func migrateDatastoreValues(datastore *Datastore) {
migrateToDatastoreValues1(datastore)
case 1:
migrateToDatastoreValues2(datastore)
case 2:
migrateToDatastoreValues3ServingEndpoint3(datastore)
default:
log.Fatalln("missing datastore values migration step")
}
@@ -61,3 +63,13 @@ func migrateToDatastoreValues2(datastore *Datastore) {
{Key: oldAdminPassword, Comment: "Default stream key"},
})
}
func migrateToDatastoreValues3ServingEndpoint3(_ *Datastore) {
s3Config := GetS3Config()
if !s3Config.Enabled {
return
}
_ = SetVideoServingEndpoint(s3Config.ServingEndpoint)
}

View File

@@ -6,13 +6,15 @@ import (
log "github.com/sirupsen/logrus"
"github.com/owncast/owncast/config"
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/core/transcoder"
)
// LocalStorage represents an instance of the local storage provider for HLS video.
type LocalStorage struct {
// Cleanup old public HLS content every N min from the webroot.
onlineCleanupTicker *time.Ticker
onlineCleanupTicker *time.Ticker
customVideoServingEndpoint string
}
// NewLocalStorage returns a new LocalStorage instance.
@@ -22,6 +24,10 @@ func NewLocalStorage() *LocalStorage {
// Setup configures this storage provider.
func (s *LocalStorage) Setup() error {
if data.GetVideoServingEndpoint() != "" {
s.customVideoServingEndpoint = data.GetVideoServingEndpoint()
}
// 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.
s.onlineCleanupTicker = time.NewTicker(1 * time.Minute)
@@ -50,7 +56,12 @@ func (s *LocalStorage) VariantPlaylistWritten(localFilePath string) {
// MasterPlaylistWritten is called when the master hls playlist is written.
func (s *LocalStorage) MasterPlaylistWritten(localFilePath string) {
if _, err := s.Save(localFilePath, 0); err != nil {
if s.customVideoServingEndpoint != "" {
// Rewrite the playlist to use custom absolute remote URLs
if err := rewriteRemotePlaylist(localFilePath, s.customVideoServingEndpoint); err != nil {
log.Warnln(err)
}
} else if _, err := s.Save(localFilePath, 0); err != nil {
log.Warnln(err)
}
}

View File

@@ -0,0 +1,36 @@
package storageproviders
import (
"bufio"
"os"
"path/filepath"
"github.com/grafov/m3u8"
"github.com/owncast/owncast/config"
"github.com/owncast/owncast/core/playlist"
log "github.com/sirupsen/logrus"
)
// rewriteRemotePlaylist will take a local playlist and rewrite it to have absolute URLs to remote locations.
func rewriteRemotePlaylist(localFilePath, remoteServingEndpoint string) error {
f, err := os.Open(localFilePath) // nolint
if err != nil {
log.Fatalln(err)
}
p := m3u8.NewMasterPlaylist()
if err := p.DecodeFrom(bufio.NewReader(f), false); err != nil {
log.Warnln(err)
}
for _, item := range p.Variants {
item.URI = remoteServingEndpoint + filepath.Join("/hls", item.URI)
}
publicPath := filepath.Join(config.HLSStoragePath, filepath.Base(localFilePath))
newPlaylist := p.String()
return playlist.WritePlaylist(newPlaylist, publicPath)
}

View File

@@ -1,7 +1,6 @@
package storageproviders
import (
"bufio"
"fmt"
"net/http"
"os"
@@ -11,7 +10,6 @@ import (
"time"
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/core/playlist"
"github.com/owncast/owncast/utils"
log "github.com/sirupsen/logrus"
@@ -21,8 +19,6 @@ import (
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/owncast/owncast/config"
"github.com/grafov/m3u8"
)
// S3Storage is the s3 implementation of a storage provider.
@@ -58,8 +54,9 @@ func (s *S3Storage) Setup() error {
log.Trace("Setting up S3 for external storage of video...")
s3Config := data.GetS3Config()
if s3Config.ServingEndpoint != "" {
s.host = s3Config.ServingEndpoint
customVideoServingEndpoint := data.GetVideoServingEndpoint()
if customVideoServingEndpoint != "" {
s.host = customVideoServingEndpoint
} else {
s.host = fmt.Sprintf("%s/%s", s3Config.Endpoint, s3Config.Bucket)
}
@@ -130,7 +127,7 @@ func (s *S3Storage) VariantPlaylistWritten(localFilePath string) {
// MasterPlaylistWritten is called when the master hls playlist is written.
func (s *S3Storage) MasterPlaylistWritten(localFilePath string) {
// Rewrite the playlist to use absolute remote S3 URLs
if err := s.rewriteRemotePlaylist(localFilePath); err != nil {
if err := rewriteRemotePlaylist(localFilePath, s.host); err != nil {
log.Warnln(err)
}
}
@@ -216,26 +213,3 @@ func (s *S3Storage) connectAWS() *session.Session {
}
return sess
}
// rewriteRemotePlaylist will take a local playlist and rewrite it to have absolute URLs to remote locations.
func (s *S3Storage) rewriteRemotePlaylist(filePath string) error {
f, err := os.Open(filePath) // nolint
if err != nil {
log.Fatalln(err)
}
p := m3u8.NewMasterPlaylist()
if err := p.DecodeFrom(bufio.NewReader(f), false); err != nil {
log.Warnln(err)
}
for _, item := range p.Variants {
item.URI = s.host + filepath.Join("/hls", item.URI)
}
publicPath := filepath.Join(config.HLSStoragePath, filepath.Base(filePath))
newPlaylist := p.String()
return playlist.WritePlaylist(newPlaylist, publicPath)
}