1
0
Fork 0
mirror of https://github.com/Chocobozzz/PeerTube.git synced 2025-10-06 03:50:26 +02:00

Add Scheduled Lives functionality (#7144)

* Add Scheduled Lives functionality through originallyPublishedAt

Implements #6604 by reusing the originallyPublishedAt field of isLive videos to mark "waiting for live" videos as scheduled at a set time.

* Hide scheduled lives from Browse Videos page

* Add tests for Scheduled Live videos

* Make scheduled lives use a dedicated scheduledAt field in the VideoLive table

* Plan live schedules to evolve in the future

 * Use a dedicated table to store live schedules, so we can add multiple
   schedules in the future and also add a title, description etc. for a
   specific schedule
 * Adapt REST API to use an array to store/get live schedules
 * Add REST API param so it's the client choice to include or not
   scheduled lives
 * Export schedules info in user import/export

---------

Co-authored-by: Chocobozzz <me@florianbigard.com>
This commit is contained in:
Bojidar Marinov 2025-08-01 16:06:27 +03:00 committed by GitHub
parent a5c087d3d4
commit 8c9b4abe45
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
62 changed files with 858 additions and 148 deletions

View file

@ -6,7 +6,7 @@ import { ActorModel } from '@server/models/actor/actor.js'
import { MActorFull } from '@server/types/models/index.js'
import WebFinger from 'webfinger.js'
// eslint-disable-next-line new-cap
// eslint-disable-next-line @typescript-eslint/no-deprecated
const webfinger = new WebFinger({
tls_only: isProdInstance(),
uri_fallback: false,

View file

@ -17,6 +17,7 @@ import { setVideoTags } from '@server/lib/video.js'
import { StoryboardModel } from '@server/models/video/storyboard.js'
import { VideoCaptionModel } from '@server/models/video/video-caption.js'
import { VideoFileModel } from '@server/models/video/video-file.js'
import { VideoLiveScheduleModel } from '@server/models/video/video-live-schedule.js'
import { VideoLiveModel } from '@server/models/video/video-live.js'
import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist.js'
import {
@ -35,6 +36,7 @@ import {
getCaptionAttributesFromObject,
getFileAttributesFromUrl,
getLiveAttributesFromObject,
getLiveSchedulesAttributesFromObject,
getPreviewFromIcons,
getStoryboardAttributeFromObject,
getStreamingPlaylistAttributesFromObject,
@ -101,7 +103,7 @@ export abstract class APVideoAbstractBuilder {
const existingCaptions = await VideoCaptionModel.listVideoCaptions(video.id, t)
let captionsToCreate = getCaptionAttributesFromObject(video, this.videoObject)
.map(a => new VideoCaptionModel(a) as MVideoCaption)
.map(a => new VideoCaptionModel(a) as MVideoCaption)
for (const existingCaption of existingCaptions) {
// Only keep captions that do not already exist
@ -136,7 +138,14 @@ export abstract class APVideoAbstractBuilder {
const attributes = getLiveAttributesFromObject(video, this.videoObject)
const [ videoLive ] = await VideoLiveModel.upsert(attributes, { transaction, returning: true })
video.VideoLive = videoLive
await VideoLiveScheduleModel.deleteAllOfLiveId(videoLive.id, transaction)
videoLive.LiveSchedules = []
for (const scheduleAttributes of getLiveSchedulesAttributesFromObject(videoLive, this.videoObject)) {
const scheduleModel = new VideoLiveScheduleModel(scheduleAttributes)
videoLive.LiveSchedules.push(await scheduleModel.save({ transaction }))
}
}
protected async setWebVideoFiles (video: MVideoFullLight, t: Transaction) {

View file

@ -31,7 +31,15 @@ import { VideoCaptionModel } from '@server/models/video/video-caption.js'
import { VideoFileModel } from '@server/models/video/video-file.js'
import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist.js'
import { FilteredModelAttributes } from '@server/types/index.js'
import { isStreamingPlaylist, MChannelId, MStreamingPlaylistVideo, MVideo, MVideoFile, MVideoId } from '@server/types/models/index.js'
import {
isStreamingPlaylist,
MChannelId,
MStreamingPlaylistVideo,
MVideo,
MVideoFile,
MVideoId,
MVideoLive
} from '@server/types/models/index.js'
import { decode as magnetUriDecode } from 'magnet-uri'
import { basename, extname } from 'path'
import { getDurationFromActivityStream } from '../../activity.js'
@ -206,6 +214,14 @@ export function getLiveAttributesFromObject (video: MVideoId, videoObject: Video
videoId: video.id
}
}
export function getLiveSchedulesAttributesFromObject (live: MVideoLive, videoObject: VideoObject) {
const schedules = videoObject.schedules || []
return schedules.map(s => ({
liveVideoId: live.id,
startAt: s.startDate
}))
}
export function getCaptionAttributesFromObject (video: MVideoId, videoObject: VideoObject) {
return videoObject.subtitleLanguage.map(c => {