| @@ -1,4 +1,4 @@ | |||
| # LotWebUi | |||
| # iotWebUi | |||
| This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.2.14. | |||
| @@ -27,7 +27,8 @@ | |||
| "inlineStyleLanguage": "scss", | |||
| "assets": [ | |||
| "src/favicon.ico", | |||
| "src/assets" | |||
| "src/assets", | |||
| "src/server" | |||
| ], | |||
| "styles": [ | |||
| "@angular/material/prebuilt-themes/indigo-pink.css", | |||
| @@ -6,7 +6,8 @@ | |||
| "start": "ng serve -o", | |||
| "build": "ng build", | |||
| "watch": "ng build --watch --configuration development", | |||
| "test": "ng test" | |||
| "test": "ng test", | |||
| "camera-server": "node dist/lot-web-ui/server/server.js" | |||
| }, | |||
| "private": true, | |||
| "dependencies": { | |||
| @@ -23,6 +24,8 @@ | |||
| "@angular/router": "^16.2.0", | |||
| "@asymmetrik/ngx-leaflet": "^16.0.1", | |||
| "@asymmetrik/ngx-leaflet-markercluster": "^16.0.0", | |||
| "express": "^4.19.2", | |||
| "express-ws": "^5.0.2", | |||
| "leaflet": "^1.9.4", | |||
| "ngx-toastr": "^17.0.2", | |||
| "rtsp-relay": "^1.8.0", | |||
| @@ -1,4 +1,4 @@ | |||
| import {APP_INITIALIZER, NgModule} from '@angular/core'; | |||
| import { NgModule} from '@angular/core'; | |||
| import { BrowserModule } from '@angular/platform-browser'; | |||
| import { AppRoutingModule } from './app-routing.module'; | |||
| @@ -6,12 +6,9 @@ import { AppComponent } from './app.component'; | |||
| import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; | |||
| import {SharedModule} from "./shared/shared.module"; | |||
| import {HttpClientModule} from "@angular/common/http"; | |||
| import {AppInitService} from "./shared/services/app-init.service"; | |||
| import { ToastrModule } from 'ngx-toastr'; | |||
| export function appInitializerFactory(appInitializerService: AppInitService): () => Promise<any> { | |||
| return () => appInitializerService.initializeApp(); | |||
| } | |||
| @NgModule({ | |||
| @@ -30,15 +27,7 @@ export function appInitializerFactory(appInitializerService: AppInitService): () | |||
| autoDismiss: true | |||
| }), // ToastrModule added | |||
| ], | |||
| providers: [ | |||
| { | |||
| provide: APP_INITIALIZER, | |||
| useFactory: appInitializerFactory, | |||
| deps: [AppInitService], | |||
| multi: true | |||
| } | |||
| ], | |||
| providers: [], | |||
| bootstrap: [AppComponent] | |||
| }) | |||
| export class AppModule { } | |||
| @@ -1,6 +1,2 @@ | |||
| <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> | |||
| @@ -1,22 +1,5 @@ | |||
| .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 | |||
| } | |||
| @@ -9,21 +9,25 @@ import {loadPlayer, Player} from "rtsp-relay/browser"; | |||
| export class CameraStreamComponent implements OnInit, AfterViewInit{ | |||
| player?: Player; | |||
| // @ViewChild('videoPlayer') | |||
| // videoPlayer?: ElementRef<HTMLCanvasElement>; | |||
| @ViewChild('videoPlayer') | |||
| videoPlayer?: ElementRef<HTMLCanvasElement>; | |||
| constructor(private el: ElementRef, private renderer: Renderer2) {} | |||
| ngOnInit() { | |||
| } | |||
| constructor(private el: ElementRef, private renderer: Renderer2) {} | |||
| 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(); | |||
| } | |||
| @@ -3,6 +3,7 @@ import {SocketService} from "../../../shared/services/socket.service"; | |||
| import {Subscription} from "rxjs"; | |||
| import {ToastrService} from "ngx-toastr"; | |||
| @Component({ | |||
| selector: 'app-overall-ground', | |||
| templateUrl: './overall-ground.component.html', | |||
| @@ -8,6 +8,7 @@ import {MapComponent} from "./map/map.component"; | |||
| import {SharedMaterialModule} from "../../shared/shared-material.module"; | |||
| import { CameraStreamComponent } from './camera-stream/camera-stream.component'; | |||
| @NgModule({ | |||
| declarations: [ | |||
| OverallGroundComponent, | |||
| @@ -1,18 +0,0 @@ | |||
| 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); | |||
| }); | |||
| } | |||
| } | |||
| @@ -10,8 +10,7 @@ import { config } from "../../../assets/config/config"; | |||
| export class SocketService { | |||
| gateway = config.gateway; | |||
| websocket$!: WebSocketSubject<any> ; | |||
| websocket$: WebSocketSubject<any> | undefined ; | |||
| private isConnected: boolean = false; | |||
| private messagesSubject$ = new Subject(); | |||
| public messages$ = this.messagesSubject$.asObservable(); | |||
| @@ -20,22 +19,25 @@ export class SocketService { | |||
| constructor() { | |||
| } | |||
| 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$.subscribe((messages) => { | |||
| this.messagesSubject$.next(messages); | |||
| }); | |||
| } | |||
| } | |||
| close() { | |||
| this.websocket$.complete(); | |||
| this.websocket$?.complete(); | |||
| this.websocket$ = undefined; | |||
| } | |||
| sendMessage(msg: any) { | |||
| this.websocket$.next(msg); | |||
| this.websocket$?.next(msg); | |||
| } | |||
| private getNewWebSocket() { | |||
| @@ -51,6 +53,7 @@ export class SocketService { | |||
| closeObserver: { | |||
| next: () => { | |||
| console.log('Connection closed'); | |||
| this.websocket$ = undefined; | |||
| this.isConnected = false; | |||
| this.connect({reconnect: true}); | |||
| this.statusSubject.next(this.isConnected); | |||
| @@ -1,23 +1,29 @@ | |||
| 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}`); | |||
| }); | |||
| @@ -1,4 +0,0 @@ | |||
| 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 | |||