openvidu-test-app: allow forcing encoder resolution change on a video publisher with LocalVideoTrack.restartTrack method

pull/907/head
pabloFuente 2026-09-04 13:55:45 +02:00
parent adb21bc6c4
commit ab3c341646
3 changed files with 41 additions and 11 deletions

View File

@ -56,18 +56,31 @@ video {
/* Keep the form-field visually compact (16×16 clipped box).
The CDK overlay uses the Popover API (top layer) so it is
immune to overflow:hidden and renders at its natural size. */
#max-video-quality {
#max-video-quality,
#restart-video-resolution {
width: 16px;
height: 16px;
overflow: hidden;
font-size: 10px;
font-weight: bold;
--mat-form-field-container-vertical-padding: 0px;
}
::ng-deep #max-video-quality .mdc-notched-outline * {
/* The MDC outlined text field reserves 16px of horizontal padding
for the value, which alone exceeds this 16px-wide box and pushes
the value/arrow trigger (and therefore the dropdown svg) out of
the visible area. Removing it keeps the trigger inside the clip. */
::ng-deep #max-video-quality .mat-mdc-text-field-wrapper,
::ng-deep #restart-video-resolution .mat-mdc-text-field-wrapper {
padding: 0;
}
::ng-deep #max-video-quality .mdc-notched-outline *,
::ng-deep #restart-video-resolution .mdc-notched-outline * {
border-color: transparent !important;
}
::ng-deep #max-video-quality .mat-mdc-form-field-subscript-wrapper {
::ng-deep #max-video-quality .mat-mdc-form-field-subscript-wrapper,
::ng-deep #restart-video-resolution .mat-mdc-form-field-subscript-wrapper {
display: none !important;
}

View File

@ -7,20 +7,26 @@
<mat-icon aria-label="Mute/Unmute video" class="mat-icon material-icons" role="img"
aria-hidden="true">{{muteVideoIcon}}</mat-icon>
</button>
}
@if (localParticipant) {
<button (click)="unpublishTrack()" class="video-btn publish-unpublish-video" matTooltip="Unpublish track"
matTooltipClass="custom-tooltip">
<mat-icon aria-label="Unpublish track" class="mat-icon material-icons" role="img"
aria-hidden="true">stop</mat-icon>
</button>
}
@if (localParticipant) {
<button (click)="openProcessorDialog()" class="video-btn toggle-track-processor" matTooltip="Open track processor settings"
matTooltipClass="custom-tooltip">
<mat-icon aria-label="Open track processor settings" class="mat-icon material-icons" role="img"
aria-hidden="true">auto_fix_high</mat-icon>
</button>
<mat-form-field id="restart-video-resolution" class="video-btn quality-option" matTooltip="Restart video at resolution" matTooltipClass="custom-tooltip">
<mat-select [(value)]="restartResolution" (selectionChange)="onRestartResolutionChange()">
@for (r of ['1920x1080', '1280x720', '640x360', '320x180']; track r) {
<mat-option [value]="r" [ngClass]="'res-' + r">{{r}}</mat-option>
}
</mat-select>
</mat-form-field>
}
@if (!localParticipant) {
<button (click)="toggleEnableTrack()" class="video-btn toggle-video-enabled" matTooltip="Toggle track enabled"
@ -28,15 +34,13 @@
<mat-icon aria-label="Toggle track enabled" class="mat-icon material-icons" role="img"
aria-hidden="true">{{trackEnabled ? 'toggle_off' : 'toggle_on'}}</mat-icon>
</button>
}
@if (!localParticipant) {
<button (click)="toggleSubscribeTrack()" class="video-btn toggle-video-subscribed" matTooltip="Toggle track subscription"
matTooltipClass="custom-tooltip">
<mat-icon aria-label="Toggle track subscription" class="mat-icon material-icons" role="img"
aria-hidden="true">{{trackSubscribed ? 'stop' : 'play_arrow'}}</mat-icon>
</button>
}
@if (!localParticipant) {
<mat-form-field id="max-video-quality" class="video-btn quality-option" matTooltip="Set video quality" matTooltipClass="custom-tooltip">
<mat-select [(value)]="maxVideoQuality" (selectionChange)="onQualityChange()">
@for (q of ['LOW', 'MEDIUM', 'HIGH']; track q) {

View File

@ -2,6 +2,7 @@ import { Component, inject, ChangeDetectionStrategy } from '@angular/core';
import { NgClass } from '@angular/common';
import {
LocalTrack,
LocalVideoTrack,
VideoTrack,
RemoteTrackPublication,
VideoQuality,
@ -32,6 +33,8 @@ import { MatSelectModule } from '@angular/material/select';
export class VideoTrackComponent extends TrackComponent {
muteVideoIcon: string = 'videocam';
maxVideoQuality: string;
// Resolution to re-capture the local video track at (see onRestartResolutionChange)
restartResolution: string;
videoZoom = false;
@ -96,6 +99,16 @@ export class VideoTrackComponent extends TrackComponent {
);
}
// Re-captures the local video track at the selected resolution. Useful to make
// the encoder change its layer structure on the fly: an SVC encoder drops its
// upper spatial layers when the capture is too small to host them
async onRestartResolutionChange() {
const [width, height] = this.restartResolution.split('x').map(Number);
await (this._track as LocalVideoTrack).restartTrack({
resolution: { width, height, frameRate: 30 },
});
}
openInfoDialog() {
const updateFunction = async (): Promise<string> => {
const videoLayers: any[] = [];