forked from react-native-webrtc/react-native-webrtc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
getUserMedia.js
181 lines (159 loc) · 6.67 KB
/
getUserMedia.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
'use strict';
import {Platform, NativeModules} from 'react-native';
import * as RTCUtil from './RTCUtil';
import MediaStream from './MediaStream';
import MediaStreamError from './MediaStreamError';
import MediaStreamTrack from './MediaStreamTrack';
import permissions from './Permissions';
const {WebRTCModule} = NativeModules;
// native side consume string eventually
const DEFAULT_VIDEO_CONSTRAINTS = {
mandatory: {
minWidth: '1280',
minHeight: '720',
minFrameRate: '30',
},
facingMode: "environment",
optional: [],
};
function getDefaultMediaConstraints(mediaType) {
return (mediaType === 'audio'
? {} // no audio default constraint currently
: RTCUtil.mergeMediaConstraints(DEFAULT_VIDEO_CONSTRAINTS));
}
// this will make sure we have the correct constraint structure
// TODO: support width/height range and the latest param names according to spec
// media constraints param name should follow spec. then we need a function to convert these `js names`
// into the real `const name that native defined` on both iOS and Android.
// see mediaTrackConstraints: https://www.w3.org/TR/mediacapture-streams/#dom-mediatrackconstraints
function parseMediaConstraints(customConstraints, mediaType) {
return (mediaType === 'audio'
? RTCUtil.mergeMediaConstraints(customConstraints) // no audio default constraint currently
: RTCUtil.mergeMediaConstraints(customConstraints, DEFAULT_VIDEO_CONSTRAINTS));
}
// this will make sure we have the correct value type
function normalizeMediaConstraints(constraints, mediaType) {
if (mediaType === 'audio') {
; // to be added
} else {
// NOTE: android only support minXXX currently
for (const param of ['minWidth', 'minHeight', 'minFrameRate', 'maxWidth', 'maxHeight', 'maxFrameRate', ]) {
if (constraints.mandatory.hasOwnProperty(param)) {
// convert to correct type here so that native can consume directly without worries.
constraints.mandatory[param] = (Platform.OS === 'ios'
? constraints.mandatory[param].toString() // ios consumes string
: parseInt(constraints.mandatory[param])); // android eats integer
}
}
}
return constraints;
}
export default function getUserMedia(constraints = {}) {
// According to
// https://www.w3.org/TR/mediacapture-streams/#dom-mediadevices-getusermedia,
// the constraints argument is a dictionary of type MediaStreamConstraints.
if (typeof constraints !== 'object') {
return Promise.reject(new TypeError('constraints is not a dictionary'));
}
if ((typeof constraints.audio === 'undefined' || !constraints.audio)
&& (typeof constraints.video === 'undefined' || !constraints.video)) {
return Promise.reject(new TypeError('audio and/or video is required'));
}
// Deep clone constraints.
constraints = JSON.parse(JSON.stringify(constraints));
// According to step 2 of the getUserMedia() algorithm, requestedMediaTypes
// is the set of media types in constraints with either a dictionary value
// or a value of "true".
for (const mediaType of [ 'audio', 'video' ]) {
// According to the spec, the types of the audio and video members of
// MediaStreamConstraints are either boolean or MediaTrackConstraints
// (i.e. dictionary).
const mediaTypeConstraints = constraints[mediaType];
const typeofMediaTypeConstraints = typeof mediaTypeConstraints;
if (typeofMediaTypeConstraints !== 'undefined') {
if (typeofMediaTypeConstraints === 'boolean') {
if (mediaTypeConstraints) {
constraints[mediaType] = getDefaultMediaConstraints(mediaType);
}
} else if (typeofMediaTypeConstraints === 'object') {
// Note: object constraints for audio is not implemented in native side
constraints[mediaType] = parseMediaConstraints(mediaTypeConstraints, mediaType);
} else {
return Promise.reject(
new TypeError('constraints.' + mediaType + ' is neither a boolean nor a dictionary'));
}
// final check constraints and convert value to native accepted type
if (typeof constraints[mediaType] === 'object') {
constraints[mediaType] = normalizeMediaConstraints(constraints[mediaType], mediaType);
}
}
}
// Request required permissions
let reqPermissions = [];
if (constraints.audio) {
reqPermissions.push(permissions.request({ name: 'microphone' }));
} else {
reqPermissions.push(Promise.resolve(false));
}
if (constraints.video) {
reqPermissions.push(permissions.request({ name: 'camera' }));
} else {
reqPermissions.push(Promise.resolve(false));
}
return new Promise((resolve, reject) => {
Promise.all(reqPermissions).then(results => {
const [ audioPerm, videoPerm ] = results;
// Check permission results and remove unneeded permissions.
if (!audioPerm && !videoPerm) {
// https://www.w3.org/TR/mediacapture-streams/#dom-mediadevices-getusermedia
// step 4
const error = {
message: 'Permission denied.',
name: 'SecurityError'
};
reject(new MediaStreamError(error));
return;
}
audioPerm || (delete constraints.audio);
videoPerm || (delete constraints.video);
WebRTCModule.getUserMedia(
constraints,
/* successCallback */ (id, tracks) => {
const stream = new MediaStream(id);
for (const track of tracks) {
stream.addTrack(new MediaStreamTrack(track));
}
resolve(stream);
},
/* errorCallback */ (type, message) => {
let error;
switch (type) {
case 'DOMException':
// According to
// https://www.w3.org/TR/mediacapture-streams/#idl-def-MediaStreamError,
// MediaStreamError is either a DOMException object or an
// OverconstrainedError object. We are very likely to not have a
// definition of DOMException on React Native (unless the client has
// provided such a definition). If necessary, we will fall back to our
// definition of MediaStreamError.
if (typeof DOMException === 'function') {
error = new DOMException(/* message */ undefined, /* name */ message);
}
break;
case 'OverconstrainedError':
if (typeof OverconstrainedError === 'function') {
error = new OverconstrainedError(/* constraint */ undefined, message);
}
break;
case 'TypeError':
error = new TypeError(message);
break;
}
if (!error) {
error = new MediaStreamError({ message, name: type });
}
reject(error);
});
});
});
}