├── .gitignore
├── plugins
├── audiobridge
│ ├── AudioBridgeParticipant.js
│ └── JanusAudioBridgePlugin.js
├── videoroom
│ ├── JanusVideoRoomPublisher.js
│ └── JanusVideoRoomPlugin.js
└── streaming
│ └── JanusStreamingPlugin.js
├── index.js
├── utils
├── iceServers.js
├── JanusUtils.js
├── JanusSocket.js
└── JanusPlugin.js
├── package.json
├── Janus.js
└── readme.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 |
--------------------------------------------------------------------------------
/plugins/audiobridge/AudioBridgeParticipant.js:
--------------------------------------------------------------------------------
1 | export default class JanusAudioBridgeParticipant {
2 | constructor({display, id, talking, muted, setup}) {
3 | this.id = id;
4 | this.displayName = display;
5 | this.isTalking = talking;
6 | this.isMuted = muted;
7 | this.didSetup = setup;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import Janus from './Janus';
2 | import JanusVideoRoomPlugin from './plugins/videoroom/JanusVideoRoomPlugin';
3 | import JanusStreamingPlugin from './plugins/streaming/JanusStreamingPlugin';
4 | import JanusVideoRoomPublisher from './plugins/videoroom/JanusVideoRoomPublisher';
5 |
6 | export {Janus, JanusVideoRoomPlugin, JanusStreamingPlugin, JanusVideoRoomPublisher};
7 |
--------------------------------------------------------------------------------
/plugins/videoroom/JanusVideoRoomPublisher.js:
--------------------------------------------------------------------------------
1 | export default class JanusVideoRoomPublisher {
2 | constructor({audio_codec, display, id, talking, video_codec}) {
3 | this.id = id;
4 | this.displayName = display;
5 | this.isTalking = talking;
6 | this.codecs = {
7 | audio: audio_codec,
8 | video: video_codec,
9 | };
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/utils/iceServers.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'iceServers': [
3 | {
4 | 'url': 'stun:stun.l.google.com:19302',
5 | },
6 | {
7 | 'url': 'stun:stun1.l.google.com:19302',
8 | },
9 | {
10 | 'url': 'stun:stun2.l.google.com:19302',
11 | },
12 | {
13 | 'url': 'stun:stun3.l.google.com:19302',
14 | },
15 | {
16 | 'url': 'stun:stun4.l.google.com:19302',
17 | },
18 | ],
19 | };
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-janus",
3 | "version": "0.0.1",
4 | "description": "A react-native client for Janus WebRTC Gateway",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/canozgen9/react-native-janus.git"
12 | },
13 | "keywords": [
14 | "janus",
15 | "react-native"
16 | ],
17 | "author": "Can ÖZGEN",
18 | "license": "ISC",
19 | "bugs": {
20 | "url": "https://github.com/canozgen9/react-native-janus/issues"
21 | },
22 | "homepage": "https://github.com/canozgen9/react-native-janus#readme"
23 | }
24 |
--------------------------------------------------------------------------------
/utils/JanusUtils.js:
--------------------------------------------------------------------------------
1 | class JanusUtils {
2 | static log(...msg) {
3 | console.log('[Janus]:' + msg.map(msg => {
4 | if (typeof msg === 'object') {
5 | return JSON.stringify(msg);
6 | }
7 | return msg;
8 | }).join(', '));
9 | }
10 |
11 | static randomString(length) {
12 | const charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
13 | let randomString = '';
14 | for (let i = 0; i < length; i++) {
15 | const randomPoz = Math.floor(Math.random() * charSet.length);
16 | randomString += charSet.substring(randomPoz, randomPoz + 1);
17 | }
18 | return randomString;
19 | }
20 | }
21 |
22 | export default JanusUtils;
23 |
--------------------------------------------------------------------------------
/Janus.js:
--------------------------------------------------------------------------------
1 | import JanusSocket from './utils/JanusSocket';
2 |
3 | export default class Janus {
4 | /**
5 | * @type {RTCSessionDescription}
6 | */
7 | static RTCSessionDescription;
8 | /**
9 | * @type {RTCPeerConnection}
10 | */
11 | static RTCPeerConnection;
12 | /**
13 | * @type {RTCIceCandidate}
14 | */
15 | static RTCIceCandidate;
16 |
17 | apiSecret = null;
18 | iceServers = [];
19 |
20 | constructor(address) {
21 | this.socket = new JanusSocket(address);
22 | }
23 |
24 | /**
25 | *
26 | * @param RTCSessionDescription
27 | * @param RTCPeerConnection
28 | * @param RTCIceCandidate
29 | * @param MediaStream
30 | */
31 | static setDependencies = ({RTCSessionDescription, RTCPeerConnection, RTCIceCandidate, MediaStream}) => {
32 | Janus.RTCSessionDescription = RTCSessionDescription;
33 | Janus.RTCPeerConnection = RTCPeerConnection;
34 | Janus.RTCIceCandidate = RTCIceCandidate;
35 | Janus.MediaStream = MediaStream;
36 | };
37 |
38 | init = async () => {
39 | await this.socket.connect();
40 | };
41 |
42 | destroy = async () => {
43 | try {
44 | const destroySessionResponse = await this.socket.sendAsync({
45 | 'janus': 'destroy',
46 | 'session_id': this.socket.sessionID,
47 | });
48 | } catch (e) {
49 | console.error('destroy janus', e);
50 | }
51 |
52 | try {
53 | await this.socket.disconnect();
54 | } catch (e) {
55 | console.error('destroy socket', e);
56 | }
57 | };
58 |
59 | /**
60 | *
61 | * @param secret
62 | */
63 | setApiSecret = (secret) => {
64 | this.apiSecret = secret;
65 | };
66 |
67 | /**
68 | *
69 | * @param {Object[]} iceServers
70 | */
71 | setIceServers = (iceServers) => {
72 | this.iceServers = iceServers;
73 | };
74 | }
75 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | ### Video Room
2 |
3 | ```javascript
4 | import {mediaDevices, MediaStream, RTCIceCandidate, RTCPeerConnection, RTCSessionDescription, RTCView} from 'react-native-webrtc';
5 | import React from 'react';
6 | import {Dimensions, FlatList, StatusBar, View} from 'react-native';
7 | import {Janus, JanusVideoRoomPlugin} from 'react-native-janus';
8 |
9 | Janus.setDependencies({
10 | RTCPeerConnection,
11 | RTCSessionDescription,
12 | RTCIceCandidate,
13 | MediaStream,
14 | });
15 |
16 | class JanusVideoRoomScreen extends React.Component {
17 | constructor(props) {
18 | super(props);
19 |
20 | this.state = {
21 | stream: null,
22 | publishers: [],
23 | };
24 | }
25 |
26 | async receivePublisher(publisher) {
27 | try {
28 | let videoRoom = new JanusVideoRoomPlugin(this.janus);
29 | videoRoom.setRoomID(1234);
30 | videoRoom.setOnStreamListener((stream) => {
31 | this.setState(state => ({
32 | publishers: [
33 | ...state.publishers,
34 | {
35 | publisher: publisher,
36 | stream: stream,
37 | },
38 | ],
39 | }));
40 | });
41 |
42 | await videoRoom.createPeer();
43 | await videoRoom.connect();
44 | await videoRoom.receive(this.videoRoom.getUserPrivateID(), publisher);
45 | } catch (e) {
46 | console.error(e);
47 | }
48 | }
49 |
50 | async removePublisher(publisherID) {
51 | try {
52 | this.setState(state => ({
53 | publishers: state.publishers.filter(pub => pub.publisher == null || pub.publisher.id !== publisherID),
54 | }));
55 | } catch (e) {
56 | console.error(e);
57 | }
58 | }
59 |
60 | async initJanus(stream) {
61 | try {
62 | this.setState(state => ({
63 | publishers: [
64 | {
65 | publisher: null,
66 | stream: stream,
67 | },
68 | ],
69 | }));
70 |
71 | this.janus = new Janus('ws://${YOUR_JANUS_ADDRESS]:8188');
72 | this.janus.setApiSecret('janusrocks');
73 | await this.janus.init();
74 |
75 | this.videoRoom = new JanusVideoRoomPlugin(this.janus);
76 | this.videoRoom.setRoomID(1234);
77 | this.videoRoom.setDisplayName('can');
78 | this.videoRoom.setOnPublishersListener((publishers) => {
79 | for (let i = 0; i < publishers.length; i++) {
80 | this.receivePublisher(publishers[i]);
81 | }
82 | });
83 | this.videoRoom.setOnPublisherJoinedListener((publisher) => {
84 | this.receivePublisher(publisher);
85 | });
86 | this.videoRoom.setOnPublisherLeftListener((publisherID) => {
87 | this.removePublisher(publisherID);
88 | });
89 | this.videoRoom.setOnWebRTCUpListener(async () => {
90 |
91 | });
92 |
93 | await this.videoRoom.createPeer();
94 | await this.videoRoom.connect();
95 | await this.videoRoom.join();
96 | await this.videoRoom.publish(stream);
97 |
98 | } catch (e) {
99 | console.error('main', JSON.stringify(e));
100 | }
101 | }
102 |
103 | getMediaStream = async () => {
104 | let isFront = true;
105 | let sourceInfos = await mediaDevices.enumerateDevices();
106 | let videoSourceId;
107 | for (let i = 0; i < sourceInfos.length; i++) {
108 | const sourceInfo = sourceInfos[i];
109 | console.log(sourceInfo);
110 | if (sourceInfo.kind == 'videoinput' && sourceInfo.facing == (isFront ? 'front' : 'environment')) {
111 | videoSourceId = sourceInfo.deviceId;
112 | }
113 | }
114 |
115 | let stream = await mediaDevices.getUserMedia({
116 | audio: true,
117 | video: {
118 | facingMode: (isFront ? 'user' : 'environment'),
119 | },
120 | });
121 | await this.initJanus(stream);
122 | };
123 |
124 | async componentDidMount() {
125 | this.getMediaStream();
126 | }
127 |
128 | componentWillUnmount = async () => {
129 | if (this.janus) {
130 | await this.janus.destroy();
131 | }
132 | };
133 |
134 | renderView() {
135 | }
136 |
137 | render() {
138 | return (
139 |
140 |
141 | {
145 | if (item.publisher === null) {
146 | return `rtc-default`;
147 | }
148 | return `rtc-${item.publisher.id}`;
149 | }}
150 | renderItem={({item}) => (
151 |
155 | )}
156 | />
157 |
158 | );
159 | }
160 | }
161 |
162 | export default JanusVideoRoomScreen;
163 | ```
164 |
--------------------------------------------------------------------------------
/utils/JanusSocket.js:
--------------------------------------------------------------------------------
1 | import JanusPlugin from './JanusPlugin';
2 | import JanusUtils from './JanusUtils';
3 |
4 | export default class JanusSocket {
5 | constructor(address) {
6 | this.address = address;
7 | this.transactions = {};
8 | this.plugins = {};
9 | this.senders = {};
10 | this.sessionID = null;
11 | this.connected = false;
12 | this.keepAliveTimeoutID = null;
13 | }
14 |
15 | /**
16 | *
17 | * @param {JanusPlugin} plugin
18 | */
19 | attachPlugin = (plugin) => {
20 | this.plugins[plugin.handleID] = plugin;
21 | };
22 |
23 | /**
24 | *
25 | * @param {JanusPlugin} plugin
26 | */
27 | detachPlugin = (plugin) => {
28 | delete this.plugins[plugin.handleID];
29 | };
30 |
31 | connect = () => {
32 | return new Promise(((resolve, reject) => {
33 | this.ws = new WebSocket(this.address, ['janus-protocol']);
34 |
35 | this.ws.onopen = () => {
36 | this.send({
37 | 'janus': 'create',
38 | }, async (response) => {
39 | if (response.janus === 'success') {
40 | this.sessionID = response.data.id;
41 | this.connected = true;
42 | this.setKeepAliveTimeout();
43 | resolve();
44 | } else {
45 | //todo: send error
46 | reject();
47 | }
48 | });
49 | };
50 |
51 | this.ws.onmessage = async (e) => {
52 | try {
53 | let message = JSON.parse(e.data);
54 |
55 | switch (message.janus) {
56 | // general events
57 | case 'keepalive':
58 | case 'ack':
59 | case 'timeout': {
60 | return await this.onMessage(message);
61 | }
62 |
63 | // plugin events
64 | case 'trickle':
65 | case 'webrtcup':
66 | case 'hangup':
67 | case 'detached':
68 | case 'media':
69 | case 'slowlink':
70 | case 'event': {
71 | if (message.transaction) {
72 | const {transaction} = message;
73 | await this.transactions[transaction](message);
74 | return delete this.transactions[transaction];
75 | } else {
76 | return await this.plugins[message.sender].onMessage(message);
77 | }
78 | }
79 |
80 | // transaction responses
81 | case 'success':
82 | case 'error': {
83 | if (message.transaction) {
84 | const {transaction} = message;
85 | await this.transactions[transaction](message);
86 | delete this.transactions[transaction];
87 | return;
88 | }
89 | }
90 | }
91 |
92 | if (message.janus && message.janus !== 'ack' && message.transaction) {
93 | const {transaction} = message;
94 | await this.transactions[transaction](message);
95 | delete this.transactions[transaction];
96 | } else {
97 | if (message.sender && this.plugins[message.sender] && this.plugins[message.sender].onMessage) {
98 | await this.plugins[message.sender].onMessage(message);
99 | } else {
100 | await this.onMessage(message);
101 | }
102 | }
103 | } catch (e) {
104 |
105 | }
106 | };
107 |
108 | this.ws.onerror = (e) => {
109 | JanusUtils.log('socket_error', e);
110 | };
111 |
112 | this.ws.onclose = (e) => {
113 | JanusUtils.log('socket_closed', e.code, e.reason);
114 | };
115 | }));
116 | };
117 |
118 | disconnect = async () => {
119 | this.ws.close();
120 | };
121 |
122 | onMessage = async (message) => {
123 |
124 | };
125 |
126 | send = (request, callback = null, transaction = null) => {
127 | if (!transaction && typeof callback === 'function') {
128 | transaction = JanusUtils.randomString(12);
129 | this.transactions[transaction] = callback;
130 | }
131 |
132 | request['transaction'] = transaction;
133 |
134 | this.ws.send(JSON.stringify(request));
135 | };
136 |
137 | sendAsync = (request) => {
138 | return new Promise((resolve, reject) => {
139 | this.send(request, (response) => {
140 | if (response.janus !== 'error') {
141 | resolve(response);
142 | } else {
143 | reject(response);
144 | }
145 | });
146 | });
147 | };
148 |
149 | keepAlive = () => {
150 | if (this.connected) {
151 | this.setKeepAliveTimeout();
152 | this.send({
153 | 'janus': 'keepalive',
154 | 'session_id': this.sessionID,
155 | }, (response) => {
156 | // JanusUtils.log('keepAlive', response.session_id);
157 | });
158 | }
159 | };
160 |
161 | setKeepAliveTimeout = () => {
162 | if (this.connected) {
163 | this.keepAliveTimeoutID = setTimeout(this.keepAlive, 25000);
164 | }
165 | };
166 | }
167 |
--------------------------------------------------------------------------------
/utils/JanusPlugin.js:
--------------------------------------------------------------------------------
1 | import JanusUtils from './JanusUtils';
2 | import Janus from './../Janus';
3 | import iceServers from './iceServers';
4 |
5 | class JanusPlugin {
6 | /**
7 | *
8 | * @callback onWebRTCUpListener
9 | */
10 | /**
11 | *
12 | * @type {onWebRTCUpListener}
13 | */
14 | onWebRTCUpListener = null;
15 |
16 | /**
17 | *
18 | * @callback onStreamListener
19 | */
20 | /**
21 | *
22 | * @type {onStreamListener}
23 | */
24 | onStreamListener = null;
25 |
26 | /**
27 | *
28 | * @type {boolean}
29 | */
30 | isWebRtcUp = false;
31 |
32 | /**
33 | * @type {Janus.RTCPeerConnection}
34 | */
35 | pc = null;
36 |
37 | /**
38 | * @type {Janus.RTCIceCandidate[]}
39 | */
40 | cachedCandidates = [];
41 |
42 | /**
43 | *
44 | * @type {Janus}
45 | */
46 | janus = null;
47 |
48 | /**
49 | *
50 | * @type {boolean}
51 | */
52 | isReceivingAudio = false;
53 |
54 |
55 | /**
56 | *
57 | * @type {boolean}
58 | */
59 | isReceivingVideo = false;
60 |
61 | /**
62 | *
63 | * @param id
64 | * @param {Janus} janus
65 | */
66 | constructor(id, janus) {
67 | this.id = id;
68 | this.janus = janus;
69 | this.handleID = null;
70 | }
71 |
72 | connect = async () => {
73 | return new Promise(((resolve, reject) => {
74 | this.janus.socket.send({
75 | 'janus': 'attach',
76 | 'plugin': this.id,
77 | 'opaque_id': `janus-${JanusUtils.randomString(12)}`,
78 | 'session_id': this.janus.socket.sessionID,
79 | }, (response) => {
80 | if (response.janus === 'success') {
81 | this.handleID = response.data.id;
82 | this.janus.socket.attachPlugin(this);
83 | resolve();
84 | return;
85 | }
86 | reject();
87 | });
88 | }));
89 | };
90 |
91 | createPeer = async () => {
92 | this.pc = new Janus.RTCPeerConnection({
93 | 'iceServers': [
94 | ...iceServers['iceServers'],
95 | ...this.janus.iceServers,
96 | ]
97 | });
98 |
99 | this.pc.onicecandidate = async (event) => {
100 | if (!event.candidate || event.candidate.candidate.indexOf('endOfCandidates') > 0) {
101 |
102 | } else {
103 | await this.sendCandidateAsync(event.candidate);
104 | }
105 | };
106 |
107 | this.pc.onaddstream = (event) => {
108 | if (event && event.stream) {
109 | if (this.onStreamListener && typeof this.onStreamListener === 'function') {
110 | this.onStreamListener(event.stream);
111 | }
112 | }
113 | };
114 |
115 | this.pc.onremovestream = (event) => {
116 | //fixme: what to do on remove stream?
117 | console.error('onremovestream', event);
118 | if (this.onStreamListener && typeof this.onStreamListener === 'function') {
119 | this.onStreamListener(null);
120 | }
121 | };
122 |
123 | this.pc.onnegotiationneeded = (e) => {
124 | console.log('onnegotiationneeded', e.target);
125 | };
126 |
127 | this.pc.onconnectionstatechange = (e) => {
128 | e.target.connectionState; // new, connected, connecting
129 | e.target.signalingState; // stable, have-local-offer, have-remote-offer, have-local-pranswer, have-remote-pranswer, closed
130 | e.target.iceConnectionState; // new, checking, connected, completed, failed, disconnected, closed
131 | e.target.iceGatheringState; // new, complete, gathering
132 | console.log('connectionState', e.target.connectionState);
133 | };
134 |
135 | this.pc.onsignalingstatechange = (e) => {
136 | console.log('signalingState', e.target.signalingState);
137 | };
138 |
139 | this.pc.onicecandidateerror = (e) => {
140 | console.log('onicecandidateerror', e);
141 | };
142 |
143 | this.pc.onicegatheringstatechange = (e) => {
144 | console.log('iceConnectionState', e.target.iceConnectionState);
145 | };
146 |
147 | this.pc.oniceconnectionstatechange = (e) => {
148 | console.log('iceGatheringState', e.target.iceGatheringState);
149 | };
150 | };
151 |
152 | /**
153 | *
154 | * @param listener {onWebRTCUpListener}
155 | */
156 | setOnWebRTCUpListener = (listener) => {
157 | this.onWebRTCUpListener = listener;
158 | };
159 |
160 | /**
161 | *
162 | * @param listener {onStreamListener}
163 | */
164 | setOnStreamListener = (listener) => {
165 | this.onStreamListener = listener;
166 | };
167 |
168 | send = (request, callback) => {
169 | return this.janus.socket.send({
170 | 'janus': 'message',
171 | 'session_id': this.janus.socket.sessionID,
172 | 'handle_id': this.handleID,
173 | 'body': request,
174 | }, callback);
175 | };
176 |
177 | sendAsyncWithJsep = (request, jsep) => {
178 | return this.janus.socket.sendAsync({
179 | 'janus': 'message',
180 | 'session_id': this.janus.socket.sessionID,
181 | 'handle_id': this.handleID,
182 | 'body': request,
183 | 'jsep': jsep,
184 | });
185 | };
186 |
187 | sendCandidateAsync = (candidate) => {
188 | return this.janus.socket.sendAsync({
189 | 'janus': 'trickle',
190 | 'session_id': this.janus.socket.sessionID,
191 | 'handle_id': this.handleID,
192 | 'candidate': candidate,
193 | });
194 | };
195 |
196 | sendAsync = (request) => {
197 | let additionalConfig = {};
198 | if (this.janus.apiSecret) {
199 | additionalConfig['apisecret'] = this.janus.apiSecret;
200 | }
201 | return this.janus.socket.sendAsync({
202 | 'janus': 'message',
203 | 'session_id': this.janus.socket.sessionID,
204 | 'handle_id': this.handleID,
205 | 'body': request,
206 | ...additionalConfig,
207 | });
208 | };
209 |
210 | detach = () => {
211 | let additionalConfig = {};
212 |
213 | if (this.janus.apiSecret) {
214 | additionalConfig['apisecret'] = this.janus.apiSecret;
215 | }
216 |
217 | return this.janus.socket.sendAsync({
218 | 'janus': 'detach',
219 | 'session_id': this.janus.socket.sessionID,
220 | 'handle_id': this.handleID,
221 | ...additionalConfig,
222 | });
223 | };
224 | }
225 |
226 | export default JanusPlugin;
227 |
--------------------------------------------------------------------------------
/plugins/streaming/JanusStreamingPlugin.js:
--------------------------------------------------------------------------------
1 | import JanusPlugin from './../../utils/JanusPlugin';
2 | import Janus from './../../Janus';
3 |
4 | export default class JanusStreamingPlugin extends JanusPlugin {
5 | /**
6 | *
7 | * @param {Janus} janus
8 | */
9 | constructor(janus) {
10 | super('janus.plugin.streaming', janus);
11 |
12 | this.isRemoteDescriptionSet = false;
13 | this.cachedCandidates = [];
14 | }
15 |
16 | onMessage = async (message) => {
17 | switch (message.janus) {
18 | case 'webrtcup': {
19 | this.isWebRtcUp = true;
20 | if (this.onWebRTCUpListener && typeof this.onWebRTCUpListener === 'function') {
21 | this.onWebRTCUpListener();
22 | }
23 | return;
24 | }
25 |
26 | case 'media': {
27 | if (message.type === 'audio') {
28 | this.isReceivingAudio = message.receiving;
29 | console.log('plugin', (message.receiving ? '' : 'not ') + 'receiving audio now...');
30 | } else if (message.type === 'video') {
31 | this.isReceivingVideo = message.receiving;
32 | console.log('plugin', (message.receiving ? '' : 'not ') + 'receiving video now...');
33 | }
34 | return;
35 | }
36 |
37 | case 'trickle': {
38 |
39 | if (this.isRemoteDescriptionSet) {
40 | await this.pc.addIceCandidate(new Janus.RTCIceCandidate({
41 | candidate: message.candidate.candidate,
42 | sdpMid: message.candidate.sdpMid,
43 | sdpMLineIndex: message.candidate.sdpMLineIndex,
44 | }));
45 | }
46 |
47 | this.cachedCandidates.push(new Janus.RTCIceCandidate({
48 | candidate: message.candidate.candidate,
49 | sdpMid: message.candidate.sdpMid,
50 | sdpMLineIndex: message.candidate.sdpMLineIndex,
51 | }));
52 | return;
53 | }
54 |
55 | case 'hangup': {
56 | message.reason;
57 | console.log('plugin', 'hangup', message.reason);
58 | return;
59 | }
60 |
61 | case 'detached': {
62 | console.log('plugin', 'detached');
63 | return;
64 | }
65 |
66 | case 'slowlink': {
67 | console.log('plugin', 'slowlink', message);
68 | return;
69 | }
70 |
71 | case 'event': {
72 | const data = message.plugindata.data;
73 | console.log('plugin', 'event', data);
74 | return;
75 | }
76 | }
77 | };
78 |
79 | /**
80 | *
81 | * @param id
82 | * @param pin
83 | * @returns {Promise}
84 | */
85 | watch = async (id, {pin}) => {
86 | try {
87 | let additionalConfig = {};
88 |
89 | if (pin) {
90 | additionalConfig.pin = additionalConfig;
91 | }
92 |
93 | const watchStreamingResponse = await this.sendAsync({
94 | 'request': 'watch',
95 | 'id': id,
96 | 'offer_audio': true,
97 | 'offer_video': true,
98 | 'offer_data': false,
99 | ...additionalConfig,
100 | });
101 |
102 |
103 | if (watchStreamingResponse.jsep) {
104 | await this.pc.setRemoteDescription(new Janus.RTCSessionDescription({
105 | sdp: watchStreamingResponse.jsep.sdp,
106 | type: watchStreamingResponse.jsep.type,
107 | }));
108 | this.isRemoteDescriptionSet = true;
109 |
110 | console.error('watch', 'set remote desc');
111 |
112 | for (const candidate of this.cachedCandidates) {
113 | await this.pc.addIceCandidate(candidate);
114 | }
115 | this.cachedCandidates = [];
116 |
117 | console.error('watch', 'added candidates');
118 |
119 | let answer = await this.pc.createAnswer({
120 | 'offerToReceiveAudio': true,
121 | 'offerToReceiveVideo': true,
122 | mandatory: {
123 | OfferToReceiveAudio: true,
124 | OfferToReceiveVideo: true,
125 | },
126 | });
127 |
128 | await this.pc.setLocalDescription(answer);
129 |
130 | const startResponse = await this.sendAsyncWithJsep({
131 | 'request': 'start',
132 | }, {
133 | type: answer.type,
134 | sdp: answer.sdp,
135 | });
136 |
137 | }
138 |
139 | // if (watchStreamingResponse && watchStreamingResponse.plugindata && watchStreamingResponse.plugindata.data && watchStreamingResponse.plugindata.data.streaming === 'created') {
140 | // return true;
141 | // }
142 | return false;
143 | } catch (e) {
144 | console.error('createStreamingResponse', e);
145 | }
146 | };
147 |
148 |
149 | create = async (id, {name, description, pin, secret, audioPort, audioPt, videoPort, videoPt}) => {
150 | try {
151 | let additionalConfig = {};
152 |
153 | if (name) {
154 | additionalConfig.name = name;
155 | }
156 |
157 | if (description) {
158 | additionalConfig.description = description;
159 | }
160 |
161 | if (pin) {
162 | additionalConfig.pin = description;
163 | }
164 |
165 | if (secret) {
166 | additionalConfig.secret = description;
167 | }
168 |
169 | const createStreamingResponse = await this.sendAsync({
170 | 'request': 'create',
171 | 'type': 'rtp',
172 | 'id': id,
173 | 'metadata': '',
174 | 'is_private': true,
175 | 'audio': true,
176 | 'video': true,
177 | 'data': false,
178 | 'permanent': false,
179 |
180 | audioport: audioPort,
181 | audiopt: audioPt,
182 | audiortpmap: 'opus/48000/2',
183 | audiofmtp: `${audioPt}`,
184 |
185 | videoport: videoPort,
186 | videopt: videoPt,
187 | videortpmap: 'VP8/90000',
188 | videofmtp: `${videoPt}`,
189 | videobufferkf: true,
190 |
191 | ...additionalConfig,
192 | });
193 |
194 |
195 | if (createStreamingResponse && createStreamingResponse.plugindata && createStreamingResponse.plugindata.data && createStreamingResponse.plugindata.data.streaming === 'created') {
196 | return true;
197 | }
198 |
199 | console.error('createStreamingResponse', createStreamingResponse);
200 | return false;
201 | } catch (e) {
202 | console.error('createStreamingResponse', e);
203 | }
204 | };
205 |
206 | destroy = async (id) => {
207 | try {
208 | const destroyStreamingResponse = await this.sendAsync({
209 | 'request': 'destroy',
210 | 'id': id,
211 | 'secret': '1234',
212 | 'permanent': false,
213 | });
214 |
215 | if (destroyStreamingResponse && destroyStreamingResponse.plugindata && destroyStreamingResponse.plugindata.data && destroyStreamingResponse.plugindata.data.destroyed === id) {
216 | return true;
217 | }
218 |
219 | console.error('destroyStreamingResponse', destroyStreamingResponse);
220 | return false;
221 | } catch (e) {
222 | console.error('createStreamingResponse', e);
223 | }
224 | };
225 | }
226 |
--------------------------------------------------------------------------------
/plugins/audiobridge/JanusAudioBridgePlugin.js:
--------------------------------------------------------------------------------
1 | import JanusPlugin from "calldemo/node_modules/react-native-janus/utils/JanusPlugin.js";
2 | import { Janus } from "../../node_modules/react-native-janus";
3 | import JanusAudioBridgeParticipant from "./AudioBridgeParticipant";
4 |
5 | export default class JanusAudioBridgePulgin extends JanusPlugin {
6 | /**
7 | * Array of audio bridge participants
8 | * @type {JanusAudioBridgeParticipant[]}
9 | */
10 | participants = null;
11 |
12 | /**
13 | *
14 | * @callback onParticipantsListener
15 | * @param {JanusAudioBridgeParticipant[]} participants
16 | */
17 | /**
18 | *
19 | * @type {onPublishersListener}
20 | */
21 | onPublishersListener = null;
22 | /**
23 | *
24 | * @callback onParticipantLeftListener
25 | * @param {number} participantID
26 | */
27 | /**
28 | *
29 | * @type {onParticipantLeftListener}
30 | */
31 | onParticipantLeftListener = null;
32 | /**
33 | *
34 | * @param {Janus} janus
35 | */
36 | /**
37 | *
38 | * @callback onParticipantJoinedListener
39 | * @param {onParticipantJoinedListener} participant
40 | */
41 | /**
42 | *
43 | * @type {onParticipantJoinedListener}
44 | */
45 | onParticipantJoinedListener = null;
46 | constructor(janus) {
47 | super("janus.plugin.audiobridge", janus);
48 |
49 | this.userID = null;
50 | this.participants = null;
51 |
52 | this.roomID = null;
53 | this.displayName = null;
54 | this.stream = null;
55 |
56 | this.isRemoteDescriptionSet = false;
57 | }
58 |
59 | getUserID = () => this.userID;
60 |
61 | /**
62 | *
63 | * @param {Number} roomID
64 | */
65 | setRoomID = (roomID) => {
66 | this.roomID = roomID;
67 | };
68 |
69 | /**
70 | *
71 | * @param {String} displayName
72 | */
73 | setDisplayName = (displayName) => {
74 | this.displayName = displayName;
75 | };
76 |
77 | /**
78 | *
79 | * @param listener {onParticipantsListener}
80 | */
81 | setOnParticipantsListener = (listener) => {
82 | this.onParticipantsListener = listener;
83 | };
84 | /**
85 | *
86 | * @param listener {onParticipantJoinedListener}
87 | */
88 | setOnParticipantJoinedListener = (listener) => {
89 | this.onParticipantJoinedListener = listener;
90 | };
91 |
92 | /**
93 | *
94 | * @param listener {onParticipantLeftListener}
95 | */
96 | setOnParticipantLeftListener = (listener) => {
97 | this.onParticipantLeftListener = listener;
98 | };
99 |
100 | onMessage = async (message) => {
101 | switch (message.janus) {
102 | case "webrtcup": {
103 | this.isWebRtcUp = true;
104 | if (
105 | this.onWebRTCUpListener &&
106 | typeof this.onWebRTCUpListener === "function"
107 | ) {
108 | this.onWebRTCUpListener();
109 | }
110 | return;
111 | }
112 |
113 | case "media": {
114 | if (message.type === "audio") {
115 | this.isReceivingAudio = message.receiving;
116 | console.log(
117 | "plugin",
118 | (message.receiving ? "" : "not ") + "receiving audio now..."
119 | );
120 | } else if (message.type === "video") {
121 | this.isReceivingVideo = message.receiving;
122 | console.log(
123 | "plugin",
124 | (message.receiving ? "" : "not ") + "receiving video now..."
125 | );
126 | }
127 | return;
128 | }
129 |
130 | case "trickle": {
131 | console.log("got trickle");
132 | if (this.isRemoteDescriptionSet) {
133 | console.log("adding ice to pc");
134 | await this.pc.addIceCandidate(
135 | new Janus.RTCIceCandidate({
136 | candidate: message.candidate.candidate,
137 | sdpMid: message.candidate.sdpMid,
138 | sdpMLineIndex: message.candidate.sdpMLineIndex,
139 | })
140 | );
141 | }
142 |
143 | this.cachedCandidates.push(
144 | new Janus.RTCIceCandidate({
145 | candidate: message.candidate.candidate,
146 | sdpMid: message.candidate.sdpMid,
147 | sdpMLineIndex: message.candidate.sdpMLineIndex,
148 | })
149 | );
150 |
151 | return;
152 | }
153 |
154 | case "hangup": {
155 | message.reason;
156 | console.log("plugin", "hangup", message.reason);
157 | return;
158 | }
159 |
160 | case "detached": {
161 | console.log("plugin", "detached");
162 | return;
163 | }
164 |
165 | case "slowlink": {
166 | console.log("plugin", "slowlink", message);
167 | return;
168 | }
169 |
170 | case "event": {
171 | const data = message.plugindata.data;
172 |
173 | if (data.audiobridge === "event") {
174 | if (data.room === this.roomID) {
175 | if (typeof data["leaving"] !== "undefined") {
176 | let leavingParticipantID = data["leaving"];
177 | if (
178 | this.onParticipantLeftListener != null &&
179 | typeof this.onParticipantLeftListener === "function"
180 | ) {
181 | this.onParticipantLeftListener(leavingParticipantID);
182 | }
183 | return;
184 | } else if (typeof data["publishers"] !== "undefined") {
185 | let newParticipants = data["participants"].map(
186 | (participantsrData) =>
187 | new JanusAudioBridgeParticipant(participantsrData)
188 | );
189 | for (let i = 0; i < newParticipants.length; i++) {
190 | if (
191 | this.onParticipantJoinedListener != null &&
192 | typeof this.onParticipantJoinedListener === "function"
193 | ) {
194 | this.onParticipantJoinedListener(newParticipants[i]);
195 | }
196 | }
197 | return;
198 | }
199 | }
200 | }
201 |
202 | console.log("plugin", "event", data);
203 | return;
204 | }
205 | }
206 | };
207 |
208 | /**
209 | *
210 | * @returns {Promise}
211 | */
212 | join = async (roomid) => {
213 | try {
214 | let joinResponse = await this.sendAsync({
215 | request: "join",
216 | room: roomid,
217 | display: this.displayName,
218 | muted: false,
219 | });
220 |
221 | if (
222 | joinResponse.janus === "event" &&
223 | joinResponse.plugindata &&
224 | joinResponse.plugindata.data
225 | ) {
226 | let data = joinResponse.plugindata.data;
227 | if (data.audiobridge === "joined") {
228 | this.userID = data.id;
229 | this.participants = data.participants.map(
230 | (participantsrData) =>
231 | new JanusAudioBridgeParticipant(participantsrData)
232 | );
233 |
234 | if (
235 | this.onParticipantsListener != null &&
236 | typeof this.onParticipantsListener === "function"
237 | ) {
238 | this.onParticipantsListener(this.participants);
239 | }
240 |
241 | return;
242 | } else {
243 | console.error("join", joinResponse);
244 | }
245 | } else {
246 | console.error("join", joinResponse);
247 | }
248 | } catch (e) {
249 | console.error("join", e);
250 | }
251 | };
252 |
253 | /**
254 | *
255 | * @param {Janus.MediaStream} stream
256 | * @returns {Promise}
257 | */
258 | configure = async (stream) => {
259 | try {
260 | console.log("joined to room.");
261 |
262 | this.pc.addStream(stream);
263 |
264 | let offer = await this.pc.createOffer({
265 | offerToReceiveAudio: true,
266 | offerToReceiveVideo: false,
267 | });
268 |
269 | await this.pc.setLocalDescription(offer);
270 |
271 | // offer.sdp = offer.sdp.replace(/a=extmap:(\d+) urn:3gpp:video-orientation\n?/, '');
272 |
273 | let response = await this.sendAsyncWithJsep(
274 | {
275 | request: "configure",
276 | muted: false,
277 | // recording preferences
278 | record: true,
279 | filename: "recording.wav",
280 | },
281 | {
282 | type: offer.type,
283 | sdp: offer.sdp,
284 | }
285 | );
286 |
287 | await this.pc.setRemoteDescription(
288 | new Janus.RTCSessionDescription({
289 | sdp: response.jsep.sdp,
290 | type: response.jsep.type,
291 | })
292 | );
293 | this.isRemoteDescriptionSet = true;
294 |
295 | for (const candidate of this.cachedCandidates) {
296 | await this.pc.addIceCandidate(candidate);
297 | }
298 | this.cachedCandidates = [];
299 | } catch (e) {
300 | console.log(e);
301 | }
302 | };
303 |
304 | detach = async () => {
305 | try {
306 | let additionalConfig = {};
307 |
308 | if (this.janus.apiSecret) {
309 | additionalConfig["apisecret"] = this.janus.apiSecret;
310 | }
311 |
312 | const hangupResponse = await this.janus.socket.sendAsync({
313 | janus: "hangup",
314 | session_id: this.janus.socket.sessionID,
315 | handle_id: this.handleID,
316 | ...additionalConfig,
317 | });
318 |
319 | if (hangupResponse.janus === "success") {
320 | }
321 |
322 | const detachResponse = await this.detach();
323 |
324 | if (detachResponse.janus === "success") {
325 | }
326 |
327 | this.pc.close();
328 | this.janus.socket.detachPlugin(this);
329 |
330 | console.error("detach", "hangupResponse", hangupResponse);
331 | console.error("detach", "detachResponse", detachResponse);
332 | } catch (e) {
333 | console.error("detach", e);
334 | }
335 | };
336 |
337 | create = async (room) => {
338 | try {
339 | let createResponse = await this.sendAsync({
340 | request: "create",
341 | room: room,
342 | description: "demo",
343 | record: true,
344 | });
345 | this.setRoomID(room);
346 |
347 | // todo finish response handling
348 | console.log("create respone", createResponse);
349 | } catch (e) {
350 | console.log("create", e);
351 | }
352 | };
353 | }
354 |
--------------------------------------------------------------------------------
/plugins/videoroom/JanusVideoRoomPlugin.js:
--------------------------------------------------------------------------------
1 | import JanusVideoRoomPublisher from "./JanusVideoRoomPublisher";
2 | import JanusPlugin from "./../../utils/JanusPlugin";
3 | import Janus from "./../../Janus";
4 |
5 | export default class JanusVideoRoomPlugin extends JanusPlugin {
6 | /**
7 | * Array of video room publishers
8 | * @type {JanusVideoRoomPublisher[]}
9 | */
10 | publishers = null;
11 |
12 | /**
13 | *
14 | * @type {Janus.MediaStream}
15 | */
16 | stream = null;
17 |
18 | /**
19 | *
20 | * @callback onPublishersListener
21 | * @param {JanusVideoRoomPublisher[]} publishers
22 | */
23 | /**
24 | *
25 | * @type {onPublishersListener}
26 | */
27 | onPublishersListener = null;
28 | /**
29 | *
30 | * @callback onPublisherJoinedListener
31 | * @param {JanusVideoRoomPublisher} publisher
32 | */
33 | /**
34 | *
35 | * @type {onPublisherJoinedListener}
36 | */
37 | onPublisherJoinedListener = null;
38 | /**
39 | *
40 | * @callback onPublisherLeftListener
41 | * @param {number} publisherID
42 | */
43 | /**
44 | *
45 | * @type {onPublisherLeftListener}
46 | */
47 | onPublisherLeftListener = null;
48 | /**
49 | *
50 | * @callback onPublisherUpdatedListener
51 | * @param {JanusVideoRoomPublisher} publisher
52 | */
53 | /**
54 | *
55 | * @type {onPublisherUpdatedListener}
56 | */
57 | onPublisherUpdatedListener = null;
58 | /**
59 | *
60 | * @callback onStreamAddedListener
61 | * @param {Janus.MediaStream} stream
62 | */
63 | /**
64 | *
65 | * @type {onStreamAddedListener}
66 | */
67 | onStreamAddedListener = null;
68 | /**
69 | *
70 | * @callback onStreamRemovedListener
71 | * @param {Janus.MediaStream} stream
72 | */
73 | /**
74 | *
75 | * @type {onStreamRemovedListener}
76 | */
77 | onStreamRemovedListener = null;
78 |
79 | /**
80 | *
81 | * @param {Janus} janus
82 | */
83 | constructor(janus) {
84 | super("janus.plugin.videoroom", janus);
85 |
86 | this.userID = null;
87 | this.userPrivateID = null;
88 | this.publishers = null;
89 |
90 | this.roomID = null;
91 | this.displayName = null;
92 | this.stream = null;
93 |
94 | this.isRemoteDescriptionSet = false;
95 | }
96 |
97 | getUserID = () => this.userID;
98 | getUserPrivateID = () => this.userPrivateID;
99 |
100 | /**
101 | *
102 | * @param {Number} roomID
103 | */
104 | setRoomID = (roomID) => {
105 | this.roomID = roomID;
106 | };
107 |
108 | /**
109 | *
110 | * @param {String} displayName
111 | */
112 | setDisplayName = (displayName) => {
113 | this.displayName = displayName;
114 | };
115 |
116 | /**
117 | *
118 | * @returns {JanusVideoRoomPublisher[]}
119 | */
120 | getPublishers = () => {
121 | return this.publishers;
122 | };
123 |
124 | /**
125 | *
126 | * @param listener {onPublishersListener}
127 | */
128 | setOnPublishersListener = (listener) => {
129 | this.onPublishersListener = listener;
130 | };
131 |
132 | /**
133 | *
134 | * @param listener {onPublisherJoinedListener}
135 | */
136 | setOnPublisherJoinedListener = (listener) => {
137 | this.onPublisherJoinedListener = listener;
138 | };
139 |
140 | /**
141 | *
142 | * @param listener {onPublisherLeftListener}
143 | */
144 | setOnPublisherLeftListener = (listener) => {
145 | this.onPublisherLeftListener = listener;
146 | };
147 |
148 | /**
149 | *
150 | * @param listener {setOnPublisherUpdatedListener}
151 | */
152 | setOnPublisherUpdatedListener = (listener) => {
153 | this.onPublisherUpdatedListener = listener;
154 | };
155 |
156 | /**
157 | *
158 | * @param listener {onStreamAddedListener}
159 | */
160 | setOnStreamAddedListener = (listener) => {
161 | this.onStreamAddedListener = listener;
162 | };
163 |
164 | /**
165 | *
166 | * @param listener {onStreamRemovedListener}
167 | */
168 | setOnStreamRemovedListener = (listener) => {
169 | this.onStreamRemovedListener = listener;
170 | };
171 |
172 | onMessage = async (message) => {
173 | switch (message.janus) {
174 | case "webrtcup": {
175 | this.isWebRtcUp = true;
176 | if (
177 | this.onWebRTCUpListener &&
178 | typeof this.onWebRTCUpListener === "function"
179 | ) {
180 | this.onWebRTCUpListener();
181 | }
182 | return;
183 | }
184 |
185 | case "media": {
186 | if (message.type === "audio") {
187 | this.isReceivingAudio = message.receiving;
188 | console.log(
189 | "plugin",
190 | (message.receiving ? "" : "not ") + "receiving audio now..."
191 | );
192 | } else if (message.type === "video") {
193 | this.isReceivingVideo = message.receiving;
194 | console.log(
195 | "plugin",
196 | (message.receiving ? "" : "not ") + "receiving video now..."
197 | );
198 | }
199 | return;
200 | }
201 |
202 | case "trickle": {
203 | if (this.isRemoteDescriptionSet) {
204 | await this.pc.addIceCandidate(
205 | new Janus.RTCIceCandidate({
206 | candidate: message.candidate.candidate,
207 | sdpMid: message.candidate.sdpMid,
208 | sdpMLineIndex: message.candidate.sdpMLineIndex,
209 | })
210 | );
211 | }
212 |
213 | this.cachedCandidates.push(
214 | new Janus.RTCIceCandidate({
215 | candidate: message.candidate.candidate,
216 | sdpMid: message.candidate.sdpMid,
217 | sdpMLineIndex: message.candidate.sdpMLineIndex,
218 | })
219 | );
220 |
221 | return;
222 | }
223 |
224 | case "hangup": {
225 | message.reason;
226 | console.log("plugin", "hangup", message.reason);
227 | return;
228 | }
229 |
230 | case "detached": {
231 | console.log("plugin", "detached");
232 | return;
233 | }
234 |
235 | case "slowlink": {
236 | console.log("plugin", "slowlink", message);
237 | return;
238 | }
239 |
240 | case "event": {
241 | const data = message.plugindata.data;
242 |
243 | if (data.videoroom === "event") {
244 | if (data.room === this.roomID) {
245 | if (typeof data["unpublished"] !== "undefined") {
246 | // let unpublishedPublisherID = data['unpublished'];
247 | // let leavingPublisherID = data['leaving'];
248 | // if (this.onPublisherLeftListener != null && typeof this.onPublisherLeftListener === 'function') {
249 | // this.onPublisherLeftListener(leavingPublisherID);
250 | // }
251 | return;
252 | } else if (typeof data["leaving"] !== "undefined") {
253 | let leavingPublisherID = data["leaving"];
254 | if (
255 | this.onPublisherLeftListener != null &&
256 | typeof this.onPublisherLeftListener === "function"
257 | ) {
258 | this.onPublisherLeftListener(leavingPublisherID);
259 | }
260 | return;
261 | } else if (typeof data["publishers"] !== "undefined") {
262 | let newPublishers = data["publishers"].map(
263 | (publisherData) => new JanusVideoRoomPublisher(publisherData)
264 | );
265 | for (let i = 0; i < newPublishers.length; i++) {
266 | if (
267 | this.onPublisherJoinedListener != null &&
268 | typeof this.onPublisherJoinedListener === "function"
269 | ) {
270 | this.onPublisherJoinedListener(newPublishers[i]);
271 | }
272 | }
273 | return;
274 | }
275 | }
276 | }
277 |
278 | console.log("plugin", "event", data);
279 | return;
280 | }
281 | }
282 | };
283 |
284 | forward = async ({
285 | host,
286 | audioPort,
287 | audioPt,
288 | videoPort,
289 | videoPt,
290 | secret,
291 | }) => {
292 | try {
293 | let additionalConfig = {};
294 |
295 | if (secret) {
296 | additionalConfig.secret = secret;
297 | }
298 |
299 | const rtpForwardResponse = await this.sendAsync({
300 | request: "rtp_forward",
301 | publisher_id: this.userID,
302 | room: this.roomID,
303 | audio_port: audioPort,
304 | audio_pt: audioPt,
305 | video_port: videoPort,
306 | video_pt: videoPt,
307 | host: host,
308 | ...additionalConfig,
309 | });
310 |
311 | if (
312 | rtpForwardResponse &&
313 | rtpForwardResponse.plugindata &&
314 | rtpForwardResponse.plugindata.data &&
315 | rtpForwardResponse.plugindata.data.rtp_stream
316 | ) {
317 | return true;
318 | }
319 |
320 | console.error("rtpForwardResponse", "success!", rtpForwardResponse);
321 | } catch (e) {
322 | console.error("streaming", e);
323 | }
324 | };
325 |
326 | muteAudio() {}
327 |
328 | unmuteAudio() {}
329 |
330 | closeVideo() {}
331 |
332 | openVideo() {}
333 |
334 | /**
335 | *
336 | * @returns {Promise}
337 | */
338 | join = async () => {
339 | try {
340 | let joinResponse = await this.sendAsync({
341 | request: "join",
342 | room: this.roomID,
343 | display: this.displayName,
344 | ptype: "publisher",
345 | });
346 |
347 | if (
348 | joinResponse.janus === "event" &&
349 | joinResponse.plugindata &&
350 | joinResponse.plugindata.data
351 | ) {
352 | let data = joinResponse.plugindata.data;
353 | if (data.videoroom === "joined") {
354 | this.userID = data.id;
355 | this.userPrivateID = data.private_id;
356 | this.publishers = data.publishers.map(
357 | (publisherData) => new JanusVideoRoomPublisher(publisherData)
358 | );
359 |
360 | if (
361 | this.onPublishersListener != null &&
362 | typeof this.onPublishersListener === "function"
363 | ) {
364 | this.onPublishersListener(this.publishers);
365 | }
366 |
367 | return;
368 | } else {
369 | console.error("join", joinResponse);
370 | }
371 | } else {
372 | console.error("join", joinResponse);
373 | }
374 | } catch (e) {
375 | console.error("join", e);
376 | }
377 | };
378 |
379 | /**
380 | *
381 | * @returns {Promise}
382 | */
383 | leave = async () => {};
384 |
385 | /**
386 | *
387 | * @param {Janus.MediaStream} stream
388 | * @returns {Promise}
389 | */
390 | publish = async (stream, audio = true, video = true) => {
391 | try {
392 | console.log("joined to room.");
393 |
394 | this.pc.addStream(stream);
395 |
396 | let offer = await this.pc.createOffer({
397 | offerToReceiveAudio: false,
398 | offerToReceiveVideo: false,
399 | });
400 |
401 | await this.pc.setLocalDescription(offer);
402 |
403 | // offer.sdp = offer.sdp.replace(/a=extmap:(\d+) urn:3gpp:video-orientation\n?/, '');
404 |
405 | let response = await this.sendAsyncWithJsep(
406 | {
407 | request: "configure",
408 | audio: audio,
409 | video: video,
410 | bitrate: 128 * 1000,
411 | },
412 | {
413 | type: offer.type,
414 | sdp: offer.sdp,
415 | }
416 | );
417 |
418 | await this.pc.setRemoteDescription(
419 | new Janus.RTCSessionDescription({
420 | sdp: response.jsep.sdp,
421 | type: response.jsep.type,
422 | })
423 | );
424 | this.isRemoteDescriptionSet = true;
425 |
426 | for (const candidate of this.cachedCandidates) {
427 | await this.pc.addIceCandidate(candidate);
428 | }
429 | this.cachedCandidates = [];
430 | } catch (e) {
431 | console.log(e);
432 | }
433 | };
434 |
435 | /**
436 | * @returns {Promise}
437 | */
438 | unpublish = async () => {
439 | try {
440 | const unpublishResponse = await this.sendAsync({
441 | request: "unpublish",
442 | });
443 |
444 | if (
445 | unpublishResponse &&
446 | unpublishResponse.plugindata &&
447 | unpublishResponse.plugindata.data &&
448 | unpublishResponse.plugindata.data.unpublished === "ok"
449 | ) {
450 | return;
451 | }
452 |
453 | console.error("unpublishResponse", unpublishResponse);
454 | } catch (e) {
455 | console.error("unpublish", e);
456 | }
457 | };
458 |
459 | /**
460 | *
461 | * @param privateID
462 | * @param {JanusVideoRoomPublisher} publisher
463 | * @returns {Promise}
464 | */
465 | receive = async (privateID, publisher, audio = true, video = true) => {
466 | try {
467 | let joinResponse = await this.sendAsync({
468 | request: "join",
469 | room: this.roomID,
470 | ptype: "subscriber",
471 | feed: publisher.id,
472 | private_id: privateID,
473 | });
474 |
475 | if (
476 | joinResponse &&
477 | joinResponse.plugindata &&
478 | joinResponse.plugindata.data &&
479 | joinResponse.plugindata.data.videoroom === "attached"
480 | ) {
481 | // OK
482 | }
483 |
484 | await this.pc.setRemoteDescription(
485 | new Janus.RTCSessionDescription({
486 | sdp: joinResponse.jsep.sdp,
487 | type: joinResponse.jsep.type,
488 | })
489 | );
490 | this.isRemoteDescriptionSet = true;
491 |
492 | for (const candidate of this.cachedCandidates) {
493 | await this.pc.addIceCandidate(candidate);
494 | }
495 | this.cachedCandidates = [];
496 |
497 | let answer = await this.pc.createAnswer({
498 | offerToReceiveAudio: audio,
499 | offerToReceiveVideo: video,
500 | });
501 |
502 | await this.pc.setLocalDescription(answer);
503 |
504 | const startResponse = await this.sendAsyncWithJsep(
505 | {
506 | request: "start",
507 | room: this.roomID,
508 | },
509 | {
510 | type: answer.type,
511 | sdp: answer.sdp,
512 | }
513 | );
514 |
515 | if (
516 | startResponse &&
517 | startResponse.plugindata &&
518 | startResponse.plugindata.data &&
519 | startResponse.plugindata.data.started === "ok"
520 | ) {
521 | // OK
522 | }
523 | } catch (e) {
524 | console.error("receive", e);
525 | }
526 | };
527 |
528 | detach = async () => {
529 | try {
530 | let additionalConfig = {};
531 |
532 | if (this.janus.apiSecret) {
533 | additionalConfig["apisecret"] = this.janus.apiSecret;
534 | }
535 |
536 | const hangupResponse = await this.janus.socket.sendAsync({
537 | janus: "hangup",
538 | session_id: this.janus.socket.sessionID,
539 | handle_id: this.handleID,
540 | ...additionalConfig,
541 | });
542 |
543 | if (hangupResponse.janus === "success") {
544 | }
545 |
546 | const detachResponse = await this.detach();
547 |
548 | if (detachResponse.janus === "success") {
549 | }
550 |
551 | this.pc.close();
552 | this.janus.socket.detachPlugin(this);
553 |
554 | console.error("detach", "hangupResponse", hangupResponse);
555 | console.error("detach", "detachResponse", detachResponse);
556 | } catch (e) {
557 | console.error("detach", e);
558 | }
559 | };
560 |
561 | create = ({}) => {};
562 |
563 | exists = () => {};
564 |
565 | destroy = () => {};
566 |
567 | listAll = () => {};
568 |
569 | listParticipants = () => {};
570 |
571 | update = () => {};
572 | }
573 |
--------------------------------------------------------------------------------