openvidu-test-e2e: add replaceTrack test

pull/780/head
pabloFuente 2023-03-07 15:53:23 +01:00
parent 9fa6898db2
commit e6ea4ee1d8
5 changed files with 92 additions and 48 deletions

View File

@ -86,6 +86,14 @@ public class RecordingUtils {
&& (Math.abs(rgb.get("b") - rgb.get("g")) <= 2); && (Math.abs(rgb.get("b") - rgb.get("g")) <= 2);
} }
public static boolean checkVideoAverageRgbLightGray(Map<String, Long> rgb) {
// GRAY color: {r < 50, g < 50, b < 50} and the absolute difference between them
// not greater than 2
return (rgb.get("r") >= 90) && (rgb.get("g") >= 90) && (rgb.get("b") >= 90) && (rgb.get("r") <= 110)
&& (rgb.get("g") <= 110) && (rgb.get("b") <= 110) && (Math.abs(rgb.get("r") - rgb.get("g")) <= 10)
&& (Math.abs(rgb.get("r") - rgb.get("b")) <= 10) && (Math.abs(rgb.get("b") - rgb.get("g")) <= 10);
}
public static boolean checkVideoAverageRgbRed(Map<String, Long> rgb) { public static boolean checkVideoAverageRgbRed(Map<String, Long> rgb) {
// RED color: {r > 240, g < 15, b <15} // RED color: {r > 240, g < 15, b <15}
return (rgb.get("r") > 240) && (rgb.get("g") < 15) && (rgb.get("b") < 15); return (rgb.get("r") > 240) && (rgb.get("g") < 15) && (rgb.get("b") < 15);

View File

@ -809,6 +809,49 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
gracefullyLeaveParticipants(user, 2); gracefullyLeaveParticipants(user, 2);
} }
@Test
@DisplayName("Replace track")
void replaceTrackTest() throws Exception {
OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome");
log.info("Replace track");
WebElement oneToManyInput = user.getDriver().findElement(By.id("one2many-input"));
oneToManyInput.clear();
oneToManyInput.sendKeys("1");
user.getDriver().findElement(By.id("auto-join-checkbox")).click();
user.getDriver().findElement(By.id("one2many-btn")).click();
user.getEventManager().waitUntilEventReaches("streamCreated", 2);
user.getEventManager().waitUntilEventReaches("streamPlaying", 2);
final CountDownLatch latch = new CountDownLatch(2);
user.getEventManager().on("streamPropertyChanged", (event) -> {
if ("videoTrack".equals(event.get("changedProperty").getAsString())) {
Assertions.assertEquals("trackReplaced", event.get("reason").getAsString(),
"Wrong streamPropertyChanged reason for videoTrack");
latch.countDown();
}
});
user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 .replace-track-btn")).click();
if (!latch.await(3000, TimeUnit.MILLISECONDS)) {
gracefullyLeaveParticipants(user, 2);
fail();
return;
}
WebElement publisherVideo = user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 video"));
WebElement subscriberVideo = user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 video"));
Map<String, Long> rgbPublisher = user.getBrowserUser().getAverageRgbFromVideo(publisherVideo);
Map<String, Long> rgbSubscriber = user.getBrowserUser().getAverageRgbFromVideo(subscriberVideo);
Assertions.assertTrue(RecordingUtils.checkVideoAverageRgbLightGray(rgbPublisher),
"Publisher video is not average gray");
Assertions.assertTrue(RecordingUtils.checkVideoAverageRgbLightGray(rgbSubscriber),
"Subscriber video is not average gray");
}
@Test @Test
@DisplayName("Moderator capabilities") @DisplayName("Moderator capabilities")
void moderatorCapabilitiesTest() throws Exception { void moderatorCapabilitiesTest() throws Exception {

View File

@ -617,53 +617,6 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
this.subscribers.push(session.subscribe(event.stream, undefined)); this.subscribers.push(session.subscribe(event.stream, undefined));
} }
initGrayVideo(): void {
this.OV.getUserMedia(
{
audioSource: undefined,
videoSource: undefined,
resolution: '1280x720',
frameRate: 3,
}
)
.then((mediaStream: MediaStream) => {
const videoStreamTrack: MediaStreamTrack = mediaStream.getVideoTracks()[0];
const video: HTMLVideoElement = document.createElement('video');
video.srcObject = new MediaStream([videoStreamTrack]);
video.play();
const canvas = document.createElement('canvas') as any;
const ctx = canvas.getContext('2d');
ctx.filter = 'grayscale(100%)';
video.addEventListener('play', () => {
const loop = () => {
if (!video.paused && !video.ended) {
ctx.drawImage(video, 0, 0, 300, 170);
setTimeout(loop, 33); // Drawing at 30fps
}
};
loop();
});
const grayVideoTrack: MediaStreamTrack = (<MediaStream>canvas.captureStream(30)).getVideoTracks()[0];
this.OV.getUserMedia({
audioSource: false,
videoSource: grayVideoTrack
}).then(mediastream => {
this.publisher.replaceTrack(mediastream.getVideoTracks()[0])
.then(() => console.log('New track is being published'))
.catch(error => {
console.error('Error replacing track');
console.error(error);
});
});
})
.catch(error => {
console.error(error);
});
}
openSessionPropertiesDialog() { openSessionPropertiesDialog() {
this.sessionProperties.customSessionId = this.sessionName; this.sessionProperties.customSessionId = this.sessionName;
const dialogRef = this.dialog.open(SessionPropertiesDialogComponent, { const dialogRef = this.dialog.open(SessionPropertiesDialogComponent, {

View File

@ -30,7 +30,10 @@
<mat-icon aria-label="Publish or unpublish audio" class="mat-icon material-icons" role="img" aria-hidden="true">{{pubSubAudioIcon}}</mat-icon> <mat-icon aria-label="Publish or unpublish audio" class="mat-icon material-icons" role="img" aria-hidden="true">{{pubSubAudioIcon}}</mat-icon>
</button> </button>
<button *ngIf="!this.unpublished" class="video-btn change-publisher-btn" title="Change publisher" (click)="changePub()"> <button *ngIf="!this.unpublished" class="video-btn change-publisher-btn" title="Change publisher" (click)="changePub()">
<mat-icon aria-label="Change publisher" class="mat-icon material-icons" role="img" aria-hidden="true">switch_video</mat-icon> <mat-icon aria-label="Change publisher" class="mat-icon material-icons" role="img" aria-hidden="true">cameraswitch</mat-icon>
</button>
<button *ngIf="!this.unpublished" class="video-btn replace-track-btn" title="Replace track" (click)="replaceTrack()">
<mat-icon aria-label="Replace track" class="mat-icon material-icons" role="img" aria-hidden="true">switch_video</mat-icon>
</button> </button>
<button *ngIf="!this.unpublished" class="video-btn reconnect-publisher-btn" title="Reconnect publisher" (click)="reconnect()"> <button *ngIf="!this.unpublished" class="video-btn reconnect-publisher-btn" title="Reconnect publisher" (click)="reconnect()">
<mat-icon aria-label="Reconnect publisher" class="mat-icon material-icons" role="img" aria-hidden="true">refresh</mat-icon> <mat-icon aria-label="Reconnect publisher" class="mat-icon material-icons" role="img" aria-hidden="true">refresh</mat-icon>

View File

@ -46,6 +46,7 @@ export class VideoComponent implements OnInit, OnDestroy {
unpublished = false; unpublished = false;
publisherChanged = false; publisherChanged = false;
trackReplaced = false;
audioMuted = false; audioMuted = false;
videoMuted = false; videoMuted = false;
sendAudio = true; sendAudio = true;
@ -321,6 +322,42 @@ export class VideoComponent implements OnInit, OnDestroy {
this.publisherChanged = !this.publisherChanged; this.publisherChanged = !this.publisherChanged;
} }
async replaceTrack() {
let newMediaStream: MediaStream;
let newVideoTrack: MediaStreamTrack;
const originalPublisherProperties = this.streamManager.stream.outboundStreamOpts.publisherProperties;
if (!this.trackReplaced) {
newMediaStream = await this.OV.getUserMedia(originalPublisherProperties);
const videoStreamTrack: MediaStreamTrack = newMediaStream.getVideoTracks()[0];
const video: HTMLVideoElement = document.createElement('video');
video.srcObject = new MediaStream([videoStreamTrack]);
video.play();
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.filter = 'grayscale(100%)';
video.addEventListener('play', () => {
const loop = () => {
if (!video.paused && !video.ended) {
ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, video.videoWidth, video.videoHeight);
setTimeout(loop, 33); // Drawing at 30fps
}
};
loop();
});
newVideoTrack = canvas.captureStream(30).getVideoTracks()[0];
} else {
newMediaStream = await this.OV.getUserMedia(originalPublisherProperties);
newVideoTrack = newMediaStream.getVideoTracks()[0];
}
try {
await (this.streamManager as Publisher).replaceTrack(newVideoTrack);
console.log('Track replaced');
} catch (error) {
console.error('Error replacing track', error);
}
this.trackReplaced = !this.trackReplaced;
}
reconnect() { reconnect() {
this.streamManager.stream.reconnect() this.streamManager.stream.reconnect()
.then(() => console.log(`Stream ${this.streamManager.stream} (${this.streamManager.remote ? 'Subscriber' : 'Publisher'}) successfully reconnected`)) .then(() => console.log(`Stream ${this.streamManager.stream} (${this.streamManager.remote ? 'Subscriber' : 'Publisher'}) successfully reconnected`))