mirror of https://github.com/OpenVidu/openvidu.git
openvidu-test-e2e: add BWE convergence tests. Remove custom ingress test
parent
7350f60973
commit
51537db31b
|
|
@ -1295,6 +1295,181 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Firefox H264 simulcast BWE convergence")
|
||||
void firefoxH264SimulcastBweConvergenceTest() throws Exception {
|
||||
log.info("Firefox H264 simulcast BWE convergence");
|
||||
simulcastBweConvergenceTest("h264", "firefox");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Firefox VP8 simulcast BWE convergence")
|
||||
void firefoxVP8SimulcastBweConvergenceTest() throws Exception {
|
||||
log.info("Firefox VP8 simulcast BWE convergence");
|
||||
simulcastBweConvergenceTest("vp8", "firefox");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Chrome H264 simulcast BWE convergence")
|
||||
void chromeH264SimulcastBweConvergenceTest() throws Exception {
|
||||
log.info("Chrome H264 simulcast BWE convergence");
|
||||
simulcastBweConvergenceTest("h264", "chrome");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Chrome VP8 simulcast BWE convergence")
|
||||
void chromeVP8SimulcastBweConvergenceTest() throws Exception {
|
||||
log.info("Chrome VP8 simulcast BWE convergence");
|
||||
simulcastBweConvergenceTest("vp8", "chrome");
|
||||
}
|
||||
|
||||
private void simulcastBweConvergenceTest(String publisherCodec, String subscriberBrowser) throws Exception {
|
||||
final int EXPECTED_HIGHEST_WIDTH = 1920;
|
||||
// With mediasoup the publisher's high simulcast layer can take ~20s to activate
|
||||
// (a separate, still-open BWE issue from the subscriber-side). Allow margin
|
||||
// above that so the test reflects "does it converge" rather than "is it fast".
|
||||
final int BWE_CONVERGENCE_TIMEOUT_MS = 40000;
|
||||
final int POLL_INTERVAL_MS = 2000;
|
||||
final String subscriberLabel = subscriberBrowser.toUpperCase() + "_SUBSCRIBER";
|
||||
final CountDownLatch latch = new CountDownLatch(2);
|
||||
ExecutorService executor = Executors.newFixedThreadPool(2);
|
||||
// Wall-clock timestamps of when each side first reports the 1920 layer.
|
||||
// Comparing them tells whether a slow convergence is publisher-uplink or
|
||||
// subscriber-downlink.
|
||||
final java.util.concurrent.atomic.AtomicLong publisher1920AtMs = new java.util.concurrent.atomic.AtomicLong(-1);
|
||||
final java.util.concurrent.atomic.AtomicLong subscriber1920AtMs = new java.util.concurrent.atomic.AtomicLong(
|
||||
-1);
|
||||
|
||||
Future<?> task1 = executor.submit(() -> {
|
||||
try {
|
||||
OpenViduTestappUser chromeUser = setupBrowserAndConnectToOpenViduTestapp("chrome");
|
||||
this.addOnlyPublisherVideo(chromeUser, true, false, false);
|
||||
WebElement participantNameInput = chromeUser.getDriver()
|
||||
.findElement(By.id("participant-name-input-0"));
|
||||
participantNameInput.clear();
|
||||
participantNameInput.sendKeys("CHROME_PUBLISHER");
|
||||
this.forceCodec(chromeUser, 0, publisherCodec);
|
||||
this.setPublisherSimulcastLayersAndResolution(chromeUser, 0, "h360", 1920, 1080);
|
||||
chromeUser.getDriver().findElement(By.className("connect-btn")).click();
|
||||
chromeUser.getEventManager().waitUntilEventReaches("localTrackSubscribed", "ParticipantEvent", 1);
|
||||
|
||||
// Measure when the PUBLISHER itself starts actively sending the 1920 (rid
|
||||
// "f") layer (active==true && frameWidth==1920). Compared with the
|
||||
// subscriber's 1920 time, this disambiguates publisher-uplink vs
|
||||
// subscriber-downlink as the convergence bottleneck.
|
||||
WebElement publisherVideo = chromeUser.getDriver()
|
||||
.findElement(By.cssSelector("#openvidu-instance-0 video.local"));
|
||||
waitUntilVideoLayersNotEmpty(chromeUser, publisherVideo);
|
||||
long pubStart = System.currentTimeMillis();
|
||||
while (System.currentTimeMillis() - pubStart < BWE_CONVERGENCE_TIMEOUT_MS) {
|
||||
try {
|
||||
String rawLayers = getLayersAsString(chromeUser, publisherVideo);
|
||||
log.info("publisher [{}] raw layers: {}", publisherCodec, rawLayers);
|
||||
// Highest active layer width across all simulcast layers (rid-agnostic).
|
||||
Integer maxActiveWidth = null;
|
||||
for (JsonElement el : JsonParser.parseString(rawLayers).getAsJsonArray()) {
|
||||
JsonObject lo = el.getAsJsonObject();
|
||||
JsonElement wEl = lo.get("frameWidth");
|
||||
JsonElement aEl = lo.get("active");
|
||||
boolean active = aEl != null && !aEl.isJsonNull() && aEl.getAsBoolean();
|
||||
if (active && wEl != null && !wEl.isJsonNull()) {
|
||||
int w = wEl.getAsInt();
|
||||
if (maxActiveWidth == null || w > maxActiveWidth) {
|
||||
maxActiveWidth = w;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (maxActiveWidth != null && maxActiveWidth >= EXPECTED_HIGHEST_WIDTH) {
|
||||
publisher1920AtMs.set(System.currentTimeMillis());
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Error getting publisher 'f' layer", e);
|
||||
}
|
||||
Thread.sleep(POLL_INTERVAL_MS);
|
||||
}
|
||||
latch.countDown();
|
||||
latch.await(60, TimeUnit.SECONDS);
|
||||
gracefullyLeaveParticipants(chromeUser, 1);
|
||||
} catch (Exception e) {
|
||||
Assertions.fail("Error while setting up Chrome publisher", e);
|
||||
}
|
||||
});
|
||||
|
||||
Future<?> task2 = executor.submit(() -> {
|
||||
try {
|
||||
OpenViduTestappUser subscriberUser = setupBrowserAndConnectToOpenViduTestapp(subscriberBrowser);
|
||||
this.addSubscriber(subscriberUser, false);
|
||||
WebElement participantNameInput = subscriberUser.getDriver()
|
||||
.findElement(By.id("participant-name-input-0"));
|
||||
participantNameInput.clear();
|
||||
participantNameInput.sendKeys(subscriberLabel);
|
||||
subscriberUser.getDriver().findElement(By.className("connect-btn")).click();
|
||||
subscriberUser.getEventManager().waitUntilEventReaches("trackSubscribed", "ParticipantEvent", 1);
|
||||
subscriberUser.getWaiter().until(ExpectedConditions.numberOfElementsToBe(By.tagName("video"), 1));
|
||||
Assertions.assertTrue(assertAllElementsHaveTracks(subscriberUser, "video", false, true),
|
||||
"HTMLVideoElements were expected to have only one video track");
|
||||
|
||||
WebElement subscriberVideo = subscriberUser.getDriver()
|
||||
.findElement(By.cssSelector("#openvidu-instance-0 video.remote"));
|
||||
waitUntilVideoLayersNotEmpty(subscriberUser, subscriberVideo);
|
||||
|
||||
// Poll for BWE convergence: subscriber should reach highest layer
|
||||
long startTime = System.currentTimeMillis();
|
||||
int lastWidth = 0;
|
||||
boolean converged = false;
|
||||
while (System.currentTimeMillis() - startTime < BWE_CONVERGENCE_TIMEOUT_MS) {
|
||||
try {
|
||||
lastWidth = getSubscriberVideoFrameWidth(subscriberUser, subscriberVideo);
|
||||
log.info("{} subscriber frameWidth: {}", subscriberBrowser, lastWidth);
|
||||
if (lastWidth == EXPECTED_HIGHEST_WIDTH) {
|
||||
converged = true;
|
||||
subscriber1920AtMs.set(System.currentTimeMillis());
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Error getting subscriber frameWidth", e);
|
||||
}
|
||||
Thread.sleep(POLL_INTERVAL_MS);
|
||||
}
|
||||
Assertions.assertTrue(converged,
|
||||
subscriberBrowser + " subscriber BWE did not converge to highest layer. Last frameWidth: "
|
||||
+ lastWidth + ". Expected: " + EXPECTED_HIGHEST_WIDTH);
|
||||
|
||||
latch.countDown();
|
||||
latch.await(10, TimeUnit.SECONDS);
|
||||
gracefullyLeaveParticipants(subscriberUser, 1);
|
||||
} catch (Exception e) {
|
||||
Assertions.fail("Error while setting up " + subscriberBrowser + " subscriber", e);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
task1.get();
|
||||
task2.get();
|
||||
} catch (ExecutionException ex) {
|
||||
Assertions.fail("Error while running browsers in parallel", ex);
|
||||
}
|
||||
|
||||
long pub = publisher1920AtMs.get();
|
||||
long sub = subscriber1920AtMs.get();
|
||||
String verdict;
|
||||
if (pub < 0 && sub < 0) {
|
||||
verdict = "neither publisher nor subscriber reached 1920 within timeout";
|
||||
} else if (pub < 0) {
|
||||
verdict = "subscriber reached 1920 but publisher never reported actively sending it (measurement gap)";
|
||||
} else if (sub < 0) {
|
||||
verdict = "publisher sent 1920 but subscriber never received it";
|
||||
} else {
|
||||
long deltaMs = sub - pub;
|
||||
verdict = "subscriber got 1920 " + deltaMs + "ms after the publisher started sending it => "
|
||||
+ (deltaMs > 6000 ? "SUBSCRIBER-DOWNLINK is the bottleneck"
|
||||
: "PUBLISHER-UPLINK-gated (subscriber receives 1920 ~as soon as the publisher sends it)");
|
||||
}
|
||||
log.info("[BWE-SIDE] codec={} subscriber={} publisher1920AtMs={} subscriber1920AtMs={} => {}", publisherCodec,
|
||||
subscriberBrowser, pub, sub, verdict);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Firefox toggle subscription")
|
||||
void firefoxToggleSubscriptionTest() throws Exception {
|
||||
|
|
@ -2612,8 +2787,6 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
|||
|
||||
@Test
|
||||
@DisplayName("Ingress VP8 Simulcast Chrome")
|
||||
// TODO: remove tag when not forcing VP8 no-simulcast in ingress with mediasoup
|
||||
@OnlyPion
|
||||
void ingressVP8SimulcastChromeTest() throws Exception {
|
||||
OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome");
|
||||
|
||||
|
|
@ -2627,8 +2800,6 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
|||
|
||||
@Test
|
||||
@DisplayName("Ingress VP8 Simulcast Firefox")
|
||||
// TODO: remove tag when not forcing VP8 no-simulcast in ingress with mediasoup
|
||||
@OnlyPion
|
||||
void ingressVP8SimulcastFirefoxTest() throws Exception {
|
||||
OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("firefox");
|
||||
|
||||
|
|
@ -2642,8 +2813,6 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
|||
|
||||
@Test
|
||||
@DisplayName("Ingress H264 Simulcast Chrome")
|
||||
// TODO: remove tag when not forcing VP8 no-simulcast in ingress with mediasoup
|
||||
@OnlyPion
|
||||
void ingressH264SimulcastChromeTest() throws Exception {
|
||||
OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome");
|
||||
|
||||
|
|
@ -2656,8 +2825,6 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
|||
|
||||
@Test
|
||||
@DisplayName("Ingress H264 Simulcast Firefox")
|
||||
// TODO: remove tag when not forcing VP8 no-simulcast in ingress with mediasoup
|
||||
@OnlyPion
|
||||
void ingressH264SimulcastFirefoxTest() throws Exception {
|
||||
OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("firefox");
|
||||
|
||||
|
|
@ -2670,8 +2837,6 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
|||
|
||||
@Test
|
||||
@DisplayName("Ingress H264 Simulcast two layers Chrome")
|
||||
// TODO: remove tag when not forcing VP8 no-simulcast in ingress with mediasoup
|
||||
@OnlyPion
|
||||
void ingressH264SimulcastTwoLayersChromeTest() throws Exception {
|
||||
OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome");
|
||||
|
||||
|
|
@ -2684,8 +2849,6 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
|||
|
||||
@Test
|
||||
@DisplayName("Ingress H264 Simulcast two layers Firefox")
|
||||
// TODO: remove tag when not forcing VP8 no-simulcast in ingress with mediasoup
|
||||
@OnlyPion
|
||||
void ingressH264SimulcastTwoLayersFirefoxTest() throws Exception {
|
||||
OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("firefox");
|
||||
|
||||
|
|
@ -2722,8 +2885,6 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
|||
|
||||
@Test
|
||||
@DisplayName("Ingress H264 No Simulcast Chrome")
|
||||
// TODO: remove tag when not forcing VP8 no-simulcast in ingress with mediasoup
|
||||
@OnlyPion
|
||||
void ingressH264NoSimulcastChromeTest() throws Exception {
|
||||
OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome");
|
||||
|
||||
|
|
@ -2736,8 +2897,6 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
|||
|
||||
@Test
|
||||
@DisplayName("Ingress H264 No Simulcast Firefox")
|
||||
// TODO: remove tag when not forcing VP8 no-simulcast in ingress with mediasoup
|
||||
@OnlyPion
|
||||
void ingressH264NoSimulcastFirefoxTest() throws Exception {
|
||||
OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("firefox");
|
||||
|
||||
|
|
@ -2748,75 +2907,6 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
|||
testNoSimulcast(user, subscriberVideo);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Custom ingress")
|
||||
// TODO: remove tag when not using custom ingress image with mediasoup
|
||||
@OnlyMediasoup
|
||||
void customIngressTest() throws Exception {
|
||||
OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("firefox");
|
||||
|
||||
// With custom ingress and mediasoup it should force VP8 no simulcast with
|
||||
// highest quality layer width, height, framerate and bitrate
|
||||
log.info("Custom ingress");
|
||||
|
||||
this.addSubscriber(user, true);
|
||||
user.getDriver().findElement(By.className("connect-btn")).sendKeys(Keys.ENTER);
|
||||
user.getEventManager().waitUntilEventReaches("connected", "RoomEvent", 1);
|
||||
|
||||
// Try publishing H264 with 2 layer simulcast
|
||||
createIngress(user, "H264_540P_25FPS_2_LAYERS", null, true, "HTTP", null);
|
||||
|
||||
user.getEventManager().waitUntilEventReaches("trackSubscribed", "ParticipantEvent", 1);
|
||||
user.getWaiter().until(ExpectedConditions.numberOfElementsToBe(By.tagName("video"), 1));
|
||||
int numberOfVideos = user.getDriver().findElements(By.tagName("video")).size();
|
||||
Assertions.assertEquals(1, numberOfVideos, "Wrong number of videos");
|
||||
Assertions.assertTrue(assertAllElementsHaveTracks(user, "video", false, true),
|
||||
"HTMLVideoElements were expected to have only one video track");
|
||||
|
||||
// Should receive VP8 960x540 25 fps
|
||||
WebElement subscriberVideo = user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 video.remote"));
|
||||
waitUntilVideoLayersNotEmpty(user, subscriberVideo);
|
||||
Assertions.assertEquals(1, getLayersAsJsonArray(user, subscriberVideo).size());
|
||||
long bytesReceived = this.getSubscriberVideoBytesReceived(user, subscriberVideo);
|
||||
this.waitUntilSubscriberBytesReceivedIncrease(user, subscriberVideo, bytesReceived);
|
||||
this.waitUntilSubscriberFramesPerSecondNotZero(user, subscriberVideo);
|
||||
JsonArray json = this.getLayersAsJsonArray(user, subscriberVideo);
|
||||
String subscriberCodec = json.get(0).getAsJsonObject().get("codec").getAsString();
|
||||
String expectedCodec = "video/VP8";
|
||||
Assertions.assertEquals(expectedCodec, subscriberCodec);
|
||||
this.waitUntilSubscriberFramesPerSecondIs(user, subscriberVideo, 25);
|
||||
this.waitUntilSubscriberFrameWidthIs(user, subscriberVideo, 960);
|
||||
this.waitUntilSubscriberFrameHeightIs(user, subscriberVideo, 540);
|
||||
|
||||
this.deleteAllIngresses(LK_INGRESS);
|
||||
user.getEventManager().waitUntilEventReaches("trackUnpublished", "RoomEvent", 1);
|
||||
user.getEventManager().waitUntilEventReaches("participantDisconnected", "RoomEvent", 1);
|
||||
|
||||
// Try publishing H264 with 3 layer simulcast
|
||||
createIngress(user, "H264_1080P_30FPS_3_LAYERS_HIGH_MOTION", null, true, "HTTP", null);
|
||||
user.getEventManager().waitUntilEventReaches("trackSubscribed", "ParticipantEvent", 1);
|
||||
user.getWaiter().until(ExpectedConditions.numberOfElementsToBe(By.tagName("video"), 1));
|
||||
numberOfVideos = user.getDriver().findElements(By.tagName("video")).size();
|
||||
Assertions.assertEquals(1, numberOfVideos, "Wrong number of videos");
|
||||
Assertions.assertTrue(assertAllElementsHaveTracks(user, "video", false, true),
|
||||
"HTMLVideoElements were expected to have only one video track");
|
||||
|
||||
// Should receive VP8 1920x1080 30 fps
|
||||
subscriberVideo = user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 video.remote"));
|
||||
waitUntilVideoLayersNotEmpty(user, subscriberVideo);
|
||||
Assertions.assertEquals(1, getLayersAsJsonArray(user, subscriberVideo).size());
|
||||
bytesReceived = this.getSubscriberVideoBytesReceived(user, subscriberVideo);
|
||||
this.waitUntilSubscriberBytesReceivedIncrease(user, subscriberVideo, bytesReceived);
|
||||
this.waitUntilSubscriberFramesPerSecondNotZero(user, subscriberVideo);
|
||||
json = this.getLayersAsJsonArray(user, subscriberVideo);
|
||||
subscriberCodec = json.get(0).getAsJsonObject().get("codec").getAsString();
|
||||
expectedCodec = "video/VP8";
|
||||
Assertions.assertEquals(expectedCodec, subscriberCodec);
|
||||
this.waitUntilSubscriberFramesPerSecondIs(user, subscriberVideo, 30);
|
||||
this.waitUntilSubscriberFrameWidthIs(user, subscriberVideo, 1920);
|
||||
this.waitUntilSubscriberFrameHeightIs(user, subscriberVideo, 1080);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("RTSP ingress H264 + OPUS")
|
||||
void rtspIngressH264_OPUSTest() throws Exception {
|
||||
|
|
@ -3649,6 +3739,20 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
|||
Thread.sleep(300);
|
||||
}
|
||||
|
||||
private void setPublisherSimulcastLayersAndResolution(OpenViduTestappUser user, int numberOfUser,
|
||||
String simulcastLayerName, Integer width, Integer height) throws InterruptedException {
|
||||
this.waitForBackdropAndClick(user, "#room-options-btn-" + numberOfUser);
|
||||
Thread.sleep(300);
|
||||
this.setPublisherCustomVideoProperties(user, width, height, null);
|
||||
user.getDriver().findElement(By.id("trackPublish-videoSimulcastLayers")).click();
|
||||
this.waitForBackdropAndClick(user, "#mat-option-" + simulcastLayerName);
|
||||
new org.openqa.selenium.interactions.Actions(user.getDriver())
|
||||
.sendKeys(org.openqa.selenium.Keys.ESCAPE).perform();
|
||||
Thread.sleep(300);
|
||||
this.waitForBackdropAndClick(user, "#close-dialog-btn");
|
||||
Thread.sleep(300);
|
||||
}
|
||||
|
||||
private void setPublisherCustomVideoProperties(OpenViduTestappUser user, Integer width, Integer height,
|
||||
String scalabilityMode) {
|
||||
user.getDriver().findElement(By.id("video-capture-custom")).click();
|
||||
|
|
|
|||
|
|
@ -215,6 +215,14 @@
|
|||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field id="trackPublish-videoSimulcastLayers">
|
||||
<mat-label>videoSimulcastLayers</mat-label>
|
||||
<mat-select multiple [(value)]="trackPublishOptions.videoSimulcastLayers" [compareWith]="compareVideoPresets">
|
||||
@for (item of videoSimulcastLayerPresets; track item.name) {
|
||||
<mat-option [value]="item.preset" [id]="'mat-option-' + item.name">{{item.name}}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="inner-text">
|
||||
<mat-label>name</mat-label>
|
||||
<input matInput id="trackPublish-name" placeholder="name" [(ngModel)]="trackPublishOptions.name"/>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ import {
|
|||
Track,
|
||||
TrackPublishOptions,
|
||||
VideoCaptureOptions,
|
||||
VideoPreset,
|
||||
VideoPresets,
|
||||
} from 'livekit-client';
|
||||
|
||||
@Component({
|
||||
|
|
@ -52,6 +54,25 @@ export class OptionsDialogComponent {
|
|||
|
||||
ENUMERATION_SOURCE = Object.keys(Track.Source);
|
||||
|
||||
// Selectable presets for TrackPublishOptions.videoSimulcastLayers.
|
||||
videoSimulcastLayerPresets: { name: string; preset: VideoPreset }[] = [
|
||||
{ name: 'h90', preset: VideoPresets.h90 },
|
||||
{ name: 'h180', preset: VideoPresets.h180 },
|
||||
{ name: 'h216', preset: VideoPresets.h216 },
|
||||
{ name: 'h360', preset: VideoPresets.h360 },
|
||||
{ name: 'h540', preset: VideoPresets.h540 },
|
||||
{ name: 'h720', preset: VideoPresets.h720 },
|
||||
{ name: 'h1080', preset: VideoPresets.h1080 },
|
||||
{ name: 'h1440', preset: VideoPresets.h1440 },
|
||||
{ name: 'h2160', preset: VideoPresets.h2160 },
|
||||
];
|
||||
|
||||
// mat-select compares option values by reference by default; match presets by
|
||||
// resolution so a preselected videoSimulcastLayers array (possibly built from a
|
||||
// different VideoPreset instance) still highlights the right options.
|
||||
compareVideoPresets = (a: VideoPreset, b: VideoPreset): boolean =>
|
||||
!!a && !!b && a.width === b.width && a.height === b.height;
|
||||
|
||||
inputVideoDevices: MediaDeviceInfo[] = [];
|
||||
|
||||
private data = inject<{
|
||||
|
|
|
|||
Loading…
Reference in New Issue