Skip to content

Commit 8bee451

Browse files
feat(RTC): add support for creating non-standard tracks (#2409)
* feat(RTC): add support for creating non-standard tracks * fix(RTC): add additional checks for creating tracks via mediastream * fix(RTC): simplify options to create track * fix(RTC): fix tests * fix(RTC): update sourceId documentation
1 parent dd81069 commit 8bee451

6 files changed

+96
-4
lines changed

JitsiMeetJS.spec.ts

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import JitsiMeetJS from './JitsiMeetJS';
2+
import { VideoType } from './service/RTC/VideoType';
3+
import { MediaType } from './service/RTC/MediaType';
4+
import { JitsiTrackErrors } from './JitsiTrackErrors';
5+
6+
describe('JitsiMeetJS', () => {
7+
describe('createLocalTracksFromMediaStreams', () => {
8+
it('creates a local track from a media stream', () => {
9+
const canvas = document.createElement('canvas');
10+
11+
const canvasStream = canvas.captureStream(5);
12+
const trackInfo = {
13+
stream: canvasStream,
14+
sourceType: 'canvas',
15+
mediaType: MediaType.VIDEO,
16+
videoType: VideoType.DESKTOP
17+
};
18+
const newTracks = JitsiMeetJS.createLocalTracksFromMediaStreams([ trackInfo ]);
19+
20+
expect(newTracks).toBeDefined();
21+
expect(newTracks.length).toBe(1);
22+
});
23+
24+
it('throws an error if track is not the correct media type', () => {
25+
const canvas = document.createElement('canvas');
26+
27+
const canvasStream = canvas.captureStream(5);
28+
const trackInfo = {
29+
stream: canvasStream,
30+
sourceType: 'canvas',
31+
mediaType: MediaType.AUDIO,
32+
videoType: VideoType.DESKTOP
33+
};
34+
35+
expect(() => JitsiMeetJS.createLocalTracksFromMediaStreams([ trackInfo ]))
36+
.toThrowError(JitsiTrackErrors.TRACK_NO_STREAM_TRACKS_FOUND);
37+
});
38+
});
39+
});

JitsiMeetJS.ts

+33
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import * as ConnectionQualityEvents
3636
import * as E2ePingEvents from './service/e2eping/E2ePingEvents';
3737
import { createGetUserMediaEvent } from './service/statistics/AnalyticsEvents';
3838
import * as RTCStatsEvents from './modules/RTCStats/RTCStatsEvents';
39+
import { VideoType } from './service/RTC/VideoType';
3940

4041
const logger = Logger.getLogger(__filename);
4142

@@ -90,6 +91,13 @@ interface IJitsiMeetJSOptions {
9091
}
9192
}
9293

94+
interface ICreateLocalTrackFromMediaStreamOptions {
95+
stream: MediaStream,
96+
sourceType: string,
97+
mediaType: MediaType,
98+
videoType?: VideoType
99+
}
100+
93101
/**
94102
* The public API of the Jitsi Meet library (a.k.a. {@code JitsiMeetJS}).
95103
*/
@@ -421,6 +429,31 @@ export default {
421429
});
422430
},
423431

432+
/**
433+
* Manually create JitsiLocalTrack's from the provided track info, by exposing the RTC method
434+
*
435+
* @param {Array<ICreateLocalTrackFromMediaStreamOptions>} tracksInfo - array of track information
436+
* @returns {Array<JitsiLocalTrack>} - created local tracks
437+
*/
438+
createLocalTracksFromMediaStreams(tracksInfo) {
439+
return RTC.createLocalTracks(tracksInfo.map((trackInfo) => {
440+
const tracks = trackInfo.stream.getTracks()
441+
.filter(track => track.kind === trackInfo.mediaType);
442+
443+
if (!tracks || tracks.length === 0) {
444+
throw new JitsiTrackError(JitsiTrackErrors.TRACK_NO_STREAM_TRACKS_FOUND, null, null);
445+
}
446+
447+
if (tracks.length > 1) {
448+
throw new JitsiTrackError(JitsiTrackErrors.TRACK_TOO_MANY_TRACKS_IN_STREAM, null, null);
449+
}
450+
451+
trackInfo.track = tracks[0];
452+
453+
return trackInfo;
454+
}));
455+
},
456+
424457
/**
425458
* Create a TrackVADEmitter service that connects an audio track to an VAD (voice activity detection) processor in
426459
* order to obtain VAD scores for individual PCM audio samples.

JitsiTrackErrors.spec.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ describe( "/JitsiTrackErrors members", () => {
1616
TRACK_IS_DISPOSED,
1717
TRACK_NO_STREAM_FOUND,
1818
UNSUPPORTED_RESOLUTION,
19+
TRACK_TOO_MANY_TRACKS_IN_STREAM,
20+
TRACK_NO_STREAM_TRACKS_FOUND,
1921
JitsiTrackErrors,
2022
...others
2123
} = exported;
@@ -33,6 +35,8 @@ describe( "/JitsiTrackErrors members", () => {
3335
expect( TRACK_IS_DISPOSED ).toBe( 'track.track_is_disposed' );
3436
expect( TRACK_NO_STREAM_FOUND ).toBe( 'track.no_stream_found' );
3537
expect( UNSUPPORTED_RESOLUTION ).toBe( 'gum.unsupported_resolution' );
38+
expect( TRACK_TOO_MANY_TRACKS_IN_STREAM ).toBe( 'track.too_many_tracks_in_stream' );
39+
expect( TRACK_NO_STREAM_TRACKS_FOUND ).toBe( 'track.no_stream_tracks_found' );
3640

3741
expect( JitsiTrackErrors ).toBeDefined();
3842

@@ -48,10 +52,12 @@ describe( "/JitsiTrackErrors members", () => {
4852
expect( JitsiTrackErrors.TRACK_IS_DISPOSED ).toBe( 'track.track_is_disposed' );
4953
expect( JitsiTrackErrors.TRACK_NO_STREAM_FOUND ).toBe( 'track.no_stream_found' );
5054
expect( JitsiTrackErrors.UNSUPPORTED_RESOLUTION ).toBe( 'gum.unsupported_resolution' );
55+
expect( JitsiTrackErrors.TRACK_TOO_MANY_TRACKS_IN_STREAM ).toBe( 'track.too_many_tracks_in_stream' );
56+
expect( JitsiTrackErrors.TRACK_NO_STREAM_TRACKS_FOUND ).toBe( 'track.no_stream_tracks_found' );
5157
} );
5258

5359
it( "unknown members", () => {
5460
const keys = Object.keys( others );
5561
expect( keys ).withContext( `Extra members: ${ keys.join( ", " ) }` ).toEqual( [] );
5662
} );
57-
} );
63+
} );

JitsiTrackErrors.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,17 @@ export enum JitsiTrackErrors {
6868
* An error which indicates that requested video resolution is not supported
6969
* by a webcam.
7070
*/
71-
UNSUPPORTED_RESOLUTION = 'gum.unsupported_resolution'
71+
UNSUPPORTED_RESOLUTION = 'gum.unsupported_resolution',
72+
73+
/**
74+
* An error which indicates that there are too many tracks in the provided media stream
75+
*/
76+
TRACK_TOO_MANY_TRACKS_IN_STREAM = 'track.too_many_tracks_in_stream',
77+
78+
/**
79+
* An error which indicates that no tracks were found in the media stream
80+
*/
81+
TRACK_NO_STREAM_TRACKS_FOUND = 'track.no_stream_tracks_found',
7282
}
7383

7484
// exported for backward compatibility
@@ -84,3 +94,5 @@ export const TIMEOUT = JitsiTrackErrors.TIMEOUT;
8494
export const TRACK_IS_DISPOSED = JitsiTrackErrors.TRACK_IS_DISPOSED;
8595
export const TRACK_NO_STREAM_FOUND = JitsiTrackErrors.TRACK_NO_STREAM_FOUND;
8696
export const UNSUPPORTED_RESOLUTION = JitsiTrackErrors.UNSUPPORTED_RESOLUTION;
97+
export const TRACK_TOO_MANY_TRACKS_IN_STREAM = JitsiTrackErrors.TRACK_TOO_MANY_TRACKS_IN_STREAM;
98+
export const TRACK_NO_STREAM_TRACKS_FOUND = JitsiTrackErrors.TRACK_NO_STREAM_TRACKS_FOUND;

modules/RTC/JitsiLocalTrack.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ export default class JitsiLocalTrack extends JitsiTrack {
4747
* @param {number} trackInfo.resolution - The the video resolution if it's a video track
4848
* @param {string} trackInfo.deviceId - The ID of the local device for this track.
4949
* @param {string} trackInfo.facingMode - Thehe camera facing mode used in getUserMedia call (for mobile only).
50-
* @param {sourceId} trackInfo.sourceId - The id of the desktop sharing source. NOTE: defined for desktop sharing
51-
* tracks only.
50+
* @param {sourceId} trackInfo.sourceId - The id of the desktop sharing source, which is the Chrome media source ID,
51+
* returned by Desktop Picker on Electron. NOTE: defined for desktop sharing tracks only.
5252
*/
5353
constructor({
5454
deviceId,

types/hand-crafted/JitsiMeetJS.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ export type JitsiMeetJSType = {
122122

123123
getActiveAudioDevice: () => Promise<Object>; // TODO: can we improve on object?
124124

125+
createLocalTracksFromMediaStreams: ( tracksInfo: unknown[] ) => JitsiLocalTrack[]; // TODO:
126+
125127
// isDeviceListAvailable: () => boolean; // obsosete
126128

127129
// isDeviceChangeAvailable: ( deviceType: string ) => boolean; // obsosete

0 commit comments

Comments
 (0)