mirror of
https://github.com/owncast/owncast.git
synced 2024-12-18 07:12:33 +03:00
129 lines
4.1 KiB
Go
129 lines
4.1 KiB
Go
package replays
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/grafov/m3u8"
|
|
"github.com/owncast/owncast/core/data"
|
|
"github.com/owncast/owncast/db"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
/*
|
|
The PlaylistGenerator is responsible for creating the master and media
|
|
playlists, in order to replay a stream in whole, or part. It requires detailed
|
|
metadata about how the initial live stream was configured, as well as a
|
|
access to every segment that was created during the live stream.
|
|
*/
|
|
|
|
type PlaylistGenerator struct {
|
|
datastore *data.Datastore
|
|
}
|
|
|
|
func NewPlaylistGenerator() *PlaylistGenerator {
|
|
return &PlaylistGenerator{
|
|
datastore: data.GetDatastore(),
|
|
}
|
|
}
|
|
|
|
func (p *PlaylistGenerator) GenerateMasterPlaylistForStream(streamId string) (*m3u8.MasterPlaylist, error) {
|
|
// Determine the different output configurations for this stream.
|
|
configs, err := p.GetConfigurationsForStream(streamId)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get configurations for stream")
|
|
}
|
|
|
|
// Create the master playlist that will hold the different media playlists.
|
|
masterPlaylist := p.createNewMasterPlaylist()
|
|
|
|
// Create the media playlists for each output configuration.
|
|
for _, config := range configs {
|
|
// Verify the validity of the configuration.
|
|
if err := config.Validate(); err != nil {
|
|
return nil, errors.Wrap(err, "invalid output configuration")
|
|
}
|
|
|
|
mediaPlaylist, err := p.GenerateMediaPlaylistForStreamAndConfiguration(streamId, config.ID)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to create media playlist")
|
|
}
|
|
|
|
// Append the media playlist to the master playlist.
|
|
params := p.getMediaPlaylistParamsForConfig(config)
|
|
|
|
// Add the media playlist to the master playlist.
|
|
publicPlaylistPath := strings.Join([]string{"/replay", streamId, config.ID}, "/")
|
|
masterPlaylist.Append(publicPlaylistPath, mediaPlaylist, params)
|
|
}
|
|
|
|
// Return the final master playlist that contains all the media playlists.
|
|
return masterPlaylist, nil
|
|
}
|
|
|
|
func (p *PlaylistGenerator) GenerateMediaPlaylistForStreamAndConfiguration(streamId, outputConfigurationId string) (*m3u8.MediaPlaylist, error) {
|
|
stream, err := p.GetStream(streamId)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get stream")
|
|
}
|
|
|
|
config, err := p.GetOutputConfig(outputConfigurationId)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get output configuration")
|
|
}
|
|
|
|
// Fetch all the segments for this configuration.
|
|
segments, err := p.GetAllSegmentsForOutputConfiguration(outputConfigurationId)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get all segments for output configuration")
|
|
}
|
|
|
|
// Create the media playlist for this configuration and add the segments.
|
|
mediaPlaylist, err := p.createMediaPlaylistForConfigurationAndSegments(config, stream.StartTime, stream.InProgress, segments)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to create media playlist")
|
|
}
|
|
|
|
return mediaPlaylist, nil
|
|
}
|
|
|
|
func (p *PlaylistGenerator) GetStream(streamId string) (*Stream, error) {
|
|
stream, err := p.datastore.GetQueries().GetStreamById(context.Background(), streamId)
|
|
if stream.ID == "" {
|
|
return nil, errors.Wrap(err, "failed to get stream")
|
|
}
|
|
|
|
s := Stream{
|
|
ID: stream.ID,
|
|
Title: stream.StreamTitle.String,
|
|
StartTime: stream.StartTime.Time,
|
|
EndTime: stream.EndTime.Time,
|
|
InProgress: !stream.EndTime.Valid,
|
|
}
|
|
|
|
return &s, nil
|
|
}
|
|
|
|
func (p *PlaylistGenerator) GetOutputConfig(outputConfigId string) (*HLSOutputConfiguration, error) {
|
|
config, err := p.datastore.GetQueries().GetOutputConfigurationForId(context.Background(), outputConfigId)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get output configuration")
|
|
}
|
|
|
|
return createConfigFromConfigRow(config), nil
|
|
}
|
|
|
|
func createConfigFromConfigRow(row db.GetOutputConfigurationForIdRow) *HLSOutputConfiguration {
|
|
config := HLSOutputConfiguration{
|
|
ID: row.ID,
|
|
StreamId: row.StreamID,
|
|
VariantId: row.VariantID,
|
|
Name: row.Name,
|
|
VideoBitrate: int(row.Bitrate),
|
|
Framerate: int(row.Framerate),
|
|
ScaledHeight: int(row.ResolutionWidth.Int32),
|
|
ScaledWidth: int(row.ResolutionHeight.Int32),
|
|
SegmentDuration: float64(row.SegmentDuration),
|
|
}
|
|
return &config
|
|
}
|