mirror of
https://github.com/owncast/owncast.git
synced 2024-11-23 13:24:33 +03:00
146 lines
4.6 KiB
Go
146 lines
4.6 KiB
Go
package replays
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/grafov/m3u8"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// GetConfigurationsForStream returns the output configurations for a given stream.
|
|
func (p *PlaylistGenerator) GetConfigurationsForStream(streamId string) ([]*HLSOutputConfiguration, error) {
|
|
outputConfigRows, err := p.datastore.GetQueries().GetOutputConfigurationsForStreamId(context.Background(), streamId)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get output configurations for stream")
|
|
}
|
|
|
|
outputConfigs := []*HLSOutputConfiguration{}
|
|
for _, row := range outputConfigRows {
|
|
config := &HLSOutputConfiguration{
|
|
ID: row.ID,
|
|
StreamId: 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),
|
|
}
|
|
outputConfigs = append(outputConfigs, config)
|
|
}
|
|
|
|
return outputConfigs, nil
|
|
}
|
|
|
|
func (p *PlaylistGenerator) createMediaPlaylistForConfigurationAndSegments(configuration *HLSOutputConfiguration, startTime time.Time, inProgress bool, segments []HLSSegment) (*m3u8.MediaPlaylist, error) {
|
|
playlistSize := len(segments)
|
|
segmentDuration := configuration.SegmentDuration
|
|
playlist, err := m3u8.NewMediaPlaylist(0, uint(playlistSize))
|
|
|
|
playlist.TargetDuration = configuration.SegmentDuration
|
|
|
|
if !inProgress {
|
|
playlist.MediaType = m3u8.VOD
|
|
} else {
|
|
playlist.MediaType = m3u8.EVENT
|
|
}
|
|
|
|
// Add the segments to the playlist.
|
|
for index, segment := range segments {
|
|
// If it's a URL leave it as is, if it's a local path then append a slash.
|
|
path := segment.Path
|
|
if !strings.HasPrefix(path, "http") {
|
|
path = "/" + path
|
|
}
|
|
|
|
mediaSegment := m3u8.MediaSegment{
|
|
URI: path,
|
|
Duration: segmentDuration,
|
|
SeqId: uint64(index),
|
|
ProgramDateTime: segment.Timestamp,
|
|
}
|
|
if err := playlist.AppendSegment(&mediaSegment); err != nil {
|
|
return nil, errors.Wrap(err, "failed to append segment to recording playlist")
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Configure the properties of this media playlist.
|
|
if err := playlist.SetProgramDateTime(startTime); err != nil {
|
|
return nil, errors.Wrap(err, "failed to set media playlist program date time")
|
|
}
|
|
|
|
// Our live output is specified as v6, so let's match it to be as close as
|
|
// possible to what we're doing for live streams.
|
|
playlist.SetVersion(6)
|
|
|
|
if !inProgress {
|
|
// Specify explicitly that the playlist content is allowed to be cached.
|
|
// However, if in-progress recordings are supported this should not be enabled
|
|
// in order for the playlist to be updated with new segments. inProgress is
|
|
// determined by seeing if the stream has an endTime or not.
|
|
playlist.SetCustomTag(&MediaPlaylistAllowCacheTag{})
|
|
|
|
// Set the ENDLIST tag and close the playlist for writing if the stream is
|
|
// not still in progress.
|
|
playlist.Close()
|
|
}
|
|
|
|
return playlist, nil
|
|
}
|
|
|
|
func (p *PlaylistGenerator) createNewMasterPlaylist() *m3u8.MasterPlaylist {
|
|
playlist := m3u8.NewMasterPlaylist()
|
|
playlist.SetIndependentSegments(true)
|
|
playlist.SetVersion(6)
|
|
|
|
return playlist
|
|
}
|
|
|
|
// GetAllSegmentsForOutputConfiguration returns all the segments for a given output config.
|
|
func (p *PlaylistGenerator) GetAllSegmentsForOutputConfiguration(outputId string) ([]HLSSegment, error) {
|
|
segmentRows, err := p.datastore.GetQueries().GetSegmentsForOutputId(context.Background(), outputId)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get segments for output config")
|
|
}
|
|
|
|
segments := []HLSSegment{}
|
|
for _, row := range segmentRows {
|
|
segment := HLSSegment{
|
|
ID: row.ID,
|
|
StreamID: row.StreamID,
|
|
OutputConfigurationID: row.OutputConfigurationID,
|
|
Timestamp: row.Timestamp.Time,
|
|
Path: row.Path,
|
|
}
|
|
segments = append(segments, segment)
|
|
}
|
|
|
|
return segments, nil
|
|
}
|
|
|
|
func (p *PlaylistGenerator) getMediaPlaylistParamsForConfig(config *HLSOutputConfiguration) m3u8.VariantParams {
|
|
params := m3u8.VariantParams{
|
|
ProgramId: 1,
|
|
Name: config.Name,
|
|
FrameRate: float64(config.Framerate),
|
|
Bandwidth: uint32(config.VideoBitrate * 1000),
|
|
// Match what is generated in our live playlists.
|
|
Codecs: "avc1.64001f,mp4a.40.2",
|
|
}
|
|
|
|
// If both the width and height are set then we can set that as
|
|
// the resolution in the media playlist.
|
|
if config.ScaledHeight > 0 && config.ScaledWidth > 0 {
|
|
params.Resolution = fmt.Sprintf("%dx%d", config.ScaledWidth, config.ScaledHeight)
|
|
}
|
|
|
|
return params
|
|
}
|