amcrest: publish continuous recording

This commit is contained in:
Koushik Dutta
2022-03-14 00:57:43 -07:00
parent 477ac5ddf4
commit b9ae0e27e6
9 changed files with 71 additions and 28 deletions

1
common/.gitignore vendored
View File

@@ -3,3 +3,4 @@ node_modules
.gcloud/
dist/
scrypted.db
src/test.ts

21
common/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,21 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/dist/common/src/test.js",
"preLaunchTask": "npm: build",
"outFiles": [
"${workspaceFolder}/**/*.js"
]
}
]
}

View File

@@ -4,6 +4,7 @@
"description": "",
"main": "index.js",
"scripts": {
"build": "tsc",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",

View File

@@ -120,7 +120,7 @@ export function createMonacoEvalDefaults(extraLibs: { [lib: string]: string }) {
const libs = Object.assign(getTypeDefs(), extraLibs);
function monacoEvalDefaultsFunction(monaco, safeLibs, libs) {
function monacoEvalDefaultsFunction(monaco: any, safeLibs: any, libs: any) {
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions(
Object.assign(
{},
@@ -223,7 +223,7 @@ export function createScriptDevice(baseInterfaces: string[]): ScriptDeviceImpl {
const iface = methodInterfaces.get(method);
if (iface) {
allInterfaces.push(iface);
device[method] = handler[method].bind(handler);
(device as any)[method] = handler[method].bind(handler);
}
}
return allInterfaces;

View File

@@ -17,6 +17,18 @@ const configuration: RTCConfiguration = {
],
};
export function isPeerConnectionAlive(pc :RTCPeerConnection) {
if (pc.iceConnectionState === 'disconnected'
|| pc.iceConnectionState === 'failed'
|| pc.iceConnectionState === 'closed')
return false;
if (pc.connectionState === 'closed'
|| pc.connectionState === 'disconnected'
|| pc.connectionState === 'failed')
return false;
return true;
}
let wrtc: any;
function initalizeWebRtc() {
if (wrtc)
@@ -214,20 +226,12 @@ export async function startRTCPeerConnectionFFmpegInput(ffInput: FFMpegInput, op
}
const checkConn = () => {
if (pc.iceConnectionState === 'disconnected'
|| pc.iceConnectionState === 'failed'
|| pc.iceConnectionState === 'closed') {
if (!isPeerConnectionAlive(pc))
cleanup();
}
if (pc.connectionState === 'closed'
|| pc.connectionState === 'disconnected'
|| pc.connectionState === 'failed') {
cleanup();
}
}
pc.onconnectionstatechange = checkConn;
pc.oniceconnectionstatechange = checkConn;
pc.addEventListener('connectionstatechange', checkConn);
pc.addEventListener('iceconnectionstatechange', checkConn);
setTimeout(() => {
if (pc.connectionState !== 'connected') {
@@ -273,6 +277,7 @@ export async function startRTCPeerConnection(console: Console, mediaObject: Medi
});
await pc.setRemoteDescription(answer);
return pc;
}
catch (e) {
pc.close();
@@ -314,7 +319,7 @@ export async function startBrowserRTCSignaling(camera: ScryptedDevice & RTCSigna
camera.startRTCSignalingSession(session, options);
}
else {
startRTCPeerConnectionForBrowser(console, await camera.getVideoStream(), session, options);
return startRTCPeerConnectionForBrowser(console, await camera.getVideoStream(), session, options);
}
}
catch (e) {

View File

@@ -6,7 +6,7 @@ import { findTrack } from './sdp-utils';
import dgram from 'dgram';
import net from 'net';
import tls from 'tls';
import { DIGEST } from 'http-auth-utils/src/index';
import { DIGEST } from 'http-auth-utils/dist/index';
import crypto from 'crypto';
export const RTSP_FRAME_MAGIC = 36;
@@ -244,9 +244,11 @@ export class RtspClient extends RtspBase {
});
}
async setup(channel: number, path?: string) {
async setup(channelOrPort: number, path?: string, udp?: boolean) {
const protocol = udp ? 'UDP' : 'TCP';
const client = udp ? 'client_port' : 'interleaved';
const headers: any = {
Transport: `RTP/AVP/TCP;unicast;interleaved=${channel}-${channel + 1}`,
Transport: `RTP/AVP/${protocol};unicast;${client}=${channelOrPort}-${channelOrPort + 1}`,
};
const response = await this.request('SETUP', headers, path)
if (response.headers.session) {
@@ -266,14 +268,19 @@ export class RtspClient extends RtspBase {
return response;
}
async play() {
async play(start: string = '0.000') {
const headers: any = {
Range: 'npt=0.000-',
Range: `npt=${start}-`,
};
await this.request('PLAY', headers);
return this.client;
}
async pause() {
await this.request('PAUSE');
return this.client;
}
async teardown() {
try {
return await this.request('TEARDOWN');

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/amcrest",
"version": "0.0.90",
"version": "0.0.91",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/amcrest",
"version": "0.0.90",
"version": "0.0.91",
"license": "Apache",
"dependencies": {
"@koush/axios-digest-auth": "^0.8.5",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/amcrest",
"version": "0.0.90",
"version": "0.0.91",
"description": "Amcrest Plugin for Scrypted",
"author": "Scrypted",
"license": "Apache",

View File

@@ -50,18 +50,15 @@ export class RtspCamera extends CameraBase<UrlMediaStreamOptions> {
return ret;
}
async createVideoStream(vso: UrlMediaStreamOptions): Promise<MediaObject> {
if (!vso)
throw new Error('video streams not set up or no longer exists.');
addRtspCredentials(rtspUrl: string) {
// ignore this deprecation warning. the WHATWG URL class will trim the password
// off if it is empty, resulting in urls like rtsp://admin@foo.com/.
// this causes ffmpeg to fail on sending a blank password.
// we need to send it as follows: rtsp://admin:@foo.com/.
// Note the trailing colon.
// issue: https://github.com/koush/scrypted/issues/134
const parsedUrl = url.parse(vso.url);
this.console.log('rtsp stream url', vso.url);
const parsedUrl = url.parse(rtspUrl);
this.console.log('rtsp stream url', rtspUrl);
const username = this.storage.getItem("username");
const password = this.storage.getItem("password");
if (username) {
@@ -71,7 +68,10 @@ export class RtspCamera extends CameraBase<UrlMediaStreamOptions> {
}
const stringUrl = url.format(parsedUrl);
return stringUrl;
}
createFfmpegMediaObject(stringUrl: string, vso: MediaStreamOptions) {
const ret: FFMpegInput = {
url: stringUrl,
inputArguments: [
@@ -84,6 +84,14 @@ export class RtspCamera extends CameraBase<UrlMediaStreamOptions> {
return mediaManager.createFFmpegMediaObject(ret);
}
async createVideoStream(vso: UrlMediaStreamOptions): Promise<MediaObject> {
if (!vso)
throw new Error('video streams not set up or no longer exists.');
const stringUrl = this.addRtspCredentials(vso.url);
return this.createFfmpegMediaObject(stringUrl, vso);
}
// hide the description from CameraBase that indicates it is only used for snapshots
getUsernameDescription(): string {
return;