DTP Base provides a scalable and secure Node.js application development harness ready for production service.
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

// 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);
}
}
}