Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit d2fe695

Browse files
authoredAug 19, 2021
Video 6352 quality pane (#10)
1 parent 7d2778a commit d2fe695

File tree

16 files changed

+389
-45
lines changed

16 files changed

+389
-45
lines changed
 

‎serverless/__tests__/e2e/e2e.ts

+21-1
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,29 @@ describe('the serverless endpoints', () => {
4141

4242
describe('the token function', () => {
4343
it('should return a valid access token', async () => {
44-
const { body } = await superagent.get(`${appURL}/token`);
44+
const { body } = await superagent.get(`${appURL}/app/token`);
4545
const token = jwt.decode(body.token) as { [key: string]: any };
4646
expect(token.grants.identity).toEqual(constants.VIDEO_IDENTITY);
4747
});
4848
});
49+
50+
describe('the turn-credentials function', () => {
51+
it('should return turn credentials', async () => {
52+
const { body } = await superagent.get(`${appURL}/app/turn-credentials`);
53+
expect(body).toEqual(
54+
expect.objectContaining({
55+
password: '[Redacted]',
56+
ttl: '30',
57+
username: expect.any(String),
58+
accountSid: expect.any(String),
59+
iceServers: expect.arrayContaining([
60+
{
61+
url: expect.any(String),
62+
urls: expect.any(String),
63+
},
64+
]),
65+
})
66+
);
67+
});
68+
});
4969
});

‎serverless/__tests__/functions/token.ts ‎serverless/__tests__/functions/app/token.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { handler } from '../../functions/token';
1+
import { handler } from '../../../functions/app/token';
22
import jwt, { VerifyCallback } from 'jsonwebtoken';
33

44
const mockContext = {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { handler } from '../../../functions/app/turn-credentials';
2+
3+
const mockClient = {
4+
tokens: {
5+
create: jest.fn(() => Promise.resolve('mock token')),
6+
},
7+
};
8+
9+
describe('the turn-credentials function', () => {
10+
it('should return turn credentials', (done) => {
11+
const mockCallback = jest.fn();
12+
handler({ getTwilioClient: () => mockClient }, {}, mockCallback);
13+
expect(mockClient.tokens.create).toHaveBeenCalledWith({ ttl: 30 });
14+
setImmediate(() => {
15+
expect(mockCallback).toHaveBeenCalledWith(null, 'mock token');
16+
done();
17+
});
18+
});
19+
});
File renamed without changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
exports.handler = function (context, event, callback) {
2+
const verifyExpiry = require(Runtime.getAssets()['/verify_expiry.js'].path);
3+
verifyExpiry.handler(context, event, callback);
4+
5+
const client = context.getTwilioClient();
6+
client.tokens.create({ ttl: 30 }).then((token) => callback(null, token));
7+
};

‎src/components/AppStateProvider/AppStateProvider.test.tsx

+101-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import Video, { PreflightTestReport } from 'twilio-video';
3-
import { AudioInputTest, AudioOutputTest, VideoInputTest } from '@twilio/rtc-diagnostics';
3+
import { AudioInputTest, AudioOutputTest, VideoInputTest, MediaConnectionBitrateTest } from '@twilio/rtc-diagnostics';
44
import {
55
ActivePane,
66
useAppStateContext,
@@ -11,9 +11,16 @@ import {
1111
} from './AppStateProvider';
1212
import { renderHook, act } from '@testing-library/react-hooks';
1313

14+
const mockStartBitrateTest = jest.fn();
1415
const mockStartPreflightTest = jest.fn();
1516
const mockGetTwilioStatus = jest.fn();
1617

18+
jest.mock('./useBitrateTest/useBitrateTest', () =>
19+
jest.fn(() => ({
20+
startBitrateTest: mockStartBitrateTest,
21+
}))
22+
);
23+
1724
jest.mock('./usePreflightTest/usePreflightTest', () =>
1825
jest.fn(() => ({
1926
startPreflightTest: mockStartPreflightTest,
@@ -34,36 +41,71 @@ describe('the useAppStateContext hook', () => {
3441
});
3542

3643
describe('the isDownButtonDisabled function', () => {
37-
const mockError = Error();
38-
it('should return true if preflightTest fails or if Twilio services are down', () => {
39-
expect(isDownButtonDisabled(false, 'partial_outage', ActivePane.Connectivity, mockError)).toBe(true);
44+
it('should return true if preflightTest fails', () => {
45+
const mockCurrentState = {
46+
...initialState,
47+
activePane: ActivePane.Connectivity,
48+
preflightTestInProgress: false,
49+
preflightTest: { ...initialState.preflightTest, error: Error() },
50+
bitrateTestInProgress: false,
51+
};
52+
expect(isDownButtonDisabled(mockCurrentState)).toBe(true);
4053
});
4154

42-
it('should return true when preflight test is in progress', () => {
43-
expect(isDownButtonDisabled(true, 'operational', ActivePane.Connectivity, null)).toBe(true);
55+
it('should return true when preflight test and/or bitrate test is in progress', () => {
56+
const mockCurrentState = {
57+
...initialState,
58+
activePane: ActivePane.Connectivity,
59+
preflightTestInProgress: true,
60+
bitrateTestInProgress: false,
61+
};
62+
63+
expect(isDownButtonDisabled(mockCurrentState)).toBe(true);
4464
});
4565

4666
it('should return true if the active pane is the last pane', () => {
47-
expect(isDownButtonDisabled(false, 'operational', ActivePane.Results, null)).toBe(true);
67+
const mockCurrentState = {
68+
...initialState,
69+
activePane: ActivePane.Results,
70+
};
71+
expect(isDownButtonDisabled(mockCurrentState)).toBe(true);
4872
});
4973

5074
it('should return true when active pane is DeviceCheck', () => {
51-
expect(isDownButtonDisabled(false, 'operational', ActivePane.DeviceCheck, null)).toBe(true);
75+
const mockCurrentState = {
76+
...initialState,
77+
activePane: ActivePane.DeviceCheck,
78+
};
79+
expect(isDownButtonDisabled(mockCurrentState)).toBe(true);
5280
});
5381

5482
it('should return true when active pane is DeviceError', () => {
55-
expect(isDownButtonDisabled(false, 'operational', ActivePane.DeviceError, null)).toBe(true);
83+
const mockCurrentState = {
84+
...initialState,
85+
activePane: ActivePane.DeviceError,
86+
};
87+
expect(isDownButtonDisabled(mockCurrentState)).toBe(true);
5688
});
5789

5890
it('should return true when active pane is BrowserCheck and the browser is unsupported', () => {
91+
const mockCurrentState = {
92+
...initialState,
93+
activePane: ActivePane.BrowserTest,
94+
};
5995
// @ts-ignore
6096
Video.isSupported = false;
6197

62-
expect(isDownButtonDisabled(false, 'operational', ActivePane.BrowserTest, null)).toBe(true);
98+
expect(isDownButtonDisabled(mockCurrentState)).toBe(true);
6399
});
64100

65-
it('should return false if preflightTest completes and active pane is not in Device setup', () => {
66-
expect(isDownButtonDisabled(false, 'operational', ActivePane.CameraTest, null)).toBe(false);
101+
it('should return false if preflightTest and bitrateTest complete and active pane is not DeviceCheck or DeviceError', () => {
102+
const mockCurrentState = {
103+
...initialState,
104+
activePane: ActivePane.CameraTest,
105+
preflightTestInProgress: false,
106+
bitrateTestInProgress: false,
107+
};
108+
expect(isDownButtonDisabled(mockCurrentState)).toBe(false);
67109
});
68110
});
69111

@@ -304,6 +346,46 @@ describe('the appState reducer', () => {
304346
expect(newState.preflightTestInProgress).toBe(false);
305347
});
306348
});
349+
350+
describe('the "set-bitrate" action type', () => {
351+
it('should set the bitrate for the bitrate test', () => {
352+
const newState = appStateReducer(initialState, { type: 'set-bitrate', bitrate: 100 });
353+
expect(newState.bitrateTest.bitrate).toBe(100);
354+
});
355+
});
356+
357+
describe('the "set-bitrate-test-error" action type', () => {
358+
it('should save the error if an error is thrown during bitrate test', () => {
359+
const mockError = Error();
360+
const newState = appStateReducer(initialState, { type: 'set-bitrate-test-error', error: mockError });
361+
expect(newState.bitrateTest.error).toBe(mockError);
362+
});
363+
});
364+
365+
describe('the "set-bitrate-test-report" action type', () => {
366+
it('should save the report from the bitrate test', () => {
367+
const mockReport = {} as MediaConnectionBitrateTest.Report;
368+
369+
const newState = appStateReducer(initialState, { type: 'set-bitrate-test-report', report: mockReport });
370+
expect(newState.bitrateTest.report).toBe(mockReport);
371+
});
372+
});
373+
374+
describe('the "bitrate-test-started" action type', () => {
375+
it('should set bitrateTestInProgress to true and bitrateTestFinished to false', () => {
376+
const newState = appStateReducer(initialState, { type: 'bitrate-test-started' });
377+
expect(newState.bitrateTestInProgress).toBe(true);
378+
expect(newState.bitrateTestFinished).toBe(false);
379+
});
380+
});
381+
382+
describe('the "bitrate-test-finished" action type', () => {
383+
it('should set bitrateTestInProgress to false and bitrateTestFinished to true', () => {
384+
const newState = appStateReducer(initialState, { type: 'bitrate-test-finished' });
385+
expect(newState.bitrateTestFinished).toBe(true);
386+
expect(newState.bitrateTestInProgress).toBe(false);
387+
});
388+
});
307389
});
308390

309391
describe('the AppStateProvider component', () => {
@@ -336,6 +418,13 @@ describe('the AppStateProvider component', () => {
336418
"audioGranted": false,
337419
"audioInputTestReport": null,
338420
"audioOutputTestReport": null,
421+
"bitrateTest": Object {
422+
"bitrate": null,
423+
"error": null,
424+
"report": null,
425+
},
426+
"bitrateTestFinished": false,
427+
"bitrateTestInProgress": false,
339428
"deviceError": null,
340429
"downButtonDisabled": false,
341430
"preflightTest": Object {

‎src/components/AppStateProvider/AppStateProvider.tsx

+56-20
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import Video, { PreflightTestReport } from 'twilio-video';
44
import UAParser from 'ua-parser-js';
55
import usePreflightTest from './usePreflightTest/usePreflightTest';
66
import useTwilioStatus from './useTwilioStatus/useTwilioStatus';
7-
import { AudioInputTest, AudioOutputTest, VideoInputTest } from '@twilio/rtc-diagnostics';
7+
import useBitrateTest from './useBitrateTest/useBitrateTest';
8+
import { VideoInputTest, MediaConnectionBitrateTest, AudioInputTest, AudioOutputTest } from '@twilio/rtc-diagnostics';
89

910
export enum ActivePane {
1011
GetStarted,
@@ -39,6 +40,13 @@ interface stateType {
3940
downButtonDisabled: boolean;
4041
preflightTestInProgress: boolean;
4142
preflightTestFinished: boolean;
43+
bitrateTest: {
44+
bitrate: null | number;
45+
report: null | MediaConnectionBitrateTest.Report;
46+
error: null | Error;
47+
};
48+
bitrateTestInProgress: boolean;
49+
bitrateTestFinished: boolean;
4250
}
4351

4452
export type ACTIONTYPE =
@@ -57,7 +65,12 @@ export type ACTIONTYPE =
5765
| { type: 'set-twilio-status'; status: string }
5866
| { type: 'set-twilio-status-error'; error: Error }
5967
| { type: 'preflight-started' }
60-
| { type: 'preflight-finished' };
68+
| { type: 'preflight-finished' }
69+
| { type: 'set-bitrate'; bitrate: number }
70+
| { type: 'set-bitrate-test-error'; error: Error }
71+
| { type: 'set-bitrate-test-report'; report: MediaConnectionBitrateTest.Report }
72+
| { type: 'bitrate-test-started' }
73+
| { type: 'bitrate-test-finished' };
6174

6275
type AppStateContextType = {
6376
state: stateType;
@@ -87,6 +100,13 @@ export const initialState = {
87100
downButtonDisabled: false,
88101
preflightTestInProgress: false,
89102
preflightTestFinished: false,
103+
bitrateTest: {
104+
bitrate: null,
105+
report: null,
106+
error: null,
107+
},
108+
bitrateTestInProgress: false,
109+
bitrateTestFinished: false,
90110
};
91111

92112
export const AppStateContext = createContext<AppStateContextType>(null!);
@@ -100,20 +120,17 @@ export function useAppStateContext() {
100120
}
101121

102122
// helper function for determining whether to disable the down arrow button:
103-
export const isDownButtonDisabled = (
104-
inProgress: boolean,
105-
status: string | null,
106-
pane: ActivePane,
107-
preflightError: Error | null
108-
) => {
123+
export const isDownButtonDisabled = (currentState: stateType) => {
124+
const { activePane, preflightTestInProgress, preflightTest, bitrateTestInProgress } = currentState;
125+
109126
const connectionFailedOrLoading =
110-
pane === ActivePane.Connectivity && (inProgress || status !== 'operational' || preflightError !== null);
111-
const onDeviceCheck = pane === ActivePane.DeviceCheck || pane === ActivePane.DeviceError;
112-
const unsupportedBrowser = pane === ActivePane.BrowserTest && !Video.isSupported;
127+
activePane === ActivePane.Connectivity &&
128+
(preflightTestInProgress || bitrateTestInProgress || preflightTest.error !== null);
113129

114-
if (connectionFailedOrLoading || !ActivePane[pane + 1] || onDeviceCheck || unsupportedBrowser) return true;
130+
const onDeviceCheck = activePane === ActivePane.DeviceCheck || activePane === ActivePane.DeviceError;
131+
const unsupportedBrowser = activePane === ActivePane.BrowserTest && !Video.isSupported;
115132

116-
return false;
133+
return connectionFailedOrLoading || !ActivePane[activePane + 1] || onDeviceCheck || unsupportedBrowser;
117134
};
118135

119136
/**
@@ -229,21 +246,39 @@ export const appStateReducer = produce((draft: stateType, action: ACTIONTYPE) =>
229246
draft.preflightTestFinished = true;
230247
draft.preflightTestInProgress = false;
231248
break;
249+
250+
case 'set-bitrate':
251+
draft.bitrateTest.bitrate = action.bitrate;
252+
break;
253+
254+
case 'set-bitrate-test-report':
255+
draft.bitrateTest.report = action.report;
256+
break;
257+
258+
case 'set-bitrate-test-error':
259+
draft.bitrateTest.error = action.error;
260+
break;
261+
262+
case 'bitrate-test-started':
263+
draft.bitrateTestInProgress = true;
264+
draft.bitrateTestFinished = false;
265+
break;
266+
267+
case 'bitrate-test-finished':
268+
draft.bitrateTestFinished = true;
269+
draft.bitrateTestInProgress = false;
270+
break;
232271
}
233272

234273
const currentState = current(draft);
235274

236-
draft.downButtonDisabled = isDownButtonDisabled(
237-
currentState.preflightTestInProgress,
238-
currentState.twilioStatus,
239-
currentState.activePane,
240-
currentState.preflightTest.error
241-
);
275+
draft.downButtonDisabled = isDownButtonDisabled(currentState);
242276
});
243277

244278
export const AppStateProvider: React.FC = ({ children }) => {
245279
const [state, dispatch] = useReducer(appStateReducer, initialState);
246280
const { startPreflightTest } = usePreflightTest(dispatch);
281+
const { startBitrateTest } = useBitrateTest(dispatch);
247282
const { getTwilioStatus } = useTwilioStatus(dispatch);
248283

249284
const userAgentParser = new UAParser();
@@ -252,14 +287,15 @@ export const AppStateProvider: React.FC = ({ children }) => {
252287
const nextPane = useCallback(() => {
253288
switch (state.activePane) {
254289
case ActivePane.GetStarted:
290+
startBitrateTest();
255291
startPreflightTest();
256292
getTwilioStatus();
257293
dispatch({ type: 'next-pane' });
258294
break;
259295
default:
260296
dispatch({ type: 'next-pane' });
261297
}
262-
}, [startPreflightTest, getTwilioStatus, state, dispatch]);
298+
}, [startBitrateTest, startPreflightTest, getTwilioStatus, state, dispatch]);
263299

264300
useEffect(() => {
265301
navigator.mediaDevices.enumerateDevices().then((devices) => dispatch({ type: 'set-devices', devices }));

0 commit comments

Comments
 (0)
Please sign in to comment.