Skip to content
This repository has been archived by the owner on Sep 2, 2024. It is now read-only.

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
saghul committed Jul 17, 2019
1 parent 762a7e2 commit a7bc109
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 243 deletions.
7 changes: 3 additions & 4 deletions RTCPeerConnection.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ const DEFAULT_SDP_CONSTRAINTS = {
mandatory: {
OfferToReceiveAudio: true,
OfferToReceiveVideo: true,
},
optional: [],
}
};

const PEER_CONNECTION_EVENTS = [
Expand Down Expand Up @@ -121,7 +120,7 @@ export default class RTCPeerConnection extends EventTarget(PEER_CONNECTION_EVENT
return new Promise((resolve, reject) => {
WebRTCModule.peerConnectionCreateOffer(
this._peerConnectionId,
RTCUtil.mergeMediaConstraints(options, DEFAULT_SDP_CONSTRAINTS),
RTCUtil.mergeOfferAnswerOptions(options, DEFAULT_SDP_CONSTRAINTS),
(successful, data) => {
if (successful) {
resolve(new RTCSessionDescription(data));
Expand All @@ -136,7 +135,7 @@ export default class RTCPeerConnection extends EventTarget(PEER_CONNECTION_EVENT
return new Promise((resolve, reject) => {
WebRTCModule.peerConnectionCreateAnswer(
this._peerConnectionId,
RTCUtil.mergeMediaConstraints(options, DEFAULT_SDP_CONSTRAINTS),
RTCUtil.mergeOfferAnswerOptions(options, DEFAULT_SDP_CONSTRAINTS),
(successful, data) => {
if (successful) {
resolve(new RTCSessionDescription(data));
Expand Down
167 changes: 149 additions & 18 deletions RTCUtil.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,166 @@
'use strict';

const DEFAULT_AUDIO_CONSTRAINTS = {};

const DEFAULT_VIDEO_CONSTRAINTS = {
facingMode: 'user',
frameRate: 30,
height: 720,
width: 1280
};

const ASPECT_RATIO = 16 / 9;


function getDefaultMediaConstraints(mediaType) {
switch(mediaType) {
case 'audio':
return DEFAULT_AUDIO_CONSTRAINTS;
case 'video':
return DEFAULT_VIDEO_CONSTRAINTS;
default:
throw new TypeError(`Invalid media type: ${mediaType}`);
}
}

function extractString(constraints, prop) {
const value = constraints[prop];
const type = typeof value;

if (type === 'object') {
for (const v of [ 'exact', 'ideal' ]) {
if (value[v]) {
return value[v];
}
}
} else if (type === 'string') {
return value;
}
}

function extractNumber(constraints, prop) {
const value = constraints[prop];
const type = typeof value;

if (type === 'number') {
return Number.parseInt(value);
} else if (type === 'object') {
for (const v of [ 'exact', 'ideal', 'min ']) {
if (value[v]) {
return Number.parseInt(value[v]);
}
}
}
}

function normalizeMediaConstraints(constraints, mediaType) {
switch(mediaType) {
case 'audio':
return constraints;
case 'video': {
let c;
if (constraints.mandatory) {
// Old style.
c = {
deviceId: extractString(constraints.optional || {}, 'sourceId'),
facingMode: extractString(constraints, 'facingMode'),
frameRate: extractNumber(constraints.mandatory, 'minFrameRate'),
height: extractNumber(constraints.mandatory, 'minHeight'),
width: extractNumber(constraints.mandatory, 'minWidth')
};
} else {
// New style.
c = {
deviceId: extractString(constraints, 'deviceId'),
facingMode: extractString(constraints, 'facingMode'),
frameRate: extractNumber(constraints, 'frameRate'),
height: extractNumber(constraints, 'height'),
width: extractNumber(constraints, 'width')
};
}

if (!c.deviceId) {
delete c.deviceId;
}

if (!c.facingMode || (c.facingMode !== 'user' && c.facingMode !== 'environment')) {
c.facingMode = DEFAULT_VIDEO_CONSTRAINTS.facingMode;
}

if (!c.frameRate) {
c.frameRate = DEFAULT_VIDEO_CONSTRAINTS.frameRate;
}

if (!c.height && !c.width) {
c.height = DEFAULT_VIDEO_CONSTRAINTS.height;
c.width = DEFAULT_VIDEO_CONSTRAINTS.width;
} else if (!c.height) {
c.height = Math.round(c.width / ASPECT_RATIO);
} else if (!c.width) {
c.width = Math.round(c.height * ASPECT_RATIO);
}

return c;
}
default:
throw new TypeError(`Invalid media type: ${mediaType}`);
}
}

/**
* Internal util for deep clone object. Object.assign() only does a shallow copy
*
* @param {Object} obj - object to be cloned
* @return {Object} cloned obj
*/
function _deepClone(obj) {
return JSON.parse(JSON.stringify(obj));
return JSON.parse(JSON.stringify(obj));
}

/**
* Merge custom constraints with the default one. The custom one take precedence.
* Merge Offer-Answer options.
*
* @param {Object} custom - custom webrtc constraints
* @param {Object} def - default webrtc constraints
* @return {Object} constraints - merged webrtc constraints
* @param {Object} custom - custom options
* @param {Object} def - default options
* @return {Object} options - merged options
*/
export function mergeMediaConstraints(custom, def) {
const constraints = (def ? _deepClone(def) : {});
if (custom) {
if (custom.mandatory) {
constraints.mandatory = {...constraints.mandatory, ...custom.mandatory};
}
if (custom.optional && Array.isArray(custom.optional)) {
// `optional` is an array, webrtc only finds first and ignore the rest if duplicate.
constraints.optional = custom.optional.concat(constraints.optional);
export function mergeOfferAnswerOptions(custom, def) {
const constraints = (def ? _deepClone(def) : {});

if (custom) {
if (custom.mandatory) {
constraints.mandatory = {...constraints.mandatory, ...custom.mandatory};
}

// `optional` is not really used on the native side.
// https://github.com/jitsi/webrtc/blob/0cd6ce4de669bed94ba47b88cb71b9be0341bb81/sdk/media_constraints.cc#L227
}
if (custom.facingMode) {
constraints.facingMode = custom.facingMode.toString(); // string, 'user' or the default 'environment'

return constraints;
}

/**
* Normalize the given constraints in something we can work with.
*/
export function normalizeConstraints(constraints) {
const c = _deepClone(constraints);

for (const mediaType of [ 'audio', 'video' ]) {
const mediaTypeConstraints = c[mediaType];
const typeofMediaTypeConstraints = typeof mediaTypeConstraints;

if (typeofMediaTypeConstraints !== 'undefined') {
if (typeofMediaTypeConstraints === 'boolean') {
if (mediaTypeConstraints) {
c[mediaType] = getDefaultMediaConstraints(mediaType);
}
} else if (typeofMediaTypeConstraints === 'object') {
c[mediaType] = normalizeMediaConstraints(mediaTypeConstraints, mediaType);
} else {
throw new TypeError(`constraints.${mediaType} is neither a boolean nor a dictionary`);
}
}
}
}
return constraints;

return c;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

import android.util.Log;

import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableType;

import org.webrtc.CameraEnumerator;
import org.webrtc.CameraVideoCapturer;
Expand All @@ -20,23 +18,15 @@ public class VideoCaptureController {
private static final String TAG
= VideoCaptureController.class.getSimpleName();

/**
* Default values for width, height and fps (respectively) which will be
* used to open the camera at.
*/
private static final int DEFAULT_WIDTH = 1280;
private static final int DEFAULT_HEIGHT = 720;
private static final int DEFAULT_FPS = 30;

private boolean isFrontFacing;

/**
* Values for width, height and fps (respectively) which will be
* used to open the camera at.
*/
private int width = DEFAULT_WIDTH;
private int height = DEFAULT_HEIGHT;
private int fps = DEFAULT_FPS;
private final int width;
private final int height;
private final int fps;

private CameraEnumerator cameraEnumerator;

Expand All @@ -54,33 +44,17 @@ public class VideoCaptureController {
*/
private VideoCapturer videoCapturer;

public VideoCaptureController(CameraEnumerator cameraEnumerator,
ReadableMap constraints) {
public VideoCaptureController(CameraEnumerator cameraEnumerator, ReadableMap constraints) {
this.cameraEnumerator = cameraEnumerator;

ReadableMap videoConstraintsMandatory = null;
width = constraints.getInt("width");
height = constraints.getInt("height");
fps = constraints.getInt("frameRate");

if (constraints.hasKey("mandatory")
&& constraints.getType("mandatory") == ReadableType.Map) {
videoConstraintsMandatory = constraints.getMap("mandatory");
}
String deviceId = ReactBridgeUtil.getMapStrValue(constraints, "deviceId");
String facingMode = ReactBridgeUtil.getMapStrValue(constraints, "facingMode");

String sourceId = getSourceIdConstraint(constraints);
String facingMode = getFacingMode(constraints);

videoCapturer = createVideoCapturer(sourceId, facingMode);

if (videoConstraintsMandatory != null) {
width = videoConstraintsMandatory.hasKey("minWidth")
? videoConstraintsMandatory.getInt("minWidth")
: DEFAULT_WIDTH;
height = videoConstraintsMandatory.hasKey("minHeight")
? videoConstraintsMandatory.getInt("minHeight")
: DEFAULT_HEIGHT;
fps = videoConstraintsMandatory.hasKey("minFrameRate")
? videoConstraintsMandatory.getInt("minFrameRate")
: DEFAULT_FPS;
}
videoCapturer = createVideoCapturer(deviceId, facingMode);
}

public void dispose() {
Expand Down Expand Up @@ -179,23 +153,23 @@ public void onCameraSwitchError(String s) {
* Constructs a new {@code VideoCapturer} instance attempting to satisfy
* specific constraints.
*
* @param sourceId the ID of the requested video source. If not
* @param deviceId the ID of the requested video device. If not
* {@code null} and a {@code VideoCapturer} can be created for it, then
* {@code facingMode} is ignored.
* @param facingMode the facing of the requested video source such as
* {@code user} and {@code environment}. If {@code null}, "user" is
* presumed.
* @return a {@code VideoCapturer} satisfying the {@code facingMode} or
* {@code sourceId} constraint
* {@code deviceId} constraint
*/
private VideoCapturer createVideoCapturer(String sourceId, String facingMode) {
private VideoCapturer createVideoCapturer(String deviceId, String facingMode) {
String[] deviceNames = cameraEnumerator.getDeviceNames();
List<String> failedDevices = new ArrayList<>();

// If sourceId is specified, then it takes precedence over facingMode.
if (sourceId != null) {
// If deviceId is specified, then it takes precedence over facingMode.
if (deviceId != null) {
for (String name : deviceNames) {
if (name.equals(sourceId)) {
if (name.equals(deviceId)) {
VideoCapturer videoCapturer
= cameraEnumerator.createCapturer(name, cameraEventsHandler);
String message = "Create user-specified camera " + name;
Expand Down Expand Up @@ -267,49 +241,4 @@ private VideoCapturer createVideoCapturer(String sourceId, String facingMode) {

return null;
}

/**
* Retrieves "facingMode" constraint value.
*
* @param mediaConstraints a {@code ReadableMap} which represents "GUM"
* constraints argument.
* @return String value of "facingMode" constraints in "GUM" or
* {@code null} if not specified.
*/
private String getFacingMode(ReadableMap mediaConstraints) {
return
mediaConstraints == null
? null
: ReactBridgeUtil.getMapStrValue(mediaConstraints, "facingMode");
}

/**
* Retrieves "sourceId" constraint value.
*
* @param mediaConstraints a {@code ReadableMap} which represents "GUM"
* constraints argument
* @return String value of "sourceId" optional "GUM" constraint or
* {@code null} if not specified.
*/
private String getSourceIdConstraint(ReadableMap mediaConstraints) {
if (mediaConstraints != null
&& mediaConstraints.hasKey("optional")
&& mediaConstraints.getType("optional") == ReadableType.Array) {
ReadableArray optional = mediaConstraints.getArray("optional");

for (int i = 0, size = optional.size(); i < size; i++) {
if (optional.getType(i) == ReadableType.Map) {
ReadableMap option = optional.getMap(i);

if (option.hasKey("sourceId")
&& option.getType("sourceId")
== ReadableType.String) {
return option.getString("sourceId");
}
}
}
}

return null;
}
}
15 changes: 0 additions & 15 deletions android/src/main/java/com/oney/WebRTCModule/WebRTCModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -505,21 +505,6 @@ MediaConstraints parseMediaConstraints(ReadableMap constraints) {
Log.d(TAG, "mandatory constraints are not a map");
}

if (constraints.hasKey("optional")
&& constraints.getType("optional") == ReadableType.Array) {
ReadableArray optional = constraints.getArray("optional");

for (int i = 0, size = optional.size(); i < size; i++) {
if (optional.getType(i) == ReadableType.Map) {
parseConstraints(
optional.getMap(i),
mediaConstraints.optional);
}
}
} else {
Log.d(TAG, "optional constraints are not an array");
}

return mediaConstraints;
}

Expand Down
Loading

0 comments on commit a7bc109

Please sign in to comment.