@scrypted/prebuffer-mixin 0.9.43 → 0.9.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
- package/.vscode/launch.json +0 -24
- package/.vscode/settings.json +0 -3
- package/.vscode/tasks.json +0 -20
- package/dist/main.nodejs.js +0 -2
- package/src/file-rtsp-server.ts +0 -61
- package/src/main.ts +0 -1808
- package/src/rfc4571.ts +0 -224
- package/src/rtsp-session.ts +0 -302
- package/src/sps-resolution.ts +0 -50
- package/src/stream-settings.ts +0 -247
- package/src/transcode-settings.ts +0 -51
- package/tsconfig.json +0 -11
package/src/stream-settings.ts
DELETED
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
import { getH264DecoderArgs } from "@scrypted/common/src/ffmpeg-hardware-acceleration";
|
|
2
|
-
import { StorageSetting, StorageSettings } from "@scrypted/sdk/storage-settings";
|
|
3
|
-
import { MixinDeviceBase, ResponseMediaStreamOptions, VideoCamera } from "@scrypted/sdk";
|
|
4
|
-
import { getTranscodeMixinProviderId } from "./transcode-settings";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export function getDefaultPrebufferedStreams(msos: ResponseMediaStreamOptions[]) {
|
|
8
|
-
if (!msos)
|
|
9
|
-
return;
|
|
10
|
-
|
|
11
|
-
// do not enable rebroadcast on cloud streams or rawvideo by default.
|
|
12
|
-
const firstNonCloudStream = msos.find(mso => mso.source !== 'cloud' && mso.container !== 'rawvideo');
|
|
13
|
-
return firstNonCloudStream ? [firstNonCloudStream] : [];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function getPrebufferedStreams(storageSettings: StorageSettings<'enabledStreams'>, msos: ResponseMediaStreamOptions[]) {
|
|
17
|
-
if (!msos)
|
|
18
|
-
return;
|
|
19
|
-
|
|
20
|
-
if (!storageSettings.hasValue.enabledStreams)
|
|
21
|
-
return getDefaultPrebufferedStreams(msos);
|
|
22
|
-
|
|
23
|
-
return msos.filter(mso => storageSettings.values.enabledStreams.includes(mso.name));
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export type StreamStorageSetting = StorageSetting & {
|
|
27
|
-
prefersPrebuffer: boolean,
|
|
28
|
-
preferredResolution: number,
|
|
29
|
-
};
|
|
30
|
-
export type StreamStorageSettingsDict<T extends string> = { [key in T]: StreamStorageSetting };
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
function getStreamTypes<T extends string>(storageSettings: StreamStorageSettingsDict<T>) {
|
|
34
|
-
return storageSettings;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function pickBestStream(msos: ResponseMediaStreamOptions[], resolution: number) {
|
|
38
|
-
if (!msos)
|
|
39
|
-
return;
|
|
40
|
-
|
|
41
|
-
let best: ResponseMediaStreamOptions;
|
|
42
|
-
let bestScore: number;
|
|
43
|
-
for (const mso of msos) {
|
|
44
|
-
const score = Math.abs(mso.video?.width * mso.video?.height - resolution);
|
|
45
|
-
if (!best || score < bestScore) {
|
|
46
|
-
best = mso;
|
|
47
|
-
bestScore = score;
|
|
48
|
-
if (Number.isNaN(bestScore))
|
|
49
|
-
bestScore = Number.MAX_SAFE_INTEGER;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return best;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function createStreamSettings(device: MixinDeviceBase<VideoCamera>) {
|
|
57
|
-
const streamTypes = getStreamTypes({
|
|
58
|
-
defaultStream: {
|
|
59
|
-
title: 'Local Stream',
|
|
60
|
-
description: 'The media stream to use when streaming on your local network. This stream should be prebuffered. Recommended resolution: 1920x1080 to 4K.',
|
|
61
|
-
hide: true,
|
|
62
|
-
prefersPrebuffer: true,
|
|
63
|
-
preferredResolution: 3840 * 2160,
|
|
64
|
-
},
|
|
65
|
-
remoteStream: {
|
|
66
|
-
title: 'Remote (Medium Resolution) Stream',
|
|
67
|
-
description: 'The media stream to use when streaming from outside your local network. Selecting a low birate stream is recommended. Recommended resolution: 1280x720.',
|
|
68
|
-
hide: true,
|
|
69
|
-
prefersPrebuffer: false,
|
|
70
|
-
preferredResolution: 1280 * 720,
|
|
71
|
-
},
|
|
72
|
-
lowResolutionStream: {
|
|
73
|
-
title: 'Low Resolution Stream',
|
|
74
|
-
description: 'The media stream to use for low resolution output, such as Apple Watch and Video Analysis. Recommended resolution: 480x360.',
|
|
75
|
-
hide: true,
|
|
76
|
-
prefersPrebuffer: false,
|
|
77
|
-
preferredResolution: 480 * 360,
|
|
78
|
-
},
|
|
79
|
-
recordingStream: {
|
|
80
|
-
title: 'Local Recording Stream',
|
|
81
|
-
description: 'The media stream to use when recording to local storage such as an NVR. This stream should be prebuffered. Recommended resolution: 1920x1080 to 4K.',
|
|
82
|
-
hide: true,
|
|
83
|
-
prefersPrebuffer: true,
|
|
84
|
-
preferredResolution: 3840 * 2160,
|
|
85
|
-
},
|
|
86
|
-
remoteRecordingStream: {
|
|
87
|
-
title: 'Remote Recording Stream',
|
|
88
|
-
description: 'The media stream to use when recording to cloud storage such as HomeKit Secure Video clips in iCloud. This stream should be prebuffered. Recommended resolution: 1280x720.',
|
|
89
|
-
hide: true,
|
|
90
|
-
prefersPrebuffer: true,
|
|
91
|
-
preferredResolution: 1280 * 720,
|
|
92
|
-
},
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
const storageSettings = new StorageSettings(device, {
|
|
96
|
-
enabledStreams: {
|
|
97
|
-
title: 'Prebuffered Streams',
|
|
98
|
-
description: 'Prebuffering maintains an active connection to the stream and improves load times. Prebuffer also retains the recent video for capturing motion events with HomeKit Secure video. Enabling Prebuffer is not recommended on Cloud cameras.',
|
|
99
|
-
multiple: true,
|
|
100
|
-
hide: false,
|
|
101
|
-
},
|
|
102
|
-
...streamTypes,
|
|
103
|
-
rebroadcastPort: {
|
|
104
|
-
title: 'Rebroadcast Port',
|
|
105
|
-
description: 'The port of the RTSP server that will rebroadcast your streams.',
|
|
106
|
-
type: 'number',
|
|
107
|
-
hide: false,
|
|
108
|
-
},
|
|
109
|
-
transcodeStreams: {
|
|
110
|
-
group: 'Transcoding',
|
|
111
|
-
title: 'Transcode Streams',
|
|
112
|
-
description: 'The media streams to transcode. Transcoding audio and video is not recommended and should only be used when necessary. The Rebroadcast Plugin manages the system-wide Transcode settings. See the Rebroadcast Readme for optimal configuration.',
|
|
113
|
-
multiple: true,
|
|
114
|
-
choices: Object.values(streamTypes).map(st => st.title),
|
|
115
|
-
hide: true,
|
|
116
|
-
},
|
|
117
|
-
videoDecoderArguments: {
|
|
118
|
-
group: 'Transcoding',
|
|
119
|
-
title: 'Video Decoder Arguments',
|
|
120
|
-
description: 'FFmpeg arguments used to decode input video when transcoding a stream.',
|
|
121
|
-
placeholder: '-hwaccel auto',
|
|
122
|
-
choices: Object.keys(getH264DecoderArgs()),
|
|
123
|
-
combobox: true,
|
|
124
|
-
mapPut: (oldValue, newValue) => getH264DecoderArgs()[newValue]?.join(' ') || newValue,
|
|
125
|
-
hide: true,
|
|
126
|
-
},
|
|
127
|
-
videoFilterArguments: {
|
|
128
|
-
group: 'Transcoding',
|
|
129
|
-
title: 'Video Filter Arguments',
|
|
130
|
-
description: 'FFmpeg arguments used to filter input video when transcoding a stream. This can be used to crops, scale, rotates, etc.',
|
|
131
|
-
placeholder: 'transpose=1',
|
|
132
|
-
hide: true,
|
|
133
|
-
},
|
|
134
|
-
// 3/6/2022
|
|
135
|
-
// Ran into an issue where the RTSP source had SPS/PPS in the SDP,
|
|
136
|
-
// and none in the bitstream. Codec copy will not add SPS/PPS before IDR frames
|
|
137
|
-
// unless this flag is used.
|
|
138
|
-
// 3/7/2022
|
|
139
|
-
// This flag was enabled by default, but I believe this is causing issues with some users.
|
|
140
|
-
// Make it a setting.
|
|
141
|
-
missingCodecParameters: {
|
|
142
|
-
group: 'Transcoding',
|
|
143
|
-
title: 'Out of Band Codec Parameters',
|
|
144
|
-
description: 'Some cameras do not include H264 codec parameters in the stream and this causes live streaming to always fail (but recordings may be working). This is a inexpensive video filter and does not perform a transcode. Enable this setting only as necessary.',
|
|
145
|
-
type: 'boolean',
|
|
146
|
-
hide: true,
|
|
147
|
-
},
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
function getDefaultMediaStream(v: StreamStorageSetting, msos: ResponseMediaStreamOptions[]) {
|
|
151
|
-
const enabledStreams = getPrebufferedStreams(storageSettings, msos);
|
|
152
|
-
const prebufferPreferenceStreams = v.prefersPrebuffer && enabledStreams?.length > 0 ? enabledStreams : msos;
|
|
153
|
-
return pickBestStream(prebufferPreferenceStreams, v.preferredResolution);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function getMediaStream(key: string, msos: ResponseMediaStreamOptions[]) {
|
|
157
|
-
const v: StreamStorageSetting = storageSettings.settings[key];
|
|
158
|
-
const value = storageSettings.values[key];
|
|
159
|
-
let isDefault = value === 'Default';
|
|
160
|
-
let stream = msos?.find(mso => mso.name === value);
|
|
161
|
-
if (isDefault || !stream) {
|
|
162
|
-
isDefault = true;
|
|
163
|
-
stream = getDefaultMediaStream(v, msos);
|
|
164
|
-
}
|
|
165
|
-
return {
|
|
166
|
-
title: streamTypes[key].title,
|
|
167
|
-
isDefault,
|
|
168
|
-
stream,
|
|
169
|
-
};
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
function createStreamOptions(v: StreamStorageSetting, msos: ResponseMediaStreamOptions[]) {
|
|
173
|
-
const choices = [
|
|
174
|
-
'Default',
|
|
175
|
-
...msos.map(mso => mso.name),
|
|
176
|
-
];
|
|
177
|
-
const defaultValue = getDefaultMediaStream(v, msos).name;
|
|
178
|
-
|
|
179
|
-
const streamOptions = {
|
|
180
|
-
defaultValue: 'Default',
|
|
181
|
-
description: v.description + ` The default for this stream is ${defaultValue}.`,
|
|
182
|
-
choices,
|
|
183
|
-
hide: false,
|
|
184
|
-
};
|
|
185
|
-
return streamOptions;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
storageSettings.options = {
|
|
189
|
-
onGet: async () => {
|
|
190
|
-
let enabledStreams: StorageSetting;
|
|
191
|
-
|
|
192
|
-
const hideTranscode = device.mixins?.includes(getTranscodeMixinProviderId()) ? {
|
|
193
|
-
hide: false,
|
|
194
|
-
} : {};
|
|
195
|
-
const hideTranscodes = {
|
|
196
|
-
transcodeStreams: hideTranscode,
|
|
197
|
-
missingCodecParameters: hideTranscode,
|
|
198
|
-
videoDecoderArguments: hideTranscode,
|
|
199
|
-
videoFilterArguments: hideTranscode,
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
try {
|
|
203
|
-
const msos = await device.mixinDevice.getVideoStreamOptions();
|
|
204
|
-
|
|
205
|
-
enabledStreams = {
|
|
206
|
-
defaultValue: getDefaultPrebufferedStreams(msos)?.map(mso => mso.name || mso.id),
|
|
207
|
-
choices: msos.map((mso, index) => mso.name || mso.id),
|
|
208
|
-
hide: false,
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
if (msos?.length > 1) {
|
|
212
|
-
return {
|
|
213
|
-
enabledStreams,
|
|
214
|
-
defaultStream: createStreamOptions(streamTypes.defaultStream, msos),
|
|
215
|
-
remoteStream: createStreamOptions(streamTypes.remoteStream, msos),
|
|
216
|
-
lowResolutionStream: createStreamOptions(streamTypes.lowResolutionStream, msos),
|
|
217
|
-
recordingStream: createStreamOptions(streamTypes.recordingStream, msos),
|
|
218
|
-
remoteRecordingStream: createStreamOptions(streamTypes.remoteRecordingStream, msos),
|
|
219
|
-
...hideTranscodes,
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
else {
|
|
223
|
-
return {
|
|
224
|
-
enabledStreams,
|
|
225
|
-
...hideTranscodes,
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
catch (e) {
|
|
230
|
-
device.console.error('error retrieving getVideoStreamOptions', e);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return {
|
|
234
|
-
...hideTranscodes,
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return {
|
|
240
|
-
getDefaultStream: (msos: ResponseMediaStreamOptions[]) => getMediaStream(storageSettings.keys.defaultStream, msos),
|
|
241
|
-
getRemoteStream: (msos: ResponseMediaStreamOptions[]) => getMediaStream(storageSettings.keys.remoteStream, msos),
|
|
242
|
-
getLowResolutionStream: (msos: ResponseMediaStreamOptions[]) => getMediaStream(storageSettings.keys.lowResolutionStream, msos),
|
|
243
|
-
getRecordingStream: (msos: ResponseMediaStreamOptions[]) => getMediaStream(storageSettings.keys.recordingStream, msos),
|
|
244
|
-
getRemoteRecordingStream: (msos: ResponseMediaStreamOptions[]) => getMediaStream(storageSettings.keys.remoteRecordingStream, msos),
|
|
245
|
-
storageSettings,
|
|
246
|
-
};
|
|
247
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import sdk, { MixinProvider, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting, Settings, SettingValue } from "@scrypted/sdk";
|
|
2
|
-
import { RebroadcastPlugin } from "./main";
|
|
3
|
-
const { deviceManager } = sdk;
|
|
4
|
-
|
|
5
|
-
export const TRANSCODE_MIXIN_PROVIDER_NATIVE_ID = 'transcode';
|
|
6
|
-
export const REBROADCAST_MIXIN_INTERFACE_TOKEN = 'mixin:@scrypted/prebuffer-mixin';
|
|
7
|
-
|
|
8
|
-
export function getTranscodeMixinProviderId() {
|
|
9
|
-
if (!deviceManager.getNativeIds().includes(TRANSCODE_MIXIN_PROVIDER_NATIVE_ID))
|
|
10
|
-
return;
|
|
11
|
-
const transcodeMixin = deviceManager.getDeviceState(TRANSCODE_MIXIN_PROVIDER_NATIVE_ID);
|
|
12
|
-
return transcodeMixin?.id;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export class TranscodeMixinProvider extends ScryptedDeviceBase implements MixinProvider, Settings {
|
|
16
|
-
constructor(public plugin: RebroadcastPlugin) {
|
|
17
|
-
super(TRANSCODE_MIXIN_PROVIDER_NATIVE_ID);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
getSettings(): Promise<Setting[]> {
|
|
21
|
-
return this.plugin.transcodeStorageSettings.getSettings();
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
putSetting(key: string, value: SettingValue): Promise<void> {
|
|
25
|
-
return this.plugin.transcodeStorageSettings.putSetting(key, value);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async canMixin(type: ScryptedDeviceType, interfaces: string[]): Promise<string[]> {
|
|
29
|
-
if (!interfaces.includes(REBROADCAST_MIXIN_INTERFACE_TOKEN))
|
|
30
|
-
return;
|
|
31
|
-
return [
|
|
32
|
-
ScryptedInterface.Settings,
|
|
33
|
-
];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
invalidateSettings(id: string) {
|
|
37
|
-
process.nextTick(async () =>{
|
|
38
|
-
const mixin = await this.plugin.currentMixins.get(id)?.mixin;
|
|
39
|
-
mixin?.onDeviceEvent(ScryptedInterface.Settings, undefined)
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any; }): Promise<any> {
|
|
44
|
-
this.invalidateSettings(mixinDeviceState.id);
|
|
45
|
-
return mixinDevice;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async releaseMixin(id: string, mixinDevice: any): Promise<void> {
|
|
49
|
-
this.invalidateSettings(id);
|
|
50
|
-
}
|
|
51
|
-
}
|