diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component.html b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component.html
index 799b990943c7..299ddc2f7c93 100644
--- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component.html
+++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component.html
@@ -94,7 +94,7 @@
{{ 'artemisApp.videoUnit.createVideoUnit.sourceRequiredValidationError' | artemisTranslate }}
}
- @if (sourceControl?.errors?.invalidUrl) {
+ @if (sourceControl?.errors?.invalidVideoUrl) {
{{ 'artemisApp.videoUnit.createVideoUnit.sourceURLValidationError' | artemisTranslate }}
diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component.ts b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component.ts
index a872ce716815..0670285084c4 100644
--- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component.ts
+++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component.ts
@@ -1,6 +1,6 @@
import dayjs from 'dayjs/esm';
import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
-import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { AbstractControl, FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms';
import urlParser from 'js-video-url-parser';
import { faArrowLeft, faTimes } from '@fortawesome/free-solid-svg-icons';
import { Competency } from 'app/entities/competency.model';
@@ -13,31 +13,44 @@ export interface VideoUnitFormData {
competencies?: Competency[];
}
-function videoUrlValidator(control: AbstractControl) {
- if (control.value === undefined || control.value === null || control.value === '') {
- return null;
- }
-
- const videoInfo = urlParser.parse(control.value);
- return videoInfo ? null : { invalidVideoUrl: true };
+function isTumLiveUrl(url: URL): boolean {
+ return url.host === 'live.rbg.tum.de';
}
-function urlValidator(control: AbstractControl) {
- let validUrl = true;
+function isVideoOnlyTumUrl(url: URL): boolean {
+ return url?.searchParams.get('video_only') === '1';
+}
- // for certain cases like embed links for vimeo
- const regex = /^\/\/.*$/;
- if (control.value && control.value.match(regex)) {
- return null;
+function videoSourceTransformUrlValidator(control: AbstractControl): ValidationErrors | undefined {
+ const urlValue = control.value;
+ if (!urlValue) {
+ return undefined;
}
-
+ let parsedUrl, url;
try {
- new URL(control.value);
+ url = new URL(urlValue);
+ parsedUrl = urlParser.parse(urlValue);
} catch {
- validUrl = false;
+ //intentionally empty
+ }
+ // The URL is valid if it's a TUM-Live URL or if it can be parsed by the js-video-url-parser.
+ if ((url && isTumLiveUrl(url)) || parsedUrl) {
+ return undefined;
}
+ return { invalidVideoUrl: true };
+}
- return validUrl ? null : { invalidUrl: true };
+function videoSourceUrlValidator(control: AbstractControl): ValidationErrors | undefined {
+ let url;
+ try {
+ url = new URL(control.value);
+ } catch {
+ // intentionally empty
+ }
+ if (url && !(isTumLiveUrl(url) && !isVideoOnlyTumUrl(url))) {
+ return undefined;
+ }
+ return { invalidVideoUrl: true };
}
@Component({
@@ -61,8 +74,8 @@ export class VideoUnitFormComponent implements OnInit, OnChanges {
faTimes = faTimes;
- urlValidator = urlValidator;
- videoUrlValidator = videoUrlValidator;
+ videoSourceUrlValidator = videoSourceUrlValidator;
+ videoSourceTransformUrlValidator = videoSourceTransformUrlValidator;
// Icons
faArrowLeft = faArrowLeft;
@@ -108,8 +121,8 @@ export class VideoUnitFormComponent implements OnInit, OnChanges {
name: [undefined as string | undefined, [Validators.required, Validators.maxLength(255)]],
description: [undefined as string | undefined, [Validators.maxLength(1000)]],
releaseDate: [undefined as dayjs.Dayjs | undefined],
- source: [undefined as string | undefined, [Validators.required, this.urlValidator]],
- urlHelper: [undefined as string | undefined, this.videoUrlValidator],
+ source: [undefined as string | undefined, [Validators.required, this.videoSourceUrlValidator]],
+ urlHelper: [undefined as string | undefined, this.videoSourceTransformUrlValidator],
competencies: [undefined as Competency[] | undefined],
});
}
@@ -141,6 +154,11 @@ export class VideoUnitFormComponent implements OnInit, OnChanges {
}
extractEmbeddedUrl(videoUrl: string) {
+ const url = new URL(videoUrl);
+ if (isTumLiveUrl(url)) {
+ url.searchParams.set('video_only', '1');
+ return url.toString();
+ }
return urlParser.create({
videoInfo: urlParser.parse(videoUrl)!,
format: 'embed',
diff --git a/src/test/javascript/spec/component/lecture-unit/video-unit/video-unit-form.component.spec.ts b/src/test/javascript/spec/component/lecture-unit/video-unit/video-unit-form.component.spec.ts
index 7f2944879dde..5e00cff9c987 100644
--- a/src/test/javascript/spec/component/lecture-unit/video-unit/video-unit-form.component.spec.ts
+++ b/src/test/javascript/spec/component/lecture-unit/video-unit/video-unit-form.component.spec.ts
@@ -43,8 +43,8 @@ describe('VideoUnitFormComponent', () => {
});
it('should not submit a form when name is missing', () => {
- jest.spyOn(videoUnitFormComponent, 'urlValidator').mockReturnValue(null);
- jest.spyOn(videoUnitFormComponent, 'videoUrlValidator').mockReturnValue(null);
+ jest.spyOn(videoUnitFormComponent, 'videoSourceUrlValidator').mockReturnValue(undefined);
+ jest.spyOn(videoUnitFormComponent, 'videoSourceTransformUrlValidator').mockReturnValue(undefined);
videoUnitFormComponentFixture.detectChanges();
const exampleDescription = 'lorem ipsum';
videoUnitFormComponent.descriptionControl!.setValue(exampleDescription);
@@ -67,8 +67,8 @@ describe('VideoUnitFormComponent', () => {
});
it('should not submit a form when source is missing', () => {
- jest.spyOn(videoUnitFormComponent, 'urlValidator').mockReturnValue(null);
- jest.spyOn(videoUnitFormComponent, 'videoUrlValidator').mockReturnValue(null);
+ jest.spyOn(videoUnitFormComponent, 'videoSourceUrlValidator').mockReturnValue(undefined);
+ jest.spyOn(videoUnitFormComponent, 'videoSourceTransformUrlValidator').mockReturnValue(undefined);
videoUnitFormComponentFixture.detectChanges();
const exampleName = 'test';
videoUnitFormComponent.nameControl!.setValue(exampleName);
@@ -92,8 +92,8 @@ describe('VideoUnitFormComponent', () => {
});
it('should submit valid form', () => {
- jest.spyOn(videoUnitFormComponent, 'urlValidator').mockReturnValue(null);
- jest.spyOn(videoUnitFormComponent, 'videoUrlValidator').mockReturnValue(null);
+ jest.spyOn(videoUnitFormComponent, 'videoSourceUrlValidator').mockReturnValue(undefined);
+ jest.spyOn(videoUnitFormComponent, 'videoSourceTransformUrlValidator').mockReturnValue(undefined);
videoUnitFormComponentFixture.detectChanges();
const exampleName = 'test';
videoUnitFormComponent.nameControl!.setValue(exampleName);
@@ -129,8 +129,8 @@ describe('VideoUnitFormComponent', () => {
it('should correctly transform YouTube URL into embeddable format', () => {
jest.spyOn(videoUnitFormComponent, 'extractEmbeddedUrl').mockReturnValue(validYouTubeUrlInEmbeddableFormat);
- jest.spyOn(videoUnitFormComponent, 'urlValidator').mockReturnValue(null);
- jest.spyOn(videoUnitFormComponent, 'videoUrlValidator').mockReturnValue(null);
+ jest.spyOn(videoUnitFormComponent, 'videoSourceUrlValidator').mockReturnValue(undefined);
+ jest.spyOn(videoUnitFormComponent, 'videoSourceTransformUrlValidator').mockReturnValue(undefined);
videoUnitFormComponentFixture.detectChanges();
@@ -144,6 +144,22 @@ describe('VideoUnitFormComponent', () => {
});
});
+ it('should correctly transform TUM-Live URL without video only into embeddable format', () => {
+ const tumLiveUrl = 'https://live.rbg.tum.de/w/test/26';
+ const expectedUrl = 'https://live.rbg.tum.de/w/test/26?video_only=1';
+
+ videoUnitFormComponentFixture.detectChanges();
+ videoUnitFormComponent.urlHelperControl!.setValue(tumLiveUrl);
+ videoUnitFormComponentFixture.detectChanges();
+
+ const transformButton = videoUnitFormComponentFixture.debugElement.nativeElement.querySelector('#transformButton');
+ transformButton.click();
+
+ return videoUnitFormComponentFixture.whenStable().then(() => {
+ expect(videoUnitFormComponent.sourceControl?.value).toEqual(expectedUrl);
+ });
+ });
+
it('should correctly set form values in edit mode', () => {
videoUnitFormComponent.isEditMode = true;
const formData: VideoUnitFormData = {