mirror of
https://github.com/owncast/owncast.git
synced 2024-11-27 17:59:21 +03:00
141 lines
5.1 KiB
Go
141 lines
5.1 KiB
Go
package replays
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/owncast/owncast/core/data"
|
|
"github.com/owncast/owncast/db"
|
|
"github.com/owncast/owncast/utils"
|
|
"github.com/pkg/errors"
|
|
"github.com/teris-io/shortid"
|
|
)
|
|
|
|
// Clip represents a clip that has been created from a stream.
|
|
// A clip is a subset of a stream that has has start and end seconds
|
|
// relative to the start of the stream.
|
|
type Clip struct {
|
|
ID string `json:"id"`
|
|
StreamId string `json:"stream_id"`
|
|
ClippedBy string `json:"clipped_by,omitempty"`
|
|
ClipTitle string `json:"title,omitempty"`
|
|
StreamTitle string `json:"stream_title,omitempty"`
|
|
RelativeStartTime float32 `json:"relativeStartTime"`
|
|
RelativeEndTime float32 `json:"relativeEndTime"`
|
|
DurationSeconds int `json:"durationSeconds"`
|
|
Manifest string `json:"manifest,omitempty"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// GetClips will return all clips that have been recorded.
|
|
func GetAllClips() ([]*Clip, error) {
|
|
clips, err := data.GetDatastore().GetQueries().GetAllClips(context.Background())
|
|
if err != nil {
|
|
return nil, errors.WithMessage(err, "failure to get clips")
|
|
}
|
|
|
|
response := []*Clip{}
|
|
for _, clip := range clips {
|
|
s := Clip{
|
|
ID: clip.ID,
|
|
ClipTitle: clip.ClipTitle.String,
|
|
StreamId: clip.StreamID,
|
|
StreamTitle: clip.StreamTitle.String,
|
|
RelativeStartTime: float32(clip.RelativeStartTime.Float64),
|
|
RelativeEndTime: float32(clip.RelativeEndTime.Float64),
|
|
DurationSeconds: int(clip.DurationSeconds),
|
|
Timestamp: clip.Timestamp.Time,
|
|
Manifest: fmt.Sprintf("/clip/%s", clip.ID),
|
|
}
|
|
response = append(response, &s)
|
|
}
|
|
return response, nil
|
|
}
|
|
|
|
// GetAllClipsForStream will return all clips that have been recorded for a stream.
|
|
func GetAllClipsForStream(streamId string) ([]*Clip, error) {
|
|
clips, err := data.GetDatastore().GetQueries().GetAllClipsForStream(context.Background(), streamId)
|
|
if err != nil {
|
|
return nil, errors.WithMessage(err, "failure to get clips")
|
|
}
|
|
|
|
response := []*Clip{}
|
|
for _, clip := range clips {
|
|
s := Clip{
|
|
ID: clip.ClipID,
|
|
ClipTitle: clip.ClipTitle.String,
|
|
StreamTitle: clip.StreamTitle.String,
|
|
RelativeStartTime: float32(clip.RelativeStartTime.Float64),
|
|
RelativeEndTime: float32(clip.RelativeEndTime.Float64),
|
|
Timestamp: clip.Timestamp.Time,
|
|
Manifest: fmt.Sprintf("/clips/%s", clip.ClipID),
|
|
}
|
|
response = append(response, &s)
|
|
}
|
|
return response, nil
|
|
}
|
|
|
|
// AddClipForStream will save a new clip for a stream.
|
|
func AddClipForStream(streamId, clipTitle, clippedBy string, relativeStartTimeSeconds, relativeEndTimeSeconds float32) (string, int, error) {
|
|
playlistGenerator := NewPlaylistGenerator()
|
|
|
|
// Verify this stream exists
|
|
if _, err := playlistGenerator.GetStream(streamId); err != nil {
|
|
return "", 0, errors.WithMessage(err, "stream not found")
|
|
}
|
|
|
|
// Verify this stream has at least one output configuration.
|
|
configs, err := playlistGenerator.GetConfigurationsForStream(streamId)
|
|
if err != nil {
|
|
return "", 0, errors.WithMessage(err, "unable to get configurations for stream")
|
|
}
|
|
|
|
if len(configs) == 0 {
|
|
return "", 0, errors.New("no configurations found for stream")
|
|
}
|
|
|
|
// We want the start and end seconds to be aligned to the segment so
|
|
// round up and down the values to get a fully inclusive segment range.
|
|
config := configs[0]
|
|
segmentDuration := int(config.SegmentDuration)
|
|
|
|
updatedRelativeStartTimeSeconds := utils.RoundDownToNearest(relativeStartTimeSeconds, segmentDuration)
|
|
updatedRelativeEndTimeSeconds := utils.RoundUpToNearest(relativeEndTimeSeconds, segmentDuration)
|
|
clipId := shortid.MustGenerate()
|
|
duration := updatedRelativeEndTimeSeconds - updatedRelativeStartTimeSeconds
|
|
|
|
err = data.GetDatastore().GetQueries().InsertClip(context.Background(), db.InsertClipParams{
|
|
ID: clipId,
|
|
StreamID: streamId,
|
|
ClipTitle: sql.NullString{String: clipTitle, Valid: clipTitle != ""},
|
|
RelativeStartTime: sql.NullFloat64{Float64: float64(updatedRelativeStartTimeSeconds), Valid: true},
|
|
RelativeEndTime: sql.NullFloat64{Float64: float64(updatedRelativeEndTimeSeconds), Valid: true},
|
|
Timestamp: sql.NullTime{Time: time.Now(), Valid: true},
|
|
})
|
|
if err != nil {
|
|
return "", 0, errors.WithMessage(err, "failure to add clip")
|
|
}
|
|
|
|
return clipId, duration, nil
|
|
}
|
|
|
|
// GetFinalSegmentForStream will return the final known segment for a stream.
|
|
func GetFinalSegmentForStream(streamId string) (*HLSSegment, error) {
|
|
segmentResponse, err := data.GetDatastore().GetQueries().GetFinalSegmentForStream(context.Background(), streamId)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "unable to get final segment for stream")
|
|
}
|
|
|
|
segment := HLSSegment{
|
|
ID: segmentResponse.ID,
|
|
StreamID: segmentResponse.StreamID,
|
|
OutputConfigurationID: segmentResponse.OutputConfigurationID,
|
|
Path: segmentResponse.Path,
|
|
RelativeTimestamp: segmentResponse.RelativeTimestamp,
|
|
Timestamp: segmentResponse.Timestamp.Time,
|
|
}
|
|
|
|
return &segment, nil
|
|
}
|