@campoint/vxwebrtc 0.0.2
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/.eslintignore +1 -0
- package/.eslintrc.js +22 -0
- package/.gitlab-ci.yml +42 -0
- package/README.md +1 -0
- package/dist/MungeSdp.d.ts +2 -0
- package/dist/Types/CodecsOptions.d.ts +8 -0
- package/dist/Types/CommonConnectionTypes.d.ts +48 -0
- package/dist/Types/EnumCodecContentType.d.ts +13 -0
- package/dist/Types/WebRTCStreamConfig.d.ts +26 -0
- package/dist/WebRtcInputConnection.d.ts +19 -0
- package/dist/WebRtcOutputConnection.d.ts +20 -0
- package/dist/WebRtcPeerConnection.d.ts +23 -0
- package/dist/browserSupportedAudioCodec.d.ts +13 -0
- package/dist/browserSupportedVideoCodec.d.ts +7 -0
- package/dist/stats.json +10326 -0
- package/dist/utils.d.ts +6 -0
- package/dist/version.d.ts +1 -0
- package/dist/vxwebrtc.d.ts +6 -0
- package/dist/vxwebrtc.js +3 -0
- package/dist/vxwebrtc.js.LICENSE.txt +8 -0
- package/dist/vxwebrtc.js.map +1 -0
- package/package.json +53 -0
- package/src/MungeSdp.ts +323 -0
- package/src/Types/CodecsOptions.ts +9 -0
- package/src/Types/CommonConnectionTypes.ts +57 -0
- package/src/Types/EnumCodecContentType.ts +13 -0
- package/src/Types/WebRTCStreamConfig.ts +59 -0
- package/src/WebRtcInputConnection.ts +152 -0
- package/src/WebRtcOutputConnection.ts +122 -0
- package/src/WebRtcPeerConnection.ts +91 -0
- package/src/browserSupportedAudioCodec.ts +49 -0
- package/src/browserSupportedVideoCodec.ts +39 -0
- package/src/utils.ts +18 -0
- package/src/version.ts +1 -0
- package/src/vxwebrtc.ts +8 -0
- package/tsconfig.json +15 -0
- package/tslint.json +18 -0
- package/webpack.common.js +43 -0
- package/webpack.dev.js +7 -0
- package/webpack.prod.js +7 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import adapter from 'webrtc-adapter';
|
|
2
|
+
import { mungeSdpPublish } from './MungeSdp';
|
|
3
|
+
import {
|
|
4
|
+
EnumMediaStreamTrackKind,
|
|
5
|
+
EnumWebSocketConnectionCommand,
|
|
6
|
+
EnumWebSocketConnectionDirection,
|
|
7
|
+
EnumWebSocketConnectionStatus,
|
|
8
|
+
MediaInfo,
|
|
9
|
+
WebSocketReply,
|
|
10
|
+
} from './Types/CommonConnectionTypes';
|
|
11
|
+
import WebRtcPeerConnection, { IWebRtcPeerConnectionOptions } from './WebRtcPeerConnection';
|
|
12
|
+
|
|
13
|
+
export class WebRtcOutputConnection extends WebRtcPeerConnection {
|
|
14
|
+
protected connectionOptions: IWebRtcOutputConnection;
|
|
15
|
+
protected wsConnection?: WebSocket = undefined;
|
|
16
|
+
protected peerConnection?: RTCPeerConnection = undefined;
|
|
17
|
+
private peerConnectionConfig?: RTCConfiguration = undefined;
|
|
18
|
+
private videoSender?: RTCRtpSender = undefined;
|
|
19
|
+
private audioSender?: RTCRtpSender = undefined;
|
|
20
|
+
|
|
21
|
+
constructor(connectionOptions: IWebRtcOutputConnection) {
|
|
22
|
+
super(connectionOptions);
|
|
23
|
+
|
|
24
|
+
this.onError = this.onError.bind(this);
|
|
25
|
+
this.onRtcDescription = this.onRtcDescription.bind(this);
|
|
26
|
+
this.onWsOpen = this.onWsOpen.bind(this);
|
|
27
|
+
this.onWsMessage = this.onWsMessage.bind(this);
|
|
28
|
+
this.connectionOptions = connectionOptions;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
protected onRtcDescription(description: RTCSessionDescriptionInit) {
|
|
32
|
+
if (!this.peerConnection) {
|
|
33
|
+
return this.onError(new Error('RTC got description, but no RTC'));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (description.sdp && adapter.browserDetails.browser !== 'safari') {
|
|
37
|
+
description.sdp = mungeSdpPublish(
|
|
38
|
+
description.sdp ? description.sdp : '',
|
|
39
|
+
this.connectionOptions.mediaInfo
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
this.peerConnection
|
|
44
|
+
.setLocalDescription(description)
|
|
45
|
+
.then(() => {
|
|
46
|
+
this.sendResponse(
|
|
47
|
+
EnumWebSocketConnectionDirection.PUBLISH,
|
|
48
|
+
EnumWebSocketConnectionCommand.SEND_OFFER,
|
|
49
|
+
description
|
|
50
|
+
);
|
|
51
|
+
})
|
|
52
|
+
.catch(this.onError);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
protected onWsOpen() {
|
|
56
|
+
this.peerConnection = new RTCPeerConnection(this.peerConnectionConfig);
|
|
57
|
+
this.videoSender = undefined;
|
|
58
|
+
this.audioSender = undefined;
|
|
59
|
+
|
|
60
|
+
this.peerConnection.onnegotiationneeded = () => {
|
|
61
|
+
if (this.peerConnection) {
|
|
62
|
+
this.peerConnection.createOffer().then(this.onRtcDescription, this.onError);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const localTracks = this.connectionOptions.localStream.getTracks();
|
|
67
|
+
|
|
68
|
+
for (const localTrack of localTracks) {
|
|
69
|
+
const sender = this.peerConnection.addTrack(localTrack, this.connectionOptions.localStream);
|
|
70
|
+
|
|
71
|
+
if (localTrack.kind === EnumMediaStreamTrackKind.AUDIO) {
|
|
72
|
+
this.audioSender = sender;
|
|
73
|
+
} else if (localTrack.kind === EnumMediaStreamTrackKind.VIDEO) {
|
|
74
|
+
this.videoSender = sender;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
protected onWsMessage(evt: MessageEvent) {
|
|
80
|
+
const msgJson: WebSocketReply = JSON.parse(evt.data);
|
|
81
|
+
const msgStatus = parseInt(msgJson.status, 10);
|
|
82
|
+
|
|
83
|
+
if (msgStatus !== EnumWebSocketConnectionStatus.OK) {
|
|
84
|
+
return this.onError(new Error(msgJson.statusDescription));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const sdpData = msgJson.sdp;
|
|
88
|
+
|
|
89
|
+
if (sdpData && this.peerConnection) {
|
|
90
|
+
this.peerConnection
|
|
91
|
+
.setRemoteDescription(new RTCSessionDescription(sdpData))
|
|
92
|
+
.catch(this.onError);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const iceCandidates = msgJson.iceCandidates;
|
|
96
|
+
|
|
97
|
+
if (iceCandidates !== undefined && this.peerConnection) {
|
|
98
|
+
for (const iceCandidate of iceCandidates) {
|
|
99
|
+
this.peerConnection.addIceCandidate(new RTCIceCandidate(iceCandidate)).catch(this.onError);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
public start() {
|
|
105
|
+
super.start();
|
|
106
|
+
if (this.wsConnection) {
|
|
107
|
+
this.wsConnection.onopen = this.onWsOpen;
|
|
108
|
+
this.wsConnection.onmessage = this.onWsMessage;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public stop() {
|
|
113
|
+
super.stop();
|
|
114
|
+
this.videoSender = undefined;
|
|
115
|
+
this.audioSender = undefined;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface IWebRtcOutputConnection extends IWebRtcPeerConnectionOptions {
|
|
120
|
+
localStream: MediaStream;
|
|
121
|
+
mediaInfo: MediaInfo;
|
|
122
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OnErrorCallback,
|
|
3
|
+
OnStopCallback,
|
|
4
|
+
StreamInfo,
|
|
5
|
+
EnumWebSocketConnectionCommand,
|
|
6
|
+
EnumWebSocketConnectionDirection,
|
|
7
|
+
} from './Types/CommonConnectionTypes';
|
|
8
|
+
|
|
9
|
+
abstract class WebRtcPeerConnection {
|
|
10
|
+
protected wsConnection?: WebSocket = undefined;
|
|
11
|
+
protected peerConnection?: RTCPeerConnection = undefined;
|
|
12
|
+
|
|
13
|
+
protected abstract onRtcDescription(description: RTCSessionDescriptionInit): void;
|
|
14
|
+
protected abstract onWsOpen(): void;
|
|
15
|
+
protected abstract onWsMessage(evt: MessageEvent): void;
|
|
16
|
+
|
|
17
|
+
protected constructor(protected connectionOptions: IWebRtcPeerConnectionOptions) {
|
|
18
|
+
this.connectionOptions = connectionOptions;
|
|
19
|
+
this.connectToWebSocket = this.connectToWebSocket.bind(this);
|
|
20
|
+
this.onError = this.onError.bind(this);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
protected sendResponse(
|
|
24
|
+
direction: EnumWebSocketConnectionDirection,
|
|
25
|
+
command: EnumWebSocketConnectionCommand,
|
|
26
|
+
description?: RTCSessionDescriptionInit
|
|
27
|
+
) {
|
|
28
|
+
if (this.wsConnection) {
|
|
29
|
+
let json = {
|
|
30
|
+
direction: direction,
|
|
31
|
+
command: command,
|
|
32
|
+
streamInfo: this.connectionOptions.streamInfo,
|
|
33
|
+
userData: this.connectionOptions.userData,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
if (description !== undefined) {
|
|
37
|
+
json = { ...json, ...{ sdp: description } };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.wsConnection.send(JSON.stringify(json));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
protected connectToWebSocket() {
|
|
45
|
+
this.wsConnection = new WebSocket(this.connectionOptions.wsUrl);
|
|
46
|
+
this.wsConnection.binaryType = 'arraybuffer';
|
|
47
|
+
this.wsConnection.onerror = () => this.onError(new Error('WsConnection failed'));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
protected onError(error: Error) {
|
|
51
|
+
this.stop();
|
|
52
|
+
this.connectionOptions.onError(error);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public start() {
|
|
56
|
+
if (!this.peerConnection) {
|
|
57
|
+
if (this.wsConnection) {
|
|
58
|
+
this.wsConnection.close();
|
|
59
|
+
this.wsConnection = undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.connectToWebSocket();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public stop() {
|
|
67
|
+
if (this.peerConnection) {
|
|
68
|
+
this.peerConnection.close();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.peerConnection = undefined;
|
|
72
|
+
|
|
73
|
+
if (this.wsConnection) {
|
|
74
|
+
this.wsConnection.close();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
this.wsConnection = undefined;
|
|
78
|
+
|
|
79
|
+
this.connectionOptions.onStop();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface IWebRtcPeerConnectionOptions {
|
|
84
|
+
wsUrl: string;
|
|
85
|
+
streamInfo: StreamInfo;
|
|
86
|
+
userData?: object;
|
|
87
|
+
onStop: OnStopCallback;
|
|
88
|
+
onError: OnErrorCallback;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export default WebRtcPeerConnection;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import adapter from 'webrtc-adapter';
|
|
2
|
+
import { EnumCodecContentType } from './Types/EnumCodecContentType';
|
|
3
|
+
import { supportsCodec } from './utils';
|
|
4
|
+
|
|
5
|
+
export enum EnumAudioCodecs {
|
|
6
|
+
OPUS = 'opus',
|
|
7
|
+
VORBIS = 'vorbis',
|
|
8
|
+
PCMU = 'pcmu',
|
|
9
|
+
PCMA = 'pcma',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** @see CanPlayTypeResult */
|
|
13
|
+
export enum EnumMediaCodecSupported {
|
|
14
|
+
UNKNOWN = '',
|
|
15
|
+
PROBABLY = 'probably',
|
|
16
|
+
MAYBE = 'maybe',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const browserSupportedAudioCodec = (codecs: string[]) => {
|
|
20
|
+
const isSafari = adapter.browserDetails.browser === 'safari';
|
|
21
|
+
|
|
22
|
+
for (let i = 0; i < codecs.length; i++) {
|
|
23
|
+
const codec = codecs[i];
|
|
24
|
+
|
|
25
|
+
switch (codec) {
|
|
26
|
+
case EnumAudioCodecs.OPUS:
|
|
27
|
+
if (
|
|
28
|
+
isSafari ||
|
|
29
|
+
supportsCodec(EnumCodecContentType.OPUS) === EnumMediaCodecSupported.PROBABLY
|
|
30
|
+
) {
|
|
31
|
+
return EnumAudioCodecs.OPUS;
|
|
32
|
+
}
|
|
33
|
+
break;
|
|
34
|
+
|
|
35
|
+
case EnumAudioCodecs.VORBIS:
|
|
36
|
+
if (supportsCodec(EnumCodecContentType.VORBIS) === EnumMediaCodecSupported.PROBABLY) {
|
|
37
|
+
return EnumAudioCodecs.VORBIS;
|
|
38
|
+
}
|
|
39
|
+
break;
|
|
40
|
+
|
|
41
|
+
// could not find a way to check the support of this codec in the browser
|
|
42
|
+
case EnumAudioCodecs.PCMU:
|
|
43
|
+
case EnumAudioCodecs.PCMA:
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return '';
|
|
49
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as _ from 'lodash';
|
|
2
|
+
import { EnumCodecContentType } from './Types/EnumCodecContentType';
|
|
3
|
+
import { EnumVideoCodec } from './Types/CommonConnectionTypes';
|
|
4
|
+
import { supportsCodec } from './utils';
|
|
5
|
+
|
|
6
|
+
export enum EnumVideoCodecs {
|
|
7
|
+
H264 = 'h264',
|
|
8
|
+
VP9 = 'VP9',
|
|
9
|
+
VP8 = 'VP8',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const browserSupportedVideoCodec = (codecs: string[]) => {
|
|
13
|
+
for (let i = 0; i < codecs.length; i++) {
|
|
14
|
+
const codec = codecs[i];
|
|
15
|
+
|
|
16
|
+
switch (codec) {
|
|
17
|
+
case EnumVideoCodecs.H264:
|
|
18
|
+
/** @note codec is re-named here on purpose to make Wowza server happy */
|
|
19
|
+
if (!_.isEmpty(supportsCodec(EnumCodecContentType.H264))) {
|
|
20
|
+
return EnumVideoCodec.H264;
|
|
21
|
+
}
|
|
22
|
+
break;
|
|
23
|
+
|
|
24
|
+
case EnumVideoCodecs.VP9:
|
|
25
|
+
if (!_.isEmpty(supportsCodec(EnumCodecContentType.VP9))) {
|
|
26
|
+
return EnumVideoCodecs.VP9;
|
|
27
|
+
}
|
|
28
|
+
break;
|
|
29
|
+
|
|
30
|
+
case EnumVideoCodecs.VP8:
|
|
31
|
+
if (!_.isEmpty(supportsCodec(EnumCodecContentType.VP8))) {
|
|
32
|
+
return EnumVideoCodecs.VP8;
|
|
33
|
+
}
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return '';
|
|
39
|
+
};
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as _ from 'lodash';
|
|
2
|
+
import { EnumCodecContentType } from './Types/EnumCodecContentType';
|
|
3
|
+
|
|
4
|
+
export enum EnumSupportedMediaType {
|
|
5
|
+
VIDEO = 'video',
|
|
6
|
+
AUDIO = 'audio',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const supportsCodec = (codecType: EnumCodecContentType): CanPlayTypeResult => {
|
|
10
|
+
const mediaType = codecType.split('/');
|
|
11
|
+
|
|
12
|
+
if (!_.includes(Object.values(EnumSupportedMediaType), mediaType[0])) {
|
|
13
|
+
return '';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const v = window.document.createElement(mediaType[0]) as HTMLMediaElement;
|
|
17
|
+
return v.canPlayType(codecType);
|
|
18
|
+
};
|
package/src/version.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const VERSION = '__VERSION__';
|
package/src/vxwebrtc.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { WebRtcInputConnection, IWebRtcInputConnection } from './WebRtcInputConnection';
|
|
2
|
+
export { WebRtcOutputConnection, IWebRtcOutputConnection } from './WebRtcOutputConnection';
|
|
3
|
+
|
|
4
|
+
export { TWebRtcH264CodecOptions, TWebRtcVP9CodecOptions } from './Types/CodecsOptions';
|
|
5
|
+
export { TConstrainULongRange } from './Types/WebRTCStreamConfig';
|
|
6
|
+
|
|
7
|
+
export {browserSupportedAudioCodec} from './browserSupportedAudioCodec'
|
|
8
|
+
export {browserSupportedVideoCodec} from './browserSupportedVideoCodec'
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"lib": ["dom", "es5", "scripthost", "ES2015", "es2016.array.include"],
|
|
4
|
+
"target": "es5",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"sourceMap": true,
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"newLine": "LF",
|
|
9
|
+
"outDir": "./dist",
|
|
10
|
+
"rootDirs": ["src"],
|
|
11
|
+
"allowJs": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"],
|
|
14
|
+
"exclude": ["node_modules", "**/*.spec.ts"]
|
|
15
|
+
}
|
package/tslint.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"defaultSeverity": "warning",
|
|
3
|
+
"extends": [
|
|
4
|
+
"tslint:recommended"
|
|
5
|
+
],
|
|
6
|
+
"jsRules": {},
|
|
7
|
+
"rules": {
|
|
8
|
+
"interface-name": [false],
|
|
9
|
+
"max-line-length": [false],
|
|
10
|
+
"no-empty": [false],
|
|
11
|
+
"object-literal-shorthand": [false],
|
|
12
|
+
"object-literal-sort-keys": [true, "match-declaration-order"],
|
|
13
|
+
"quotemark": [true, "single", "avoid-escape", "avoid-template"],
|
|
14
|
+
"trailing-comma": [true, {"multiline": "never", "singleline": "never"}],
|
|
15
|
+
"variable-name": { "options": [ "check-format", "allow-leading-underscore", "allow-pascal-case", "ban-keywords" ] }
|
|
16
|
+
},
|
|
17
|
+
"rulesDirectory": []
|
|
18
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
|
3
|
+
const DuplicatePackageCheckerPlugin = require('duplicate-package-checker-webpack-plugin');
|
|
4
|
+
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
entry: {
|
|
8
|
+
'vxwebrtc': './src/vxwebrtc.ts',
|
|
9
|
+
},
|
|
10
|
+
plugins: [
|
|
11
|
+
new CleanWebpackPlugin(),
|
|
12
|
+
new DuplicatePackageCheckerPlugin(),
|
|
13
|
+
new BundleAnalyzerPlugin({
|
|
14
|
+
analyzerMode: 'disabled',
|
|
15
|
+
generateStatsFile: true,
|
|
16
|
+
}),
|
|
17
|
+
],
|
|
18
|
+
module: {
|
|
19
|
+
rules: [
|
|
20
|
+
{
|
|
21
|
+
test: /version.ts$/,
|
|
22
|
+
loader: 'string-replace-loader',
|
|
23
|
+
options: {
|
|
24
|
+
search: '__VERSION__',
|
|
25
|
+
replace: require('./package.json').version,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
test: /\.tsx?$/,
|
|
30
|
+
use: 'ts-loader',
|
|
31
|
+
exclude: /node_modules/,
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
resolve: {
|
|
36
|
+
extensions: ['.tsx', '.ts', '.js'],
|
|
37
|
+
},
|
|
38
|
+
output: {
|
|
39
|
+
filename: '[name].js',
|
|
40
|
+
library: 'webRTC',
|
|
41
|
+
path: path.resolve(__dirname, 'dist'),
|
|
42
|
+
}
|
|
43
|
+
};
|
package/webpack.dev.js
ADDED