Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use the shared sfu call object in react native #60

Merged
merged 41 commits into from
Nov 11, 2022
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
160e210
feat: make sfu call object to accept rn webrtc
santhoshvai Nov 3, 2022
3b85d50
Merge branch 'main' into feat/rn-use-shared-sfu-client
santhoshvai Nov 7, 2022
2561e76
patch rtcrtpsender and rtcrtpreceiver and add polyfill for uuid
santhoshvai Nov 7, 2022
79317be
chore: fix issues after merge with main
santhoshvai Nov 7, 2022
efcaa45
fix: remove numeric symbols as its not compatible with metro for reac…
santhoshvai Nov 7, 2022
ee051b9
feat: add healper to get username fragment in rn webrtc
santhoshvai Nov 7, 2022
d9c2ad1
feat: add callID randomiser to RN
santhoshvai Nov 7, 2022
9244b18
feat: add callID randomiser to RN
santhoshvai Nov 7, 2022
5a82877
fix: typing issues with stream video client
santhoshvai Nov 7, 2022
c3b5621
chore(react-native): remove unused modules
santhoshvai Nov 7, 2022
3256af3
fix: remove numeric symbols as its not compatible with metro for reac…
santhoshvai Nov 7, 2022
981e5d7
chore(react-native): remove unused modules
santhoshvai Nov 7, 2022
a21ae16
fix: remove numeric symbols as its not compatible with metro for reac…
santhoshvai Nov 7, 2022
6b8315c
chore: remove usage of webrtc/types
santhoshvai Nov 7, 2022
41fb68b
chore: remove the previously made generics for connection config and …
santhoshvai Nov 7, 2022
ece9508
feat: add polyfill of rn-webrtc
santhoshvai Nov 8, 2022
2312446
feat: wip use participant videos from the shared state store
santhoshvai Nov 8, 2022
220083b
feat: add timeout in measureResourceLoadLatencyTo
santhoshvai Nov 8, 2022
5cd8b0e
chore: remove the temporary method that was used by react native
santhoshvai Nov 8, 2022
43b3e72
chore: remove unnecessary console log
santhoshvai Nov 8, 2022
b08b60b
fix: floating point numbers are not allowed in update subscriptions
santhoshvai Nov 8, 2022
ebbe9cf
chore: remove unnecessary console log
santhoshvai Nov 8, 2022
0134532
feat: filter out current user participant
santhoshvai Nov 8, 2022
2b6bb09
fix: remove linking event listener
santhoshvai Nov 8, 2022
20ce55a
chore: remove unused imports
santhoshvai Nov 8, 2022
8b029f8
fix: patch crash in rn webrtc for undefined method on track
santhoshvai Nov 9, 2022
787bfa6
chore: revert to using the staging urls by default
santhoshvai Nov 9, 2022
572d81f
chore: update yarn lock for webrtc patch
santhoshvai Nov 9, 2022
4f8d08a
chore: update the rn webrtc patch
santhoshvai Nov 9, 2022
6f9088e
Merge branch 'main' into feat/rn-use-shared-sfu-client
santhoshvai Nov 9, 2022
2efa258
chore: remove the generics implementation in react-sdk
santhoshvai Nov 9, 2022
a5716eb
chore: remove the generics implementation in react dogfood
santhoshvai Nov 9, 2022
3af2dd9
fix: latency call must get the blob of response
santhoshvai Nov 9, 2022
c526032
chore: revert the changes done to sfu call object
santhoshvai Nov 9, 2022
eaf4fef
Merge branch 'main' into feat/rn-use-shared-sfu-client
santhoshvai Nov 9, 2022
c70df17
feat: patch ice candidate getter for react-native
santhoshvai Nov 9, 2022
4ebc5ce
fix: if only local participant is present then show it as full view
santhoshvai Nov 9, 2022
c7dfab6
chore: align coordinator ws url
santhoshvai Nov 9, 2022
7214b49
chore: use same length to get random call id as react dogfood app
santhoshvai Nov 9, 2022
ed43ef7
fix: adding angular-sdk dir to metro.config blockList
vanGalilea Nov 10, 2022
bef7351
Merge branch 'main' into feat/rn-use-shared-sfu-client
santhoshvai Nov 10, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
diff --git a/src/RTCPeerConnection.ts b/src/RTCPeerConnection.ts
index 20de0069ce6137c1a10f65c2164084cf124c7ffe..46d89f399f65af135789b23ecd51258ce9df94f7 100644
--- a/src/RTCPeerConnection.ts
+++ b/src/RTCPeerConnection.ts
@@ -497,6 +497,7 @@ export default class RTCPeerConnection extends defineCustomEventTarget(...PEER_C

if (oldTransceiver) {
transceiver = oldTransceiver;
+ transceiver._receiver._track = new MediaStreamTrack(transceiver._receiver._track);
track = transceiver._receiver._track;
transceiver._mid = ev.transceiver.mid;
transceiver._currentDirection = ev.transceiver.currentDirection;
Comment on lines +1 to +12
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's create a PR in the RN webrtc lib.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
},
"resolutions": {
"react-native-pure-jwt@^3.0.1": "patch:react-native-pure-jwt@npm%3A3.0.1#./.yarn/patches/react-native-pure-jwt-npm-3.0.1-3e5e2c9059.patch",
"react-native-callkeep@^4.3.3": "patch:react-native-callkeep@npm%3A4.3.3#./.yarn/patches/react-native-callkeep-npm-4.3.3-71db80f4ef.patch"
"react-native-callkeep@^4.3.3": "patch:react-native-callkeep@npm%3A4.3.3#./.yarn/patches/react-native-callkeep-npm-4.3.3-71db80f4ef.patch",
"react-native-webrtc@106.0.0-beta.6": "patch:react-native-webrtc@npm%3A106.0.0-beta.6#./.yarn/patches/react-native-webrtc-npm-106.0.0-beta.6-795b82ce91.patch"
}
}
4 changes: 3 additions & 1 deletion packages/client/src/StreamVideoClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ describe('StreamVideoClient', () => {
createSocketConnection: vi.fn(),
};
});
client = new StreamVideoClient('123', { token: 'abc' });
client = new StreamVideoClient('123', {
token: 'abc',
});
});

it('should connect', async () => {
Expand Down
16 changes: 0 additions & 16 deletions packages/client/src/StreamVideoClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,22 +128,6 @@ export class StreamVideoClient {
return callEnvelope;
};

// TODO: remove this method (it's only used in react-native for now until the sfu-Call object is merged)
joinCallRaw = async (data: JoinCallRequest, sessionId?: string) => {
const { response } = await this.client.joinCall({
...data,
// FIXME: OL this needs to come from somewhere
datacenterId: 'amsterdam',
});
if (response.call && response.call.call && response.edges) {
const edge = await this.getCallEdgeServer(
response.call.call,
response.edges,
);
return { response, edge };
}
};

joinCall = async (data: JoinCallRequest, sessionId?: string) => {
const { response } = await this.client.joinCall({
...data,
Expand Down
11 changes: 7 additions & 4 deletions packages/client/src/rpc/latency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ export const measureResourceLoadLatencyTo = async (
.fill(undefined)
.map(async () => {
const start = Date.now();
const src = new URL(endpoint);
src.searchParams.set('rand', `react_${Math.random() * 10000000}`);
santhoshvai marked this conversation as resolved.
Show resolved Hide resolved
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 3000); // 3 second timeout:
try {
const src = new URL(endpoint);
src.searchParams.set('rand', `react_${Math.random() * 10000000}`);
await fetch(src.toString()).then((response) => response.blob());
await fetch(src.toString(), { signal: controller.signal });
santhoshvai marked this conversation as resolved.
Show resolved Hide resolved
} catch (e) {
console.warn(`failed to measure latency to ${endpoint}`, e);
console.warn(`failed to measure latency to ${src}`, e);
}
clearTimeout(timeoutId); // clear timeout incase fetch completes before timeout
const latency = Date.now() - start;
measurements.push(toSeconds(latency));
}),
Expand Down
156 changes: 148 additions & 8 deletions packages/client/src/rtc/Call.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createSubscriber } from './subscriber';
import { addSubscriberListeners } from './subscriber';
import {
defaultVideoLayers,
findOptimalVideoLayers,
Expand All @@ -11,7 +11,7 @@ import {
getReceiverCodecs,
getSenderCodecs,
} from './codecs';
import { createPublisher } from './publisher';
import { addPublisherListeners } from './publisher';
import { CallState, VideoDimension } from '../gen/video/sfu/models/models';
import { registerEventHandlers } from './callEventHandlers';
import { SfuRequest } from '../gen/video/sfu/event/events';
Expand All @@ -24,6 +24,8 @@ export type CallOptions = {
connectionConfig: RTCConfiguration | undefined;
};

type MediaDeviceKind = 'audioinput' | 'audiooutput' | 'videoinput';

export class Call {
/**@deprecated use store for this data */
currentUserId: string;
Expand All @@ -46,19 +48,19 @@ export class Call {
stateStore.connectedUserSubject,
)!.name;
const { dispatcher, iceTrickleBuffer } = this.client;
this.subscriber = createSubscriber({
this.subscriber = new RTCPeerConnection(this.options.connectionConfig);
santhoshvai marked this conversation as resolved.
Show resolved Hide resolved
addSubscriberListeners({
rpcClient: this.client,

// FIXME: don't do this
dispatcher: dispatcher,
connectionConfig: this.options.connectionConfig,
subscriber: this.subscriber,
onTrack: this.handleOnTrack,
candidates: iceTrickleBuffer.subscriberCandidates,
});

this.publisher = createPublisher({
this.publisher = new RTCPeerConnection(this.options.connectionConfig);
addPublisherListeners({
rpcClient: this.client,
connectionConfig: this.options.connectionConfig,
publisher: this.publisher,
candidates: iceTrickleBuffer.publisherCandidates,
});

Expand Down Expand Up @@ -100,6 +102,83 @@ export class Call {
this.stateStore.activeCallSubject.next(undefined);
};

// FIXME: remove this, it is temporary for RN implementation
joinWithCombinedStream = async (stream?: MediaStream) => {
santhoshvai marked this conversation as resolved.
Show resolved Hide resolved
await this.client.signalReady;

if (this.joinResponseReady) {
throw new Error(`Illegal State: Already joined.`);
}
this.joinResponseReady = new Promise<CallState | undefined>(
async (resolve) => {
const [audioEncode, audioDecode, videoEncode, videoDecode] =
await Promise.all([
getSenderCodecs('audio'),
getReceiverCodecs('audio', this.subscriber),
getSenderCodecs('video'),
getReceiverCodecs('video', this.subscriber),
]);

this.videoLayers = stream
? await findOptimalVideoLayers(stream)
: defaultVideoLayers;

this.client.dispatcher.on('joinResponse', (event) => {
if (event.eventPayload.oneofKind !== 'joinResponse') return;
const callState = event.eventPayload.joinResponse.callState;
this.stateStore.activeCallSubject.next(this);
this.participants.push(...(callState?.participants || []));
const ownParticipant = this.localParticipant;
if (ownParticipant) {
ownParticipant.isLoggedInUser = true;
ownParticipant.audioTrack = stream;
ownParticipant.videoTrack = stream;
}
this.stateStore.activeCallParticipantsSubject.next([
...this.participants,
]);
this.client.keepAlive();
resolve(callState); // expose call state
});

this.client.send(
SfuRequest.create({
requestPayload: {
oneofKind: 'joinRequest',
joinRequest: {
sessionId: this.client.sessionId,
token: this.client.token,
publish: true,
// FIXME OL: encode parameters and video layers
// should be announced when initiating "publish" operation
codecSettings: {
audio: {
encodes: audioEncode,
decodes: audioDecode,
},
video: {
encodes: videoEncode,
decodes: videoDecode,
},
layers: this.videoLayers.map((layer) => ({
rid: layer.rid!,
bitrate: layer.maxBitrate!,
videoDimension: {
width: layer.width,
height: layer.height,
},
})),
},
},
},
}),
);
},
);

return this.joinResponseReady;
};

join = async (videoStream?: MediaStream, audioStream?: MediaStream) => {
await this.client.signalReady;

Expand Down Expand Up @@ -235,6 +314,65 @@ export class Call {
}
};

// FIXME: remove this, it is temporary for RN implementation
publishWithCombinedStream = async (stream?: MediaStream) => {
santhoshvai marked this conversation as resolved.
Show resolved Hide resolved
if (!this.joinResponseReady) {
throw new Error(
`Illegal State: Can't publish. Please join the call first`,
);
}

// wait until we get a JoinResponse from the SFU, otherwise we risk
// breaking the ICETrickle flow.
await this.joinResponseReady;

if (stream) {
const videoEncodings: RTCRtpEncodingParameters[] =
this.videoLayers && this.videoLayers.length > 0
? this.videoLayers
: defaultVideoPublishEncodings;

const [videoTrack] = stream.getVideoTracks();
if (videoTrack) {
const videoTransceiver = this.publisher?.addTransceiver(videoTrack, {
direction: 'sendonly',
streams: [stream],
sendEncodings: videoEncodings,
});

const codecPreferences = getPreferredCodecs('video', 'vp8');
// @ts-ignore
if ('setCodecPreferences' in videoTransceiver && codecPreferences) {
console.log(`set codec preferences`, codecPreferences);
videoTransceiver.setCodecPreferences(codecPreferences);
}
}

if (this.localParticipant) {
this.localParticipant.videoTrack = stream;
this.stateStore.activeCallParticipantsSubject.next([
...this.participants,
]);
}
}

if (stream) {
const [audioTrack] = stream.getAudioTracks();
if (audioTrack) {
this.publisher?.addTransceiver(audioTrack, {
direction: 'sendonly',
});
}

if (this.localParticipant) {
this.localParticipant.audioTrack = stream;
this.stateStore.activeCallParticipantsSubject.next([
...this.participants,
]);
}
}
};

/**
* Update track subscription configuration for one or more participants. You have to create a subscription for each participant you want to receive any kind of track.
* @param changes
Expand Down Expand Up @@ -372,6 +510,7 @@ export class Call {

const params = await videoSender.getParameters();
let changed = false;
// @ts-ignore
params.encodings.forEach((enc) => {
console.log(enc.rid, enc.active);
// flip 'active' flag only when necessary
Expand All @@ -382,6 +521,7 @@ export class Call {
}
});
if (changed) {
// @ts-ignore
if (params.encodings.length === 0) {
console.warn('No suitable video encoding quality found');
}
Expand Down
6 changes: 3 additions & 3 deletions packages/client/src/rtc/codecs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ export const defaultVideoPublishEncodings: RTCRtpEncodingParameters[] = [
{
rid: 'f',
active: true,
maxBitrate: 1_280_000,
maxBitrate: 1280000,
},
{
rid: 'h',
active: true,
scaleResolutionDownBy: 2.0,
maxBitrate: 768_000,
maxBitrate: 768000,
},
{
rid: 'q',
active: true,
scaleResolutionDownBy: 4.0,
maxBitrate: 384_000,
maxBitrate: 384000,
},
];

Expand Down
16 changes: 16 additions & 0 deletions packages/client/src/rtc/helpers/iceCandidate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ICETrickle } from '../../gen/video/sfu/models/models';

export function getIceCandidate(
candidate: RTCIceCandidate,
): ICETrickle['iceCandidate'] {
if (!candidate.usernameFragment) {
// react-native-webrtc doesn't include usernameFragment in the candidate
const splittedCandidate = candidate.candidate.split(' ');
const ufragIndex =
splittedCandidate.findIndex((s: string) => s === 'ufrag') + 1;
const usernameFragment = splittedCandidate[ufragIndex];
return JSON.stringify({ ...candidate, usernameFragment });
} else {
return JSON.stringify(candidate.toJSON());
}
}
18 changes: 9 additions & 9 deletions packages/client/src/rtc/publisher.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import { StreamSfuClient } from '../StreamSfuClient';
import { ICETrickle, PeerType } from '../gen/video/sfu/models/models';
import { ReplaySubject } from 'rxjs';
import { getIceCandidate } from './helpers/iceCandidate';

export type PublisherOpts = {
export type PublisherOpts<RTCPeerConnectionType extends RTCPeerConnection> = {
rpcClient: StreamSfuClient;
connectionConfig?: RTCConfiguration;
publisher: RTCPeerConnectionType;
candidates: ReplaySubject<ICETrickle>;
};

export const createPublisher = ({
connectionConfig,
export const addPublisherListeners = <
RTCPeerConnectionType extends RTCPeerConnection,
>({
publisher,
rpcClient,
candidates,
}: PublisherOpts) => {
const publisher = new RTCPeerConnection(connectionConfig);
}: PublisherOpts<RTCPeerConnectionType>) => {
publisher.addEventListener('icecandidate', async (e) => {
const { candidate } = e;
if (!candidate) {
Expand All @@ -22,7 +24,7 @@ export const createPublisher = ({
}
await rpcClient.rpc.iceTrickle({
sessionId: rpcClient.sessionId,
iceCandidate: JSON.stringify(candidate.toJSON()),
iceCandidate: getIceCandidate(candidate),
peerType: PeerType.PUBLISHER_UNSPECIFIED,
});
});
Expand Down Expand Up @@ -69,6 +71,4 @@ export const createPublisher = ({
}
});
});

return publisher;
};
Loading