Skip to content

Commit

Permalink
feat: migrating the wcme track to webrtc core
Browse files Browse the repository at this point in the history
  • Loading branch information
arun3528 committed Oct 13, 2022
1 parent ad7e34e commit bdee8c6
Show file tree
Hide file tree
Showing 26 changed files with 4,971 additions and 4,146 deletions.
6 changes: 5 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ module.exports = {
ts: 'never',
},
],
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
'import/prefer-default-export': 0,
'jest/no-hooks': [
'error',
Expand All @@ -38,6 +39,8 @@ module.exports = {
},
],
'jest/lowercase-name': 0,
'jest/prefer-lowercase-title': 0,
'jest/require-hook': 0,
'jsdoc/check-tag-names': [
1,
{
Expand Down Expand Up @@ -87,7 +90,8 @@ module.exports = {
'jsdoc/require-param-type': 0,
'jsdoc/require-returns-type': 0,
'jsdoc/valid-types': 1,
'no-shadow': 'off',
'no-underscore-dangle': 0,
'no-shadow': 0,
'@typescript-eslint/no-shadow': ['error'],
},
settings: {
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ jobs:
with:
node-version: 16
- run: yarn install

- run: yarn build
- run: npx semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.npm_token }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
2 changes: 1 addition & 1 deletion .github/workflows/pull-request-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ jobs:
node-version: '16'
- run: yarn install
- run: yarn test:lint
- run: yarn test:coverage
- run: yarn test:coverage
2 changes: 1 addition & 1 deletion .npmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
engine-strict = true
registry = https://registry.npmjs.org
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,8 @@
"editor.formatOnSave": false,
"editor.codeActionsOnSave": ["source.organizeImports", "source.formatDocument"],
"eslint.validate": ["javascript", "typescript"],
"files.eol": "\n"
"files.eol": "\n",
"[typescript]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
}
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@
},
"dependencies": {
"events": "^3.3.0",
"js-logger": "^1.6.1",
"typed-emitter": "^2.1.0",
"webrtc-adapter": "^8.1.2"
},
"lint-staged": {
Expand Down
113 changes: 113 additions & 0 deletions src/device/device-management.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import * as media from '../media';
import { LocalCameraTrack } from '../media/local-camera-track';
import { LocalDisplayTrack } from '../media/local-display-track';
import { LocalMicrophoneTrack } from '../media/local-microphone-track';
import { createBrowserMock } from '../mocks/create-browser-mock';
import MediaStreamStub from '../mocks/media-stream-stub';
import { mocked } from '../mocks/mock';
import { createCameraTrack, createDisplayTrack, createMicrophoneTrack } from './device-management';

jest.mock('../mocks/media-stream-stub');

describe('Device Management', () => {
// const mockedMedia = createBrowserMock(media);
createBrowserMock(MediaStreamStub, 'MediaStream');

const mockStream = mocked(new MediaStream());
const track = new MediaStreamTrack();
mockStream.getTracks.mockReturnValue([track]);

describe('createMicrophoneTrack', () => {
jest
.spyOn(media, 'getUserMedia')
.mockImplementation()
.mockReturnValue(Promise.resolve(mockStream as unknown as MediaStream));

it('should call getUserMedia', async () => {
expect.assertions(1);

await createMicrophoneTrack({ deviceId: 'test-device-id' });
expect(media.getUserMedia).toHaveBeenCalledWith({
audio: {
deviceId: 'test-device-id',
},
});
});

it('should return a LocalMicrophoneTrack instance', async () => {
expect.assertions(1);

const localMicrophoneTrack = await createMicrophoneTrack({ deviceId: 'test-device-id' });
expect(localMicrophoneTrack).toBeInstanceOf(LocalMicrophoneTrack);
});
});

describe('createCameraTrack', () => {
jest
.spyOn(media, 'getUserMedia')
.mockImplementation()
.mockReturnValue(Promise.resolve(mockStream as unknown as MediaStream));

it('should call getUserMedia', async () => {
expect.assertions(1);

await createCameraTrack({ deviceId: 'test-device-id' });
expect(media.getUserMedia).toHaveBeenCalledWith({
video: {
deviceId: 'test-device-id',
},
});
});

it('should call getUserMedia with constraints', async () => {
expect.assertions(1);

await createCameraTrack({
deviceId: 'test-device-id',
aspectRatio: 1.777,
width: 1920,
height: 1080,
frameRate: 30,
facingMode: { exact: 'user' },
});
expect(media.getUserMedia).toHaveBeenCalledWith({
video: {
deviceId: 'test-device-id',
aspectRatio: 1.777,
width: 1920,
height: 1080,
frameRate: 30,
facingMode: { exact: 'user' },
},
});
});

it('should return a LocalCameraTrack instance', async () => {
expect.assertions(1);

const localCameraTrack = await createCameraTrack({ deviceId: 'test-device-id' });
expect(localCameraTrack).toBeInstanceOf(LocalCameraTrack);
});
});

describe('createDisplayTrack', () => {
jest
.spyOn(media, 'getDisplayMedia')
.mockImplementation()
.mockReturnValue(Promise.resolve(mockStream as unknown as MediaStream));

it('should call getDisplayMedia', async () => {
expect.assertions(1);

await createDisplayTrack();
expect(media.getDisplayMedia).toHaveBeenCalledWith({ video: true });
});

it('should return a LocalDisplayTrack instance', async () => {
expect.assertions(1);

const localDisplayTrack = await createDisplayTrack();
expect(localDisplayTrack).toBeInstanceOf(LocalDisplayTrack);
});
});
});
147 changes: 147 additions & 0 deletions src/device/device-management.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import * as media from '../media';
import { LocalCameraTrack } from '../media/local-camera-track';
import { LocalDisplayTrack } from '../media/local-display-track';
import { LocalMicrophoneTrack } from '../media/local-microphone-track';

export enum ErrorTypes {
DEVICE_PERMISSION_DENIED = 'DEVICE_PERMISSION_DENIED',
CREATE_CAMERA_TRACK_FAILED = 'CREATE_CAMERA_TRACK_FAILED',
CREATE_MICROPHONE_TRACK_FAILED = 'CREATE_MICROPHONE_TRACK_FAILED',
}

/**
* Represents a WCME error, which contains error type and error message.
*/
export class WcmeError {
type: string;

message: string;

/**
* Creates new error.
*
* @param type - Error type.
* @param message - Error message.
*/
constructor(type: ErrorTypes, message = '') {
this.type = type;
this.message = message;
}
}

export type AudioDeviceConstraints = {
deviceId?: string;
};

export type VideoDeviceConstraints = {
deviceId?: ConstrainDOMString;
width?: ConstrainULong;
height?: ConstrainULong;
aspectRatio?: ConstrainDouble;
frameRate?: ConstrainDouble;
facingMode?: ConstrainDOMString;
};

/**
* Creates a camera video track.
*
* @param constraints - Video device constraints.
* @returns A LocalTrack object or an error.
*/
export async function createCameraTrack(
constraints?: VideoDeviceConstraints
): Promise<LocalCameraTrack> {
let stream: MediaStream;
try {
stream = await media.getUserMedia({ video: { ...constraints } });
} catch (error) {
throw new WcmeError(
ErrorTypes.CREATE_CAMERA_TRACK_FAILED,
`Failed to create camera track ${error}`
);
}
return new LocalCameraTrack(stream);
}

/**
* Creates a microphone audio track.
*
* @param constraints - Audio device constraints.
* @returns A LocalTrack object or an error.
*/
export async function createMicrophoneTrack(
constraints?: AudioDeviceConstraints
): Promise<LocalMicrophoneTrack> {
let stream: MediaStream;
try {
stream = await media.getUserMedia({ audio: { ...constraints } });
} catch (error) {
throw new WcmeError(
ErrorTypes.CREATE_MICROPHONE_TRACK_FAILED,
`Failed to create microphone track ${error}`
);
}
return new LocalMicrophoneTrack(stream);
}

/**
* Creates a display video track.
*
* @returns A Promise that resolves to a LocalDisplayTrack.
*/
export async function createDisplayTrack(): Promise<LocalDisplayTrack> {
const stream = await media.getDisplayMedia({ video: true });
return new LocalDisplayTrack(stream);
}

/**
* Enumerates the media input and output devices available.
*
* @param deviceKind - Optional filter to return a specific device kind.
* @returns List of media devices in an array of MediaDeviceInfo objects.
*/
export async function getDevices(deviceKind?: media.DeviceKind): Promise<MediaDeviceInfo[]> {
let devices: MediaDeviceInfo[];
try {
devices = await media.ensureDevicePermissions(
[media.DeviceKind.AudioInput, media.DeviceKind.VideoInput],
media.enumerateDevices
);
} catch (error) {
throw new WcmeError(ErrorTypes.DEVICE_PERMISSION_DENIED, 'Failed to ensure device permissions');
}

return devices.filter((v: MediaDeviceInfo) => (deviceKind ? v.kind === deviceKind : true));
}

/**
* Helper function to get a list of microphone devices.
*
* @returns List of microphone devices in an array of MediaDeviceInfo objects.
*/
export async function getAudioInputDevices(): Promise<MediaDeviceInfo[]> {
return getDevices(media.DeviceKind.AudioInput);
}

/**
* Helper function to get a list of speaker devices.
*
* @returns List of speaker devices in an array of MediaDeviceInfo objects.
*/
export async function getAudioOutputDevices(): Promise<MediaDeviceInfo[]> {
return getDevices(media.DeviceKind.AudioOutput);
}

/**
* Helper function to get a list of camera devices.
*
* @returns List of camera devices in an array of MediaDeviceInfo objects.
*/
export async function getVideoInputDevices(): Promise<MediaDeviceInfo[]> {
return getDevices(media.DeviceKind.VideoInput);
}

/**
* Export the setOnDeviceChangeHandler method directly from the core lib.
*/
export const { setOnDeviceChangeHandler } = media;
10 changes: 9 additions & 1 deletion src/event-emitter.ts
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
export { EventEmitter } from 'events';
import { EventEmitter as EE } from 'events';
import TypedEmitter, { EventMap } from 'typed-emitter';

/**
* Typed event emitter class.
*/
export default class EventEmitter<T extends EventMap> extends (EE as {
new <TT extends EventMap>(): TypedEmitter<TT>;
})<T> {}
8 changes: 6 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
// support 'export * as ___ from ___' yet.
import * as media from './media';

export * from './device/device-management';
export * from './media/local-camera-track';
export * from './media/local-display-track';
export * from './media/local-microphone-track';
export * from './media/local-track';
export * from './peer-connection';
export { media };

export * from './peer-connection-utils';
export { media };
12 changes: 6 additions & 6 deletions src/media.spec.ts → src/media/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import * as media from './media';
import { createBrowserMock } from './mocks/create-browser-mock';
import { createPermissionStatus } from './mocks/create-permission-status';
import { MediaStream } from './mocks/media-stream-stub';
import { Navigator } from './mocks/navigator-stub';
import * as media from '.';
import { createBrowserMock } from '../mocks/create-browser-mock';
import { createPermissionStatus } from '../mocks/create-permission-status';
import MediaStream from '../mocks/media-stream-stub';
import { Navigator } from '../mocks/navigator-stub';

jest.mock('./mocks/navigator-stub');
jest.mock('../mocks/navigator-stub');

/**
* Create example MediaDeviceInfo objects to be used in mocks.
Expand Down
4 changes: 2 additions & 2 deletions src/media.ts → src/media/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { error } from './util/logger';
import { logger } from '../util/logger';

export enum DeviceKind {
AudioInput = 'audioinput',
Expand Down Expand Up @@ -137,7 +137,7 @@ export async function ensureDevicePermissions<T>(

return callback();
} catch (e) {
error(e);
logger.error(e);
throw new Error('Failed to ensure device permissions.');
}
}
6 changes: 6 additions & 0 deletions src/media/local-camera-track.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { LocalTrack } from './local-track';

/**
* Represents a local track for a camera source.
*/
export class LocalCameraTrack extends LocalTrack {}
Loading

0 comments on commit bdee8c6

Please sign in to comment.