|
@@ -0,0 +1,190 @@
|
|
1
|
+
|
|
2
|
+import { getLogger } from 'jitsi-meet-logger';
|
|
3
|
+
|
|
4
|
+import * as JitsiConferenceEvents from '../../JitsiConferenceEvents';
|
|
5
|
+import CodecMimeType from '../../service/RTC/CodecMimeType';
|
|
6
|
+import * as MediaType from '../../service/RTC/MediaType';
|
|
7
|
+import browser from '../browser';
|
|
8
|
+
|
|
9
|
+const logger = getLogger(__filename);
|
|
10
|
+
|
|
11
|
+/**
|
|
12
|
+ * This class handles the codec selection mechanism for the conference based on the config.js settings.
|
|
13
|
+ * The preferred codec is selected based on the settings and the list of codecs supported by the browser.
|
|
14
|
+ * The preferred codec is published in presence which is then used by the other endpoints in the
|
|
15
|
+ * conference to pick a supported codec at join time and when the call transitions between p2p and jvb
|
|
16
|
+ * connections.
|
|
17
|
+ */
|
|
18
|
+export class CodecSelection {
|
|
19
|
+ /**
|
|
20
|
+ * Creates a new instance for a given conference.
|
|
21
|
+ *
|
|
22
|
+ * @param {JitsiConference} conference the conference instance
|
|
23
|
+ * @param {*} options
|
|
24
|
+ * @param {string} options.disabledCodec the codec that needs to be disabled.
|
|
25
|
+ * @param {boolean} options.enforcePreferredCodec whether codec preference has to be
|
|
26
|
+ * enforced even when an endpoints that doesn't support the preferred codec joins the call.
|
|
27
|
+ * Falling back to the standard codec will be skipped when this option is true, endpoints
|
|
28
|
+ * that do not support the preferred codec may not be able to encode/decode video when this happens.
|
|
29
|
+ * @param {string} options.jvbCodec the codec that is preferred on jvb connection.
|
|
30
|
+ * @param {string} options.p2pCodec the codec that is preferred on p2p connection.
|
|
31
|
+ */
|
|
32
|
+ constructor(conference, options) {
|
|
33
|
+ this.conference = conference;
|
|
34
|
+
|
|
35
|
+ // VP8 cannot be disabled and it will be the default codec when no preference is set.
|
|
36
|
+ this.disabledCodec = options.disabledCodec === CodecMimeType.VP8
|
|
37
|
+ ? undefined
|
|
38
|
+ : this._getCodecMimeType(options.disabledCodec);
|
|
39
|
+
|
|
40
|
+ // Check if the codec values passed are valid.
|
|
41
|
+ const jvbCodec = this._getCodecMimeType(options.jvbCodec);
|
|
42
|
+ const p2pCodec = this._getCodecMimeType(options.p2pCodec);
|
|
43
|
+
|
|
44
|
+ this.jvbPreferredCodec = jvbCodec && this._isCodecSupported(jvbCodec) ? jvbCodec : CodecMimeType.VP8;
|
|
45
|
+ this.p2pPreferredCodec = p2pCodec && this._isCodecSupported(p2pCodec) ? p2pCodec : CodecMimeType.VP8;
|
|
46
|
+ this.enforcePreferredCodec = options.enforcePreferredCodec;
|
|
47
|
+
|
|
48
|
+ // Do not prefer VP9 on Firefox because of the following bug.
|
|
49
|
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1633876
|
|
50
|
+ if (browser.isFirefox() && this.jvbPreferredCodec === CodecMimeType.VP9) {
|
|
51
|
+ this.jvbPreferredCodec = CodecMimeType.VP8;
|
|
52
|
+ }
|
|
53
|
+
|
|
54
|
+ // Keep a list of participants that join the call with a non-preferred codec.
|
|
55
|
+ // The call is upgraded to the preferred codec once that list is empty.
|
|
56
|
+ this.nonPreferredParticipants = [];
|
|
57
|
+
|
|
58
|
+ this.conference.on(
|
|
59
|
+ JitsiConferenceEvents.USER_JOINED,
|
|
60
|
+ this._onParticipantJoined.bind(this));
|
|
61
|
+ this.conference.on(
|
|
62
|
+ JitsiConferenceEvents.USER_LEFT,
|
|
63
|
+ this._onParticipantLeft.bind(this));
|
|
64
|
+ this.conference.on(
|
|
65
|
+ JitsiConferenceEvents._MEDIA_SESSION_STARTED,
|
|
66
|
+ session => this._onMediaSessionStared(session));
|
|
67
|
+ }
|
|
68
|
+
|
|
69
|
+ /**
|
|
70
|
+ * Checks if a given string is a valid video codec mime type.
|
|
71
|
+ *
|
|
72
|
+ * @param {string} codec the codec string that needs to be validated.
|
|
73
|
+ * @returns {CodecMimeType|null} mime type if valid, null otherwise.
|
|
74
|
+ * @private
|
|
75
|
+ */
|
|
76
|
+ _getCodecMimeType(codec) {
|
|
77
|
+ if (typeof codec === 'string') {
|
|
78
|
+ return Object.values(CodecMimeType).find(value => value === codec.toLowerCase());
|
|
79
|
+ }
|
|
80
|
+
|
|
81
|
+ return null;
|
|
82
|
+ }
|
|
83
|
+
|
|
84
|
+ /**
|
|
85
|
+ * Checks if the given codec is supported by the browser.
|
|
86
|
+ *
|
|
87
|
+ * @param {CodecMimeType} preferredCodec codec to be checked.
|
|
88
|
+ * @returns {boolean} true if the given codec is supported, false otherwise.
|
|
89
|
+ * @private
|
|
90
|
+ */
|
|
91
|
+ _isCodecSupported(preferredCodec) {
|
|
92
|
+ // Skip the check on FF and RN because they do not support the getCapabilities API.
|
|
93
|
+ // It is safe to assume both of them support all the codecs supported by Chrome.
|
|
94
|
+ if (browser.isFirefox() || browser.isReactNative()) {
|
|
95
|
+ return true;
|
|
96
|
+ }
|
|
97
|
+
|
|
98
|
+ return window.RTCRtpReceiver
|
|
99
|
+ && window.RTCRtpReceiver.getCapabilities('video').codecs
|
|
100
|
+ .some(codec => codec.mimeType.toLowerCase() === `video/${preferredCodec}`);
|
|
101
|
+ }
|
|
102
|
+
|
|
103
|
+ /**
|
|
104
|
+ * Handles the {@link JitsiConferenceEvents._MEDIA_SESSION_STARTED} event. Codecs need to be
|
|
105
|
+ * configured on the media session that is newly created.
|
|
106
|
+ *
|
|
107
|
+ * @param {JingleSessionPC} mediaSession media session that started.
|
|
108
|
+ * @returns {void}
|
|
109
|
+ * @private
|
|
110
|
+ */
|
|
111
|
+ _onMediaSessionStared(mediaSession) {
|
|
112
|
+ const preferredCodec = mediaSession.isP2P ? this.p2pPreferredCodec : this.jvbPreferredCodec;
|
|
113
|
+ const disabledCodec = this.disabledCodec && this._isCodecSupported(this.disabledCodec)
|
|
114
|
+ ? this.disabledCodec
|
|
115
|
+ : null;
|
|
116
|
+
|
|
117
|
+ mediaSession.setVideoCodecs(preferredCodec, disabledCodec);
|
|
118
|
+ }
|
|
119
|
+
|
|
120
|
+ /**
|
|
121
|
+ * Handles the {@link JitsiConferenceEvents.USER_JOINED} event. When a new user joins the call,
|
|
122
|
+ * the codec types are compared and the codec configued on the peerconnection is updated when
|
|
123
|
+ * needed.
|
|
124
|
+ *
|
|
125
|
+ * @param {string} id endpoint id of the newly joined user.
|
|
126
|
+ * @returns {void}
|
|
127
|
+ * @private
|
|
128
|
+ */
|
|
129
|
+ _onParticipantJoined(id) {
|
|
130
|
+ const session = this.conference.jvbJingleSession;
|
|
131
|
+
|
|
132
|
+ if (session && !this.enforcePreferredCodec) {
|
|
133
|
+ const peerMediaInfo = session.signalingLayer.getPeerMediaInfo(id, MediaType.VIDEO);
|
|
134
|
+
|
|
135
|
+ if (peerMediaInfo) {
|
|
136
|
+ const newCodec = peerMediaInfo.codecType;
|
|
137
|
+ const currentCodec = session.getConfiguredVideoCodec();
|
|
138
|
+
|
|
139
|
+ // Add the participant to the list of participants that
|
|
140
|
+ // don't support the preferred codec.
|
|
141
|
+ if (newCodec !== this.jvbPreferredCodec) {
|
|
142
|
+ this.nonPreferredParticipants.push(id);
|
|
143
|
+ }
|
|
144
|
+ logger.warn(`Current: ${currentCodec}, new: ${newCodec}`);
|
|
145
|
+ if (newCodec
|
|
146
|
+ && newCodec !== this.jvbPreferredCodec
|
|
147
|
+ && newCodec !== currentCodec
|
|
148
|
+ && this._isCodecSupported(newCodec)) {
|
|
149
|
+ session.setVideoCodecs(newCodec);
|
|
150
|
+ }
|
|
151
|
+ }
|
|
152
|
+ }
|
|
153
|
+ }
|
|
154
|
+
|
|
155
|
+ /**
|
|
156
|
+ * Handles the {@link JitsiConferenceEvents.USER_LEFT} event. When a user leaves the call,
|
|
157
|
+ * the codec configured on the peerconnection is updated to the preferred codec if all the
|
|
158
|
+ * users that do not support the preferred codec have left the call.
|
|
159
|
+ *
|
|
160
|
+ * @param {string} id endpoint id of the user that has left the call.
|
|
161
|
+ * @returns {void}
|
|
162
|
+ * @private
|
|
163
|
+ */
|
|
164
|
+ _onParticipantLeft(id) {
|
|
165
|
+ const session = this.conference.jvbJingleSession;
|
|
166
|
+
|
|
167
|
+ if (session && !this.enforcePreferredCodec) {
|
|
168
|
+ const index = this.nonPreferredParticipants.findIndex(participantId => participantId === id);
|
|
169
|
+
|
|
170
|
+ if (index > -1) {
|
|
171
|
+ this.nonPreferredParticipants.splice(index, 1);
|
|
172
|
+ }
|
|
173
|
+
|
|
174
|
+ // If all the participants that have joined the conference with a
|
|
175
|
+ // non-preferred codec have left, switch to the preferred codec.
|
|
176
|
+ if (!this.nonPreferredParticipants.length) {
|
|
177
|
+ session.setVideoCodecs(this.jvbPreferredCodec);
|
|
178
|
+ }
|
|
179
|
+ }
|
|
180
|
+ }
|
|
181
|
+
|
|
182
|
+ /**
|
|
183
|
+ * Returns the preferred codec for the conference.
|
|
184
|
+ *
|
|
185
|
+ * @returns {CodecMimeType} preferred codec.
|
|
186
|
+ */
|
|
187
|
+ getPreferredCodec() {
|
|
188
|
+ return this.conference.isP2PActive() ? this.p2pPreferredCodec : this.jvbPreferredCodec;
|
|
189
|
+ }
|
|
190
|
+}
|