You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
109 lines
3.1 KiB
109 lines
3.1 KiB
// video.js
|
|
// Copyright (C) 2024 DTP Technologies, LLC
|
|
// All Rights Reserved
|
|
|
|
'use strict';
|
|
|
|
import { pipeline } from 'node:stream';
|
|
|
|
import { SiteController } from '../../lib/site-lib.js';
|
|
import {
|
|
populateVideoId,
|
|
} from './lib/populators.js';
|
|
|
|
export default class VideoController extends SiteController {
|
|
|
|
static get name ( ) { return 'VideoController'; }
|
|
static get slug ( ) { return 'video'; }
|
|
|
|
constructor (dtp) {
|
|
super(dtp, VideoController);
|
|
}
|
|
|
|
async start ( ) {
|
|
const {
|
|
limiter: limiterService,
|
|
session: sessionService,
|
|
} = this.dtp.services;
|
|
const limiterConfig = limiterService.config.video;
|
|
|
|
const authRequired = sessionService.authCheckMiddleware({ requireLogin: true });
|
|
|
|
const router = this.createRouter('/video');
|
|
this.dtp.app.use('/video', router);
|
|
|
|
router.use(
|
|
authRequired,
|
|
async (req, res, next) => {
|
|
res.locals.currentView = 'video';
|
|
return next();
|
|
},
|
|
);
|
|
|
|
router.param('videoId', populateVideoId(this));
|
|
|
|
router.get(
|
|
'/:videoId/media',
|
|
limiterService.create(limiterConfig.getVideoMedia),
|
|
this.getVideoMedia.bind(this),
|
|
);
|
|
|
|
return router;
|
|
}
|
|
|
|
async getVideoMedia (req, res, next) {
|
|
const { minio: minioService } = this.dtp.services;
|
|
try {
|
|
let artifact = res.locals.video.media;
|
|
|
|
const artifactStat = await minioService.statObject(artifact.bucket, artifact.key);
|
|
const fileInfo = Object.assign({ }, artifact);
|
|
const range = req.get('range');
|
|
if (range) {
|
|
let [start, end] = range.replace(/bytes=/, '').split('-').map((term) => parseInt(term.trim(), 10));
|
|
if (!isNaN(start) && isNaN(end)) {
|
|
end = artifactStat.size - 1;
|
|
}
|
|
if (isNaN(start) && !isNaN(end)) {
|
|
start = artifactStat.size - end;
|
|
end = artifactStat.size - 1;
|
|
}
|
|
fileInfo.range = { start, end };
|
|
}
|
|
|
|
this.log.debug('starting video media stream', {
|
|
media: artifact,
|
|
fileInfo,
|
|
});
|
|
|
|
const stream = await minioService.openDownloadStream(fileInfo);
|
|
if (fileInfo.range) {
|
|
res.writeHead(206, {
|
|
'Content-Type': 'video/mp4',
|
|
'Content-Length': fileInfo.range.end - fileInfo.range.start + 1,
|
|
'Accept-Ranges': 'bytes',
|
|
'Content-Range': `bytes ${fileInfo.range.start}-${fileInfo.range.end}/${artifactStat.size}`,
|
|
'Cache-Control': 'public, maxage=86400, s-maxage=86400, immutable',
|
|
});
|
|
} else {
|
|
res.writeHead(200, {
|
|
'Accept-Ranges': 'bytes',
|
|
'Content-Type': 'video/mp4',
|
|
'Content-Length': artifactStat.size,
|
|
'Cache-Control': 'public, maxage=86400, s-maxage=86400, immutable',
|
|
});
|
|
}
|
|
|
|
pipeline(stream, res, (err) => {
|
|
if (err) {
|
|
this.log.debug('failed to stream media', { err });
|
|
return;
|
|
}
|
|
this.log.info('media stream sent');
|
|
});
|
|
} catch (error) {
|
|
this.log.error('failed to open media stream', { videoId: res.locals.video._id, error });
|
|
return next(error);
|
|
}
|
|
}
|
|
}
|
|
|