Skip to content

Commit

Permalink
Displayed live streams using Adaptive technologhy HLS
Browse files Browse the repository at this point in the history
  • Loading branch information
nabil-nablotech committed Jan 9, 2024
1 parent aed8eca commit 59d3a3f
Show file tree
Hide file tree
Showing 13 changed files with 801 additions and 323 deletions.
33 changes: 32 additions & 1 deletion Backend/src/app/frontend/frontend.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
CameraValidator,
} from '../../validators/camera-id/camera.pipe';
import { AuthGuard } from '../../auth/auth.guard';
import { createReadStream } from 'fs';

const filterParams = {
name: 'filter',
Expand Down Expand Up @@ -54,7 +55,7 @@ const filterParams = {
@ApiBadRequestResponse({
description: 'Camera id or filter is invalid',
})
@UseGuards(AuthGuard)
// @UseGuards(AuthGuard)
@Controller()
export class FrontendController {
constructor(private readonly databaseService: DatabaseService) {}
Expand Down Expand Up @@ -97,4 +98,34 @@ export class FrontendController {

return new StreamableFile(array[0].intrusionDetection.buffer);
}

/* Camera stream main hls file endpoint */
@ApiParam({
name: 'id',
type: 'number',
description: 'Camera id',
example: 1,
})
@Get('/hls/:id(\\d+)')
async getMainHLS(@Param('id') cameraId: CameraIds) {
const filePath = `${__dirname}\\..\\..\\${process.env.HLS_OUTPUT_DIRECTORY}\\${cameraId}\\${cameraId}_${process.env.HLS_FILE_NAME}`;
const file = createReadStream(filePath);
return new StreamableFile(file);
}

/* Camera stream split hls file endpoint */
@ApiParam({
name: 'filename',
type: 'string',
example: 'some.ts',
})
@Get('/hls/:filename')
async getHLSFiles(
@Param('filename') filename: string,
) {
const id = filename.substring(0,filename.indexOf("_"));
const filePath = `${__dirname}\\..\\..\\${process.env.HLS_OUTPUT_DIRECTORY}\\${id}\\${filename}`;
const file = createReadStream(filePath);
return new StreamableFile(file);
}
}
33 changes: 33 additions & 0 deletions Backend/src/cameraStream/cameraData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
export type Camera = {
id: number;
name: string;
rtspUrl: string;
};

export const cameraData: Camera[] = [
{
id: 1,
name: 'Backyard',
rtspUrl: 'rtsp://192.168.252.244:554',
},
/* {
id: 2,
name: 'Pet room',
rtspUrl: 'rtsp://192.168.252.244:554',
},
{
id: 3,
name: 'Main road',
rtspUrl: 'rtsp://192.168.252.244:554',
},
{
id: 4,
name: 'Parking',
rtspUrl: 'rtsp://192.168.252.244:554',
},
{
id: 1,
name: 'Backyard',
rtspUrl: 'rtsp://192.168.252.244:554',
}, */
];
92 changes: 92 additions & 0 deletions Backend/src/cameraStream/cameraStream.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/* eslint-disable padded-blocks */
import type { Camera } from './cameraData';
import { cameraData } from './cameraData';
const { spawn } = require('child_process');
const fs = require('fs');

function createDirectory(directoryPath) {
// Check if the directory already exists
fs.access(directoryPath, fs.constants.F_OK, (err) => {
if (!err) {
console.log(
`Directory '${directoryPath}' already exists. No action taken.`,
);
return;
}

// If the directory does not exist, create it
fs.mkdir(directoryPath, { recursive: true }, (err) => {
if (err) {
console.error(`Error creating directory: ${err}`);
return;
}

console.log(`Directory '${directoryPath}' created successfully.`);
});
});
}

function transcodeRTSPtoHLS(camera: Camera) {
//Create stream directory and then run FFmpeg command

createDirectory(
`${__dirname}\\..\\${process.env.HLS_OUTPUT_DIRECTORY}\\${camera.id}`,
);

const ffmpegCommand = [
'-i',
camera.rtspUrl,
'-c:v',
'libx264',
'-c:a',
'aac',
'-hls_time',
'3',
'-hls_list_size',
'15',
'-hls_flags',
'delete_segments',
'-strict',
'experimental',
'-threads',
'8',
'-preset',
'ultrafast',
`${__dirname}\\..\\${process.env.HLS_OUTPUT_DIRECTORY}\\${camera.id}\\${camera.id}_${process.env.HLS_FILE_NAME}`,
];

// Spawn FFmpeg process
const ffmpegProcess = spawn('ffmpeg', ffmpegCommand);

// Event listeners for process output
ffmpegProcess.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});

ffmpegProcess.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});

ffmpegProcess.on('close', (code) => {
console.log(`FFmpeg process closed with code ${code}`);
});

ffmpegProcess.on('error', (err) => {
console.error(`Error executing FFmpeg: ${err}`);
});
}

// Periodically check the RTSP streams and restart transcoding for each
function periodicCheck() {
console.log('Checking RTSP stream status...');
cameraData.forEach(transcodeRTSPtoHLS);
}

// Run once the application is run
export function initiateCameraStream() {
// Run the periodic check every 10 minutes (adjust as needed)
setInterval(periodicCheck, 10 * 60 * 1000);

// Initial transcoding for each RTSP stream
cameraData.forEach(transcodeRTSPtoHLS);
}
5 changes: 4 additions & 1 deletion Backend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { IoAdapter } from '@nestjs/platform-socket.io';

import { initiateCameraStream } from './cameraStream/cameraStream';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
Expand Down Expand Up @@ -40,6 +40,9 @@ async function bootstrap() {
console.log(
'\nApp started, look at http://localhost:8080/swagger-api for the documentation',
);

initiateCameraStream();
}

bootstrap();

Loading

0 comments on commit 59d3a3f

Please sign in to comment.