From c6c0fa6cd8fe8f752463d8982c3dbcd448739c4e Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 17 Sep 2020 09:20:52 +0200 Subject: Live streaming implementation first step --- server/controllers/api/videos/live.ts | 116 ++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 server/controllers/api/videos/live.ts (limited to 'server/controllers/api/videos/live.ts') diff --git a/server/controllers/api/videos/live.ts b/server/controllers/api/videos/live.ts new file mode 100644 index 000000000..d08ef9869 --- /dev/null +++ b/server/controllers/api/videos/live.ts @@ -0,0 +1,116 @@ +import * as express from 'express' +import { v4 as uuidv4 } from 'uuid' +import { createReqFiles } from '@server/helpers/express-utils' +import { CONFIG } from '@server/initializers/config' +import { ASSETS_PATH, MIMETYPES } from '@server/initializers/constants' +import { getVideoActivityPubUrl } from '@server/lib/activitypub/url' +import { videoLiveAddValidator, videoLiveGetValidator } from '@server/middlewares/validators/videos/video-live' +import { VideoLiveModel } from '@server/models/video/video-live' +import { MVideoDetails, MVideoFullLight } from '@server/types/models' +import { VideoCreate, VideoPrivacy, VideoState } from '../../../../shared' +import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' +import { logger } from '../../../helpers/logger' +import { sequelizeTypescript } from '../../../initializers/database' +import { createVideoMiniatureFromExisting } from '../../../lib/thumbnail' +import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares' +import { TagModel } from '../../../models/video/tag' +import { VideoModel } from '../../../models/video/video' +import { buildLocalVideoFromCreate } from '@server/lib/video' + +const liveRouter = express.Router() + +const reqVideoFileLive = createReqFiles( + [ 'thumbnailfile', 'previewfile' ], + MIMETYPES.IMAGE.MIMETYPE_EXT, + { + thumbnailfile: CONFIG.STORAGE.TMP_DIR, + previewfile: CONFIG.STORAGE.TMP_DIR + } +) + +liveRouter.post('/live', + authenticate, + reqVideoFileLive, + asyncMiddleware(videoLiveAddValidator), + asyncRetryTransactionMiddleware(addLiveVideo) +) + +liveRouter.get('/live/:videoId', + authenticate, + asyncMiddleware(videoLiveGetValidator), + asyncRetryTransactionMiddleware(getVideoLive) +) + +// --------------------------------------------------------------------------- + +export { + liveRouter +} + +// --------------------------------------------------------------------------- + +async function getVideoLive (req: express.Request, res: express.Response) { + const videoLive = res.locals.videoLive + + return res.json(videoLive.toFormattedJSON()) +} + +async function addLiveVideo (req: express.Request, res: express.Response) { + const videoInfo: VideoCreate = req.body + + // Prepare data so we don't block the transaction + const videoData = buildLocalVideoFromCreate(videoInfo, res.locals.videoChannel.id) + videoData.isLive = true + + const videoLive = new VideoLiveModel() + videoLive.streamKey = uuidv4() + + const video = new VideoModel(videoData) as MVideoDetails + video.url = getVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object + + // Process thumbnail or create it from the video + const thumbnailField = req.files ? req.files['thumbnailfile'] : null + const thumbnailModel = thumbnailField + ? await createVideoMiniatureFromExisting(thumbnailField[0].path, video, ThumbnailType.MINIATURE, false) + : await createVideoMiniatureFromExisting(ASSETS_PATH.DEFAULT_LIVE_BACKGROUND, video, ThumbnailType.MINIATURE, true) + + // Process preview or create it from the video + const previewField = req.files ? req.files['previewfile'] : null + const previewModel = previewField + ? await createVideoMiniatureFromExisting(previewField[0].path, video, ThumbnailType.PREVIEW, false) + : await createVideoMiniatureFromExisting(ASSETS_PATH.DEFAULT_LIVE_BACKGROUND, video, ThumbnailType.PREVIEW, true) + + const { videoCreated } = await sequelizeTypescript.transaction(async t => { + const sequelizeOptions = { transaction: t } + + const videoCreated = await video.save(sequelizeOptions) as MVideoFullLight + + if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) + if (previewModel) await videoCreated.addAndSaveThumbnail(previewModel, t) + + // Do not forget to add video channel information to the created video + videoCreated.VideoChannel = res.locals.videoChannel + + videoLive.videoId = videoCreated.id + await videoLive.save(sequelizeOptions) + + // Create tags + if (videoInfo.tags !== undefined) { + const tagInstances = await TagModel.findOrCreateTags(videoInfo.tags, t) + + await video.$set('Tags', tagInstances, sequelizeOptions) + video.Tags = tagInstances + } + + logger.info('Video live %s with uuid %s created.', videoInfo.name, videoCreated.uuid) + + return { videoCreated } + }) + + return res.json({ + video: { + id: videoCreated.id, + uuid: videoCreated.uuid + } + }) +} -- cgit v1.2.3