mirror of https://github.com/OpenVidu/openvidu.git
openvidu-test-e2e: add replaceTrack test
parent
9fa6898db2
commit
e6ea4ee1d8
|
@ -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);
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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, {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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`))
|
||||||
|
|
Loading…
Reference in New Issue