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/index.ts | 4 +- server/controllers/api/videos/live.ts | 116 +++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 server/controllers/api/videos/live.ts (limited to 'server/controllers/api/videos') diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 15b6f214f..94f0361ee 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts @@ -63,6 +63,7 @@ import { blacklistRouter } from './blacklist' import { videoCaptionsRouter } from './captions' import { videoCommentRouter } from './comment' import { videoImportsRouter } from './import' +import { liveRouter } from './live' import { ownershipVideoRouter } from './ownership' import { rateVideoRouter } from './rate' import { watchingRouter } from './watching' @@ -96,6 +97,7 @@ videosRouter.use('/', videoCaptionsRouter) videosRouter.use('/', videoImportsRouter) videosRouter.use('/', ownershipVideoRouter) videosRouter.use('/', watchingRouter) +videosRouter.use('/', liveRouter) videosRouter.get('/categories', listVideoCategories) videosRouter.get('/licences', listVideoLicences) @@ -304,7 +306,7 @@ async function addVideo (req: express.Request, res: express.Response) { id: videoCreated.id, uuid: videoCreated.uuid } - }).end() + }) } async function updateVideo (req: express.Request, res: express.Response) { 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