| # LotWebUi | |||||
| # iotWebUi | |||||
| This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.2.14. | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.2.14. | ||||
| "inlineStyleLanguage": "scss", | "inlineStyleLanguage": "scss", | ||||
| "assets": [ | "assets": [ | ||||
| "src/favicon.ico", | "src/favicon.ico", | ||||
| "src/assets" | |||||
| "src/assets", | |||||
| "src/server" | |||||
| ], | ], | ||||
| "styles": [ | "styles": [ | ||||
| "@angular/material/prebuilt-themes/indigo-pink.css", | "@angular/material/prebuilt-themes/indigo-pink.css", |
| "start": "ng serve -o", | "start": "ng serve -o", | ||||
| "build": "ng build", | "build": "ng build", | ||||
| "watch": "ng build --watch --configuration development", | "watch": "ng build --watch --configuration development", | ||||
| "test": "ng test" | |||||
| "test": "ng test", | |||||
| "camera-server": "node dist/lot-web-ui/server/server.js" | |||||
| }, | }, | ||||
| "private": true, | "private": true, | ||||
| "dependencies": { | "dependencies": { | ||||
| "@angular/router": "^16.2.0", | "@angular/router": "^16.2.0", | ||||
| "@asymmetrik/ngx-leaflet": "^16.0.1", | "@asymmetrik/ngx-leaflet": "^16.0.1", | ||||
| "@asymmetrik/ngx-leaflet-markercluster": "^16.0.0", | "@asymmetrik/ngx-leaflet-markercluster": "^16.0.0", | ||||
| "express": "^4.19.2", | |||||
| "express-ws": "^5.0.2", | |||||
| "leaflet": "^1.9.4", | "leaflet": "^1.9.4", | ||||
| "ngx-toastr": "^17.0.2", | "ngx-toastr": "^17.0.2", | ||||
| "rtsp-relay": "^1.8.0", | "rtsp-relay": "^1.8.0", |
| import {APP_INITIALIZER, NgModule} from '@angular/core'; | |||||
| import { NgModule} from '@angular/core'; | |||||
| import { BrowserModule } from '@angular/platform-browser'; | import { BrowserModule } from '@angular/platform-browser'; | ||||
| import { AppRoutingModule } from './app-routing.module'; | import { AppRoutingModule } from './app-routing.module'; | ||||
| import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; | ||||
| import {SharedModule} from "./shared/shared.module"; | import {SharedModule} from "./shared/shared.module"; | ||||
| import {HttpClientModule} from "@angular/common/http"; | import {HttpClientModule} from "@angular/common/http"; | ||||
| import {AppInitService} from "./shared/services/app-init.service"; | |||||
| import { ToastrModule } from 'ngx-toastr'; | import { ToastrModule } from 'ngx-toastr'; | ||||
| export function appInitializerFactory(appInitializerService: AppInitService): () => Promise<any> { | |||||
| return () => appInitializerService.initializeApp(); | |||||
| } | |||||
| @NgModule({ | @NgModule({ | ||||
| autoDismiss: true | autoDismiss: true | ||||
| }), // ToastrModule added | }), // ToastrModule added | ||||
| ], | ], | ||||
| providers: [ | |||||
| { | |||||
| provide: APP_INITIALIZER, | |||||
| useFactory: appInitializerFactory, | |||||
| deps: [AppInitService], | |||||
| multi: true | |||||
| } | |||||
| ], | |||||
| providers: [], | |||||
| bootstrap: [AppComponent] | bootstrap: [AppComponent] | ||||
| }) | }) | ||||
| export class AppModule { } | export class AppModule { } |
| <h3>Camera Stream</h3> | |||||
| <!--<canvas #videoPlayer></canvas>--> | |||||
| <video width="700" controls style="display: block; margin-left: auto; margin-right: auto"> | |||||
| <source src="assets/video/video.mp4" type="video/mp4"> | |||||
| </video> | |||||
| <h3 style="text-align: center">Camera Stream</h3> | |||||
| <canvas class="video" #videoPlayer></canvas> |
| .tooltip { | |||||
| position: relative; | |||||
| display: inline-block; | |||||
| } | |||||
| .tooltip .tooltiptext { | |||||
| visibility: hidden; | |||||
| width: 300px; | |||||
| padding: 10px; | |||||
| background-color: #fff; | |||||
| color: black; | |||||
| border-radius: 6px; | |||||
| position: absolute; | |||||
| z-index: 1; | |||||
| bottom: 100%; | |||||
| left: 50%; | |||||
| margin-left: -60px; | |||||
| } | |||||
| .tooltip:hover .tooltiptext { | |||||
| visibility: visible; | |||||
| .video { | |||||
| width: 78% !important; | |||||
| display: block; | |||||
| margin: 0 auto | |||||
| } | } |
| export class CameraStreamComponent implements OnInit, AfterViewInit{ | export class CameraStreamComponent implements OnInit, AfterViewInit{ | ||||
| player?: Player; | player?: Player; | ||||
| // @ViewChild('videoPlayer') | |||||
| // videoPlayer?: ElementRef<HTMLCanvasElement>; | |||||
| @ViewChild('videoPlayer') | |||||
| videoPlayer?: ElementRef<HTMLCanvasElement>; | |||||
| constructor(private el: ElementRef, private renderer: Renderer2) {} | |||||
| ngOnInit() { | ngOnInit() { | ||||
| } | } | ||||
| constructor(private el: ElementRef, private renderer: Renderer2) {} | |||||
| async ngAfterViewInit() { | async ngAfterViewInit() { | ||||
| // this.player = await loadPlayer({ | |||||
| // url: 'ws://localhost:8081', | |||||
| // canvas: this.videoPlayer!.nativeElement, | |||||
| // | |||||
| // onDisconnect: () => console.log('Connection lost!'), | |||||
| // }); | |||||
| // | |||||
| // console.log('Connected!', this.player); | |||||
| const connect = async () => { | |||||
| this.player = await loadPlayer({ | |||||
| url: 'ws://localhost:8080/stream', | |||||
| canvas: this.videoPlayer!.nativeElement, | |||||
| onDisconnect: () => { | |||||
| setTimeout(connect, 5000); // reconnect after 5 seconds | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| connect(); | |||||
| } | } | ||||
| import {Subscription} from "rxjs"; | import {Subscription} from "rxjs"; | ||||
| import {ToastrService} from "ngx-toastr"; | import {ToastrService} from "ngx-toastr"; | ||||
| @Component({ | @Component({ | ||||
| selector: 'app-overall-ground', | selector: 'app-overall-ground', | ||||
| templateUrl: './overall-ground.component.html', | templateUrl: './overall-ground.component.html', |
| import {SharedMaterialModule} from "../../shared/shared-material.module"; | import {SharedMaterialModule} from "../../shared/shared-material.module"; | ||||
| import { CameraStreamComponent } from './camera-stream/camera-stream.component'; | import { CameraStreamComponent } from './camera-stream/camera-stream.component'; | ||||
| @NgModule({ | @NgModule({ | ||||
| declarations: [ | declarations: [ | ||||
| OverallGroundComponent, | OverallGroundComponent, |
| import { Injectable } from '@angular/core'; | |||||
| import {config} from "../../../assets/config/config"; | |||||
| @Injectable({ | |||||
| providedIn: 'root' | |||||
| }) | |||||
| export class AppInitService { | |||||
| appConfig = config; | |||||
| constructor() {} | |||||
| initializeApp(): Promise<any> { | |||||
| return new Promise((resolve, reject) => { | |||||
| console.log('Loaded:', this.appConfig); | |||||
| resolve(true); | |||||
| }); | |||||
| } | |||||
| } |
| export class SocketService { | export class SocketService { | ||||
| gateway = config.gateway; | gateway = config.gateway; | ||||
| websocket$!: WebSocketSubject<any> ; | |||||
| websocket$: WebSocketSubject<any> | undefined ; | |||||
| private isConnected: boolean = false; | private isConnected: boolean = false; | ||||
| private messagesSubject$ = new Subject(); | private messagesSubject$ = new Subject(); | ||||
| public messages$ = this.messagesSubject$.asObservable(); | public messages$ = this.messagesSubject$.asObservable(); | ||||
| constructor() { | constructor() { | ||||
| } | } | ||||
| public connect(cfg: { reconnect: boolean } = { reconnect: false }): void { | public connect(cfg: { reconnect: boolean } = { reconnect: false }): void { | ||||
| if (!this.websocket$ || this.websocket$.closed) { | |||||
| console.log('Trying to open a WebSocket connection…'); | |||||
| if (cfg.reconnect || !this.websocket$ || this.websocket$.closed) { | |||||
| console.log(cfg.reconnect ? 'Reconnecting WebSocket…' : 'Trying to open a WebSocket connection…'); | |||||
| this.websocket$ = this.getNewWebSocket(); | this.websocket$ = this.getNewWebSocket(); | ||||
| this.websocket$.subscribe((messages) => { | this.websocket$.subscribe((messages) => { | ||||
| this.messagesSubject$.next(messages); | this.messagesSubject$.next(messages); | ||||
| }); | }); | ||||
| } | } | ||||
| } | } | ||||
| close() { | close() { | ||||
| this.websocket$.complete(); | |||||
| this.websocket$?.complete(); | |||||
| this.websocket$ = undefined; | |||||
| } | } | ||||
| sendMessage(msg: any) { | sendMessage(msg: any) { | ||||
| this.websocket$.next(msg); | |||||
| this.websocket$?.next(msg); | |||||
| } | } | ||||
| private getNewWebSocket() { | private getNewWebSocket() { | ||||
| closeObserver: { | closeObserver: { | ||||
| next: () => { | next: () => { | ||||
| console.log('Connection closed'); | console.log('Connection closed'); | ||||
| this.websocket$ = undefined; | |||||
| this.isConnected = false; | this.isConnected = false; | ||||
| this.connect({reconnect: true}); | this.connect({reconnect: true}); | ||||
| this.statusSubject.next(this.isConnected); | this.statusSubject.next(this.isConnected); |
| const WebSocket = require('ws'); | |||||
| const http = require('http'); | |||||
| const express = require('express'); | |||||
| const expressWs = require('express-ws'); | |||||
| const app = express(); | |||||
| expressWs(app); | |||||
| const server = http.createServer(); | |||||
| const { proxy, scriptUrl } = require('rtsp-relay')(app); | |||||
| const wss = new WebSocket.Server({ server }); | |||||
| const port = 8080; | |||||
| const urlCamera = 'rtsp://admin:[email protected]:88/cam/realmonitor?channel=1&subtype=0'; | |||||
| wss.on('connection', function connection(ws) { | |||||
| app.ws('/api/stream/:cameraIP', (ws, req) => | |||||
| proxy({ | |||||
| url: `rtsp://${req.params.cameraIP}:554/feed`, | |||||
| })(ws), | |||||
| ); | |||||
| ws.on('message', function incoming(message) { | |||||
| // Log tin nhắn nhận được | |||||
| console.log('Nhận được: %s', message); | |||||
| const handler = proxy({ | |||||
| url: urlCamera, | |||||
| verbose: false, | |||||
| transport: 'tcp' | |||||
| }); | }); | ||||
| // Bắt sự kiện khi kết nối đóng | |||||
| ws.on('close', function close() { | |||||
| console.log('Client đã ngắt kết nối'); | |||||
| }); | |||||
| }); | |||||
| server.listen(8081, function listening() { | |||||
| console.log('WebSocket server đang lắng nghe trên cổng 8081'); | |||||
| app.ws('/stream', handler); | |||||
| const server = app.listen(port, () => { | |||||
| console.log(`RTSP Relay Server is listening on port ${port}`); | |||||
| }); | }); | ||||
| ffmpeg -rtsp_transport tcp -i rtsp://admin:hd543211@@xuanphuong32.dyndns.org:8001/cam/realmonitor?channel=7&subtype=0 ^ | |||||
| -f mpegts -codec:v mpeg1video -s 640x480 -b:v 800k -r 30 ^ | |||||
| -codec:a mp2 -b:a 128k ^ | |||||
| http://localhost:8081 |